splitting functionality out of main
This commit is contained in:
parent
b7f7d87563
commit
8af2fec11b
10 changed files with 722 additions and 627 deletions
|
|
@ -3,6 +3,7 @@
|
||||||
import alerts from './alerts.js';
|
import alerts from './alerts.js';
|
||||||
import settings from './config/manager.js';
|
import settings from './config/manager.js';
|
||||||
import STACKS from './stacks.js';
|
import STACKS from './stacks.js';
|
||||||
|
import { visuals } from './visuals.js';
|
||||||
|
|
||||||
import { broker } from '../../moto/broker.js';
|
import { broker } from '../../moto/broker.js';
|
||||||
import { client as work } from './client.js';
|
import { client as work } from './client.js';
|
||||||
|
|
@ -191,6 +192,7 @@ export const api = {
|
||||||
},
|
},
|
||||||
version,
|
version,
|
||||||
view,
|
view,
|
||||||
|
visuals,
|
||||||
web,
|
web,
|
||||||
widgets,
|
widgets,
|
||||||
work,
|
work,
|
||||||
|
|
|
||||||
17
src/kiri/ui/groups.js
Normal file
17
src/kiri/ui/groups.js
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||||
|
|
||||||
|
import { selection } from './select.js';
|
||||||
|
import { Widget } from '../core/widget.js';
|
||||||
|
|
||||||
|
function groupMerge() {
|
||||||
|
Widget.Groups.merge(selection.widgets(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
function groupSplit() {
|
||||||
|
Widget.Groups.split(selection.widgets(false));
|
||||||
|
}
|
||||||
|
|
||||||
|
export const group = {
|
||||||
|
merge: groupMerge,
|
||||||
|
split: groupSplit,
|
||||||
|
};
|
||||||
28
src/kiri/ui/help.js
Normal file
28
src/kiri/ui/help.js
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||||
|
|
||||||
|
import { $ } from '../../moto/webui.js';
|
||||||
|
import { api } from './api.js';
|
||||||
|
import { modal } from './modal.js';
|
||||||
|
import { version } from '../../moto/license.js';
|
||||||
|
|
||||||
|
const WIN = self.window;
|
||||||
|
|
||||||
|
function showHelp() {
|
||||||
|
showHelpFile(`local`,() => {});
|
||||||
|
}
|
||||||
|
|
||||||
|
function showHelpFile(local,then) {
|
||||||
|
if (!local) {
|
||||||
|
WIN.open("//docs.grid.space/", "_help");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const LANG = api.language.current;
|
||||||
|
$('kiri-version').innerHTML = `${LANG.version} ${version}`;
|
||||||
|
modal.show('help');
|
||||||
|
api.event.emit('help.show', local);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const help = {
|
||||||
|
show: showHelp,
|
||||||
|
file: showHelpFile
|
||||||
|
};
|
||||||
84
src/kiri/ui/image.js
Normal file
84
src/kiri/ui/image.js
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||||
|
|
||||||
|
import { $ } from '../../moto/webui.js';
|
||||||
|
import { api } from './api.js';
|
||||||
|
import { newWidget } from '../core/widget.js';
|
||||||
|
import { platform } from './platform.js';
|
||||||
|
import { settings } from './config/manager.js';
|
||||||
|
|
||||||
|
function loadImageDialog(image, name, force) {
|
||||||
|
if (!force && image.byteLength > 2500000) {
|
||||||
|
return api.uc.confirm("Large images may fail to import<br>Consider resizing under 1000 x 1000<br>Proceed with import?").then(ok => {
|
||||||
|
if (ok) {
|
||||||
|
loadImageDialog(image, name, true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const opt = {pre: [
|
||||||
|
"<div class='f-col a-center'>",
|
||||||
|
" <h3>Image Conversion</h3>",
|
||||||
|
" <p class='t-just' style='width:300px;line-height:1.5em'>",
|
||||||
|
" This will create a 3D model from a 2D PNG image. Photos must",
|
||||||
|
" be blurred to be usable. Values from 0=off to 50=high are suggested.",
|
||||||
|
" Higher values incur more processing time.",
|
||||||
|
" </p>",
|
||||||
|
" <div class='f-row t-right'><table>",
|
||||||
|
" <tr><th>blur value</th><td><input id='png-blur' value='0' size='3'></td>",
|
||||||
|
" <th> invert image</th><td><input id='png-inv' type='checkbox'></td></tr>",
|
||||||
|
" <tr><th>base size</th><td><input id='png-base' value='0' size='3'></td>",
|
||||||
|
" <th> invert alpha</th><td><input id='alpha-inv' type='checkbox'></td></tr>",
|
||||||
|
" <tr><th>border size</th><td><input id='png-border' value='0' size='3'></td>",
|
||||||
|
" <th></th><td></td></tr>",
|
||||||
|
" </table></div>",
|
||||||
|
"</div>"
|
||||||
|
]};
|
||||||
|
api.uc.confirm(undefined, {convert:true, cancel:false}, undefined, opt).then((ok) => {
|
||||||
|
if (ok) {
|
||||||
|
loadImage(image, {
|
||||||
|
file: name,
|
||||||
|
blur: parseInt($('png-blur').value) || 0,
|
||||||
|
base: parseInt($('png-base').value) || 0,
|
||||||
|
border: parseInt($('png-border').value) || 0,
|
||||||
|
inv_image: $('png-inv').checked,
|
||||||
|
inv_alpha: $('alpha-inv').checked
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadImage(image, opt = {}) {
|
||||||
|
const info = Object.assign({settings: settings.get(), png:image}, opt);
|
||||||
|
api.client.image2mesh(info, progress => {
|
||||||
|
api.show.progress(progress, "converting");
|
||||||
|
}, vertices => {
|
||||||
|
api.show.progress(0);
|
||||||
|
const widget = newWidget().loadVertices(vertices);
|
||||||
|
widget.meta.file = opt.file;
|
||||||
|
platform.add(widget);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert any image type to png
|
||||||
|
function loadImageConvert(res, name) {
|
||||||
|
let url = URL.createObjectURL(new Blob([res]));
|
||||||
|
|
||||||
|
$('mod-any').innerHTML = `<img id="xsrc" src="${url}"><canvas id="xdst"></canvas>`;
|
||||||
|
|
||||||
|
let img = $('xsrc');
|
||||||
|
let can = $('xdst');
|
||||||
|
|
||||||
|
img.onload = () => {
|
||||||
|
can.width = img.width;
|
||||||
|
can.height = img.height;
|
||||||
|
let ctx = can.getContext('2d');
|
||||||
|
ctx.drawImage(img, 0, 0);
|
||||||
|
fetch(can.toDataURL()).then(r => r.arrayBuffer()).then(data => {
|
||||||
|
loadImageDialog(data, name);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const image = {
|
||||||
|
dialog: loadImageDialog,
|
||||||
|
convert: loadImageConvert
|
||||||
|
};
|
||||||
|
|
@ -534,13 +534,20 @@ function isNotBelt() {
|
||||||
// MAIN INITIALIZATION FUNCTION
|
// MAIN INITIALIZATION FUNCTION
|
||||||
function init_one() {
|
function init_one() {
|
||||||
let { event, conf, view, show } = api,
|
let { event, conf, view, show } = api,
|
||||||
{ bound, toInt, toFloat } = uc,
|
|
||||||
{ newBlank, newButton, newBoolean, newGroup, newInput } = uc,
|
{ newBlank, newButton, newBoolean, newGroup, newInput } = uc,
|
||||||
{ newSelect, newLabel, newValue, newRow, newGCode, newDiv } = uc;
|
{ newSelect, newRow, newGCode, newDiv, toInt, toFloat } = uc;
|
||||||
|
|
||||||
event.emit('init.one');
|
event.emit('init.one');
|
||||||
|
|
||||||
// ensure we have settings from last session
|
// restore kiri-init vars
|
||||||
|
let inits = parseInt(sdb.getItem('kiri-init') || stats.get('init') || 0) + 1;
|
||||||
|
|
||||||
|
// update version and init count
|
||||||
|
sdb.setItem('kiri-init', inits);
|
||||||
|
stats.set('init', inits);
|
||||||
|
stats.set('kiri', version);
|
||||||
|
|
||||||
|
// restore settings from last saved session
|
||||||
conf.restore();
|
conf.restore();
|
||||||
|
|
||||||
let container = $('container'),
|
let container = $('container'),
|
||||||
|
|
|
||||||
|
|
@ -1,31 +1,30 @@
|
||||||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||||
|
|
||||||
import './frame.js';
|
|
||||||
import '../../ext/base64.js';
|
import '../../ext/base64.js';
|
||||||
|
|
||||||
import STACKS from './stacks.js';
|
|
||||||
import { modal } from './modal.js';
|
import { modal } from './modal.js';
|
||||||
|
|
||||||
import { $ } from '../../moto/webui.js';
|
import { $ } from '../../moto/webui.js';
|
||||||
import { api } from './api.js';
|
import { api } from './api.js';
|
||||||
import { broker } from '../../moto/broker.js';
|
import { broker } from '../../moto/broker.js';
|
||||||
import { consts } from '../core/consts.js';
|
|
||||||
import { Index } from '../../data/index.js';
|
import { Index } from '../../data/index.js';
|
||||||
import { local as dataLocal } from '../../data/local.js';
|
|
||||||
import { openFiles } from './files.js';
|
import { openFiles } from './files.js';
|
||||||
import { platform } from './platform.js';
|
|
||||||
import { selection } from './select.js';
|
|
||||||
import { settings } from './config/manager.js';
|
import { settings } from './config/manager.js';
|
||||||
import { showDevices } from './devices.js';
|
import { showDevices } from './devices.js';
|
||||||
import { space as SPACE } from '../../moto/space.js';
|
|
||||||
import { stats } from './stats.js';
|
|
||||||
import { noop, utils } from '../core/utils.js';
|
import { noop, utils } from '../core/utils.js';
|
||||||
import { version } from '../../moto/license.js';
|
import { version } from '../../moto/license.js';
|
||||||
import { Widget, newWidget } from '../core/widget.js';
|
|
||||||
import { showTools } from '../mode/cam/tools.js';
|
import { showTools } from '../mode/cam/tools.js';
|
||||||
|
|
||||||
let { parseOpt, o2js, js2o, ls2o } = utils,
|
// Import new modules
|
||||||
{ COLOR, MODES, VIEWS } = consts,
|
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,
|
WIN = self.window,
|
||||||
DOC = self.document,
|
DOC = self.document,
|
||||||
LOC = self.location,
|
LOC = self.location,
|
||||||
|
|
@ -33,18 +32,7 @@ let { parseOpt, o2js, js2o, ls2o } = utils,
|
||||||
SECURE = isSecure(LOC.protocol),
|
SECURE = isSecure(LOC.protocol),
|
||||||
LOCAL = self.debug && !SETUP.remote,
|
LOCAL = self.debug && !SETUP.remote,
|
||||||
EVENT = broker,
|
EVENT = broker,
|
||||||
SDB = dataLocal,
|
FILES = openFiles(new Index(SETUP.d ? SETUP.d[0] : 'kiri'));
|
||||||
FILES = openFiles(new Index(SETUP.d ? SETUP.d[0] : 'kiri')),
|
|
||||||
clone = Object.clone,
|
|
||||||
MODE = MODES.FDM,
|
|
||||||
viewMode = VIEWS.ARRANGE,
|
|
||||||
autoSaveTimer = null,
|
|
||||||
inits = parseInt(SDB.getItem('kiri-init') || stats.get('init') || 0) + 1;
|
|
||||||
|
|
||||||
// update version and init count
|
|
||||||
SDB.setItem('kiri-init', inits);
|
|
||||||
stats.set('init', inits);
|
|
||||||
stats.set('kiri', version);
|
|
||||||
|
|
||||||
// allow widget to straddle client / worker FOR NOW
|
// allow widget to straddle client / worker FOR NOW
|
||||||
self.kiri_catalog = FILES;
|
self.kiri_catalog = FILES;
|
||||||
|
|
@ -55,10 +43,8 @@ export const dialog = {
|
||||||
update_process_list: updateProcessList
|
update_process_list: updateProcessList
|
||||||
};
|
};
|
||||||
|
|
||||||
export const help = {
|
// Re-export help from new module
|
||||||
show: showHelp,
|
export const help = helpModule;
|
||||||
file: showHelpFile
|
|
||||||
};
|
|
||||||
|
|
||||||
export const event = {
|
export const event = {
|
||||||
on(t,l) { return EVENT.on(t,l) },
|
on(t,l) { return EVENT.on(t,l) },
|
||||||
|
|
@ -69,74 +55,39 @@ export const event = {
|
||||||
settings: triggerSettingsEvent
|
settings: triggerSettingsEvent
|
||||||
};
|
};
|
||||||
|
|
||||||
export const group = {
|
// Re-export group from new module
|
||||||
merge: groupMerge,
|
export const group = groupModule;
|
||||||
split: groupSplit,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const hide = {
|
export const hide = {
|
||||||
alert(rec, recs) { api.alerts.hide(...arguments) },
|
alert(rec, recs) { api.alerts.hide(...arguments) },
|
||||||
import: noop,
|
import: noop,
|
||||||
slider: hideSlider
|
slider: visuals.hide_slider
|
||||||
};
|
};
|
||||||
|
|
||||||
export const image = {
|
// Re-export image from new module
|
||||||
dialog: loadImageDialog,
|
export const image = imageModule;
|
||||||
convert: loadImageConvert
|
|
||||||
};
|
|
||||||
|
|
||||||
// Re-export modal singleton methods
|
// Re-export modal singleton methods
|
||||||
export { modal };
|
export { modal };
|
||||||
|
|
||||||
export const mode = {
|
// Re-export mode and process from new module
|
||||||
get_id() { return MODE },
|
export const mode = modeModule;
|
||||||
get_lower: getModeLower,
|
export const process = processModule;
|
||||||
get: getMode,
|
|
||||||
set: setMode,
|
|
||||||
switch: switchMode,
|
|
||||||
set_expert: noop,
|
|
||||||
is_fdm() { return MODE === MODES.FDM },
|
|
||||||
is_cam() { return MODE === MODES.CAM },
|
|
||||||
is_sla() { return MODE === MODES.SLA },
|
|
||||||
is_drag() { return MODE === MODES.DRAG },
|
|
||||||
is_wedm() { return MODE === MODES.WEDM },
|
|
||||||
is_wjet() { return MODE === MODES.WJET },
|
|
||||||
is_laser() { return MODE === MODES.LASER },
|
|
||||||
is_2d() { return false ||
|
|
||||||
api.mode.is_drag() ||
|
|
||||||
api.mode.is_wedm() ||
|
|
||||||
api.mode.is_wjet() ||
|
|
||||||
api.mode.is_laser()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const process = {
|
|
||||||
code: currentProcessCode,
|
|
||||||
get: currentProcessName
|
|
||||||
};
|
|
||||||
|
|
||||||
export const show = {
|
export const show = {
|
||||||
alert() { return api.alerts.show(...arguments) },
|
alert() { return api.alerts.show(...arguments) },
|
||||||
controls: setControlsVisible,
|
controls() { console.trace('deprecated') },
|
||||||
devices: showDevices,
|
devices: showDevices,
|
||||||
import() { api.ui.import.style.display = '' },
|
import() { api.ui.import.style.display = '' },
|
||||||
layer: setVisibleLayer,
|
layer: visuals.set_visible_layer,
|
||||||
local: showLocal,
|
local() { console.trace('deprecated') },
|
||||||
progress: setProgress,
|
progress: setProgress,
|
||||||
slices: showSlices,
|
slices: visuals.show_slices,
|
||||||
tools: showTools
|
tools: showTools
|
||||||
};
|
};
|
||||||
|
|
||||||
export const space = {
|
// Re-export space (workspace) from new module
|
||||||
reload,
|
export const space = workspace;
|
||||||
auto_save,
|
|
||||||
restore: restoreWorkspace,
|
|
||||||
clear: clearWorkspace,
|
|
||||||
save: saveWorkspace,
|
|
||||||
set_focus: setFocus,
|
|
||||||
update: SPACE.update,
|
|
||||||
is_dark() { return settings.ctrl().dark }
|
|
||||||
};
|
|
||||||
|
|
||||||
export const util = {
|
export const util = {
|
||||||
isSecure,
|
isSecure,
|
||||||
|
|
@ -147,26 +98,11 @@ export const util = {
|
||||||
b64dec(obj) { return JSON.parse(new TextDecoder().decode(base64js.toByteArray(obj))) }
|
b64dec(obj) { return JSON.parse(new TextDecoder().decode(base64js.toByteArray(obj))) }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Combine view-state and visuals modules
|
||||||
export const view = {
|
export const view = {
|
||||||
get() { return viewMode },
|
...viewModule,
|
||||||
set() { setViewMode(...arguments) },
|
...visuals,
|
||||||
set_arrange() { api.view.set(VIEWS.ARRANGE) },
|
snapshot: null
|
||||||
set_slice() { api.view.set(VIEWS.SLICE) },
|
|
||||||
set_preview() { api.view.set(VIEWS.PREVIEW) },
|
|
||||||
set_animate() { api.view.set(VIEWS.ANIMATE) },
|
|
||||||
is_arrange() { return viewMode === VIEWS.ARRANGE },
|
|
||||||
is_slice() { return viewMode === VIEWS.SLICE },
|
|
||||||
is_preview() { return viewMode === VIEWS.PREVIEW },
|
|
||||||
is_animate() { return viewMode === VIEWS.ANIMATE },
|
|
||||||
update_stack_labels: updateStackLabelState,
|
|
||||||
update_slider_max: updateSliderMax,
|
|
||||||
update_slider: updateSlider,
|
|
||||||
update_speeds: updateSpeeds,
|
|
||||||
hide_slices: hideSlices,
|
|
||||||
snapshot: null,
|
|
||||||
edges: setEdges,
|
|
||||||
unit_scale: unitScale,
|
|
||||||
wireframe: setWireframe
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// add show() to catalog for API
|
// add show() to catalog for API
|
||||||
|
|
@ -178,80 +114,10 @@ EVENT.on = (topic, listener) => {
|
||||||
return EVENT;
|
return EVENT;
|
||||||
};
|
};
|
||||||
|
|
||||||
function updateStackLabelState() {
|
|
||||||
const settings = api.conf.get();
|
|
||||||
// match label checkboxes to preference
|
|
||||||
for (let label of api.stacks.getLabels()) {
|
|
||||||
let check = `${settings.mode}-${api.view.get()}-${label}`;
|
|
||||||
api.stacks.setVisible(label, settings.labels[check] !== false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function setFocus(sel, point) {
|
|
||||||
if (point) {
|
|
||||||
SPACE.platform.setCenter(point.x, point.z, point.y);
|
|
||||||
SPACE.view.setFocus(new THREE.Vector3(point.x, point.y, point.z));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (sel === undefined) {
|
|
||||||
sel = api.widgets.all();
|
|
||||||
} else if (!Array.isArray) {
|
|
||||||
sel = [ sel ];
|
|
||||||
} else if (sel.length === 0) {
|
|
||||||
sel = api.widgets.all();
|
|
||||||
}
|
|
||||||
let pos = { x:0, y:0, z:0 };
|
|
||||||
for (let widget of sel) {
|
|
||||||
pos.x += widget.track.pos.x;
|
|
||||||
pos.y += widget.track.pos.y;
|
|
||||||
pos.z += widget.track.pos.z;
|
|
||||||
}
|
|
||||||
if (sel.length) {
|
|
||||||
pos.x /= sel.length;
|
|
||||||
pos.y /= sel.length;
|
|
||||||
pos.z /= sel.length;
|
|
||||||
}
|
|
||||||
let cam_index = api.conf.get().process.camStockIndexed || false;
|
|
||||||
let focus_z = cam_index ? 0 : platform.top_z() / 2;
|
|
||||||
SPACE.platform.setCenter(pos.x, -pos.y, focus_z);
|
|
||||||
SPACE.view.setFocus(new THREE.Vector3(pos.x, focus_z, -pos.y));
|
|
||||||
}
|
|
||||||
|
|
||||||
function reload() {
|
|
||||||
api.event.emit('reload');
|
|
||||||
do_reload(100);
|
|
||||||
}
|
|
||||||
|
|
||||||
function do_reload(time) {
|
|
||||||
// allow time for async saves to complete and busy to to to zero
|
|
||||||
setTimeout(() => {
|
|
||||||
if (api.busy.val() === 0) {
|
|
||||||
LOC.reload();
|
|
||||||
} else {
|
|
||||||
console.log(`reload deferred on busy=${api.busy.val()}`);
|
|
||||||
do_reload(250);
|
|
||||||
}
|
|
||||||
}, time || 100);
|
|
||||||
}
|
|
||||||
|
|
||||||
function auto_save() {
|
|
||||||
if (!settings.ctrl().autoSave) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
clearTimeout(autoSaveTimer);
|
|
||||||
autoSaveTimer = setTimeout(() => {
|
|
||||||
api.space.save(true);
|
|
||||||
}, 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** ******************************************************************
|
/** ******************************************************************
|
||||||
* Utility Functions
|
* Utility Functions
|
||||||
******************************************************************* */
|
******************************************************************* */
|
||||||
|
|
||||||
function unitScale() {
|
|
||||||
return api.mode.is_cam() && settings.ctrl().units === 'in' ? 25.4 : 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
function triggerSettingsEvent() {
|
function triggerSettingsEvent() {
|
||||||
api.event.emit('settings', settings.get());
|
api.event.emit('settings', settings.get());
|
||||||
}
|
}
|
||||||
|
|
@ -274,234 +140,6 @@ function setProgress(value = 0, msg) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function bound(v,min,max) {
|
|
||||||
return Math.max(min,Math.min(max,v));
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateSpeeds(maxSpeed, minSpeed) {
|
|
||||||
const { ui } = api;
|
|
||||||
ui.speeds.style.display =
|
|
||||||
maxSpeed &&
|
|
||||||
settings.mode !== 'SLA' &&
|
|
||||||
settings.mode !== 'LASER' &&
|
|
||||||
viewMode === VIEWS.PREVIEW &&
|
|
||||||
ui.showSpeeds.checked ? 'block' : '';
|
|
||||||
if (maxSpeed) {
|
|
||||||
const colors = [];
|
|
||||||
for (let i = 0; i <= maxSpeed; i += maxSpeed / 20) {
|
|
||||||
colors.push(Math.round(Math.max(i, 1)));
|
|
||||||
}
|
|
||||||
api.client.colors(colors, maxSpeed, speedColors => {
|
|
||||||
const list = [];
|
|
||||||
Object.keys(speedColors).map(v => parseInt(v)).sort((a, b) => b - a).forEach(speed => {
|
|
||||||
const color = speedColors[speed];
|
|
||||||
const hex = color.toString(16).padStart(6, 0);
|
|
||||||
const r = (color >> 16) & 0xff;
|
|
||||||
const g = (color >> 8) & 0xff;
|
|
||||||
const b = (color >> 0) & 0xff;
|
|
||||||
const style = `background-color:#${hex}`;
|
|
||||||
list.push(`<label style="${style}">${speed}</label>`);
|
|
||||||
});
|
|
||||||
ui.speedbar.innerHTML = list.join('');
|
|
||||||
});
|
|
||||||
api.event.emit('preview.speeds', {
|
|
||||||
min: minSpeed,
|
|
||||||
max: maxSpeed
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateSlider() {
|
|
||||||
const { lo, hi, max } = api.slider.getRange();
|
|
||||||
if (max > 0) {
|
|
||||||
api.slider.updatePosition(lo / max, hi / max);
|
|
||||||
}
|
|
||||||
api.conf.update_fields_from_range();
|
|
||||||
}
|
|
||||||
|
|
||||||
function setVisibleLayer(h, l) {
|
|
||||||
const { lo, hi, max } = api.slider.getRange();
|
|
||||||
h = h >= 0 ? h : hi;
|
|
||||||
l = l >= 0 ? l : lo;
|
|
||||||
const newHi = bound(h, 0, max);
|
|
||||||
const newLo = bound(l, 0, newHi);
|
|
||||||
api.slider.setRange(newLo, newHi); // Notify callbacks (STACKS, scene updates)
|
|
||||||
api.slider.showLabels();
|
|
||||||
showSlices();
|
|
||||||
}
|
|
||||||
|
|
||||||
function setWireframe(bool, color, opacity) {
|
|
||||||
api.widgets.each(function(w) { w.setWireframe(bool, color, opacity) });
|
|
||||||
SPACE.update();
|
|
||||||
}
|
|
||||||
|
|
||||||
function setEdges(bool) {
|
|
||||||
if (bool && bool.toggle) {
|
|
||||||
api.local.toggle('model.edges');
|
|
||||||
} else {
|
|
||||||
api.local.set('model.edges', bool);
|
|
||||||
}
|
|
||||||
bool = api.local.getBoolean('model.edges');
|
|
||||||
api.widgets.each(w => w.setEdges(bool));
|
|
||||||
SPACE.update();
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateSliderMax(set) {
|
|
||||||
let max = STACKS.getRange().tallest - 1;
|
|
||||||
api.slider.setMax(max);
|
|
||||||
const { hi } = api.slider.getRange();
|
|
||||||
if (set || max < hi) {
|
|
||||||
api.slider.setRange(max, max);
|
|
||||||
api.slider.showLabels();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function hideSlices() {
|
|
||||||
STACKS.clear();
|
|
||||||
api.widgets.setOpacity(COLOR.model_opacity);
|
|
||||||
api.widgets.each(function(widget) {
|
|
||||||
widget.setWireframe(false);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function setWidgetVisibility(bool) {
|
|
||||||
api.widgets.each(w => {
|
|
||||||
if (bool) {
|
|
||||||
w.show();
|
|
||||||
} else {
|
|
||||||
w.hide();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* hide or show slice-layers and their sub-elements
|
|
||||||
*
|
|
||||||
* @param {number} [layer]
|
|
||||||
*/
|
|
||||||
function showSlices(layer) {
|
|
||||||
if (viewMode === VIEWS.ARRANGE) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
showSlider();
|
|
||||||
|
|
||||||
const { lo, hi, max } = api.slider.getRange();
|
|
||||||
|
|
||||||
if (typeof(layer) === 'string' || typeof(layer) === 'number') {
|
|
||||||
layer = parseInt(layer);
|
|
||||||
} else {
|
|
||||||
layer = hi;
|
|
||||||
}
|
|
||||||
|
|
||||||
layer = bound(layer, 0, max);
|
|
||||||
const newLo = layer < lo ? layer : lo;
|
|
||||||
api.slider.setRange(newLo, layer);
|
|
||||||
api.slider.showLabels();
|
|
||||||
|
|
||||||
updateSlider();
|
|
||||||
// STACKS.setRange is called by slider callback
|
|
||||||
|
|
||||||
SPACE.update();
|
|
||||||
}
|
|
||||||
|
|
||||||
function showSlider() {
|
|
||||||
api.slider.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
function hideSlider() {
|
|
||||||
api.slider.hide();
|
|
||||||
api.ui.speeds.style.display = 'none';
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadImageDialog(image, name, force) {
|
|
||||||
if (!force && image.byteLength > 2500000) {
|
|
||||||
return api.uc.confirm("Large images may fail to import<br>Consider resizing under 1000 x 1000<br>Proceed with import?").then(ok => {
|
|
||||||
if (ok) {
|
|
||||||
loadImageDialog(image, name, true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const opt = {pre: [
|
|
||||||
"<div class='f-col a-center'>",
|
|
||||||
" <h3>Image Conversion</h3>",
|
|
||||||
" <p class='t-just' style='width:300px;line-height:1.5em'>",
|
|
||||||
" This will create a 3D model from a 2D PNG image. Photos must",
|
|
||||||
" be blurred to be usable. Values from 0=off to 50=high are suggested.",
|
|
||||||
" Higher values incur more processing time.",
|
|
||||||
" </p>",
|
|
||||||
" <div class='f-row t-right'><table>",
|
|
||||||
" <tr><th>blur value</th><td><input id='png-blur' value='0' size='3'></td>",
|
|
||||||
" <th> invert image</th><td><input id='png-inv' type='checkbox'></td></tr>",
|
|
||||||
" <tr><th>base size</th><td><input id='png-base' value='0' size='3'></td>",
|
|
||||||
" <th> invert alpha</th><td><input id='alpha-inv' type='checkbox'></td></tr>",
|
|
||||||
" <tr><th>border size</th><td><input id='png-border' value='0' size='3'></td>",
|
|
||||||
" <th></th><td></td></tr>",
|
|
||||||
" </table></div>",
|
|
||||||
"</div>"
|
|
||||||
]};
|
|
||||||
api.uc.confirm(undefined, {convert:true, cancel:false}, undefined, opt).then((ok) => {
|
|
||||||
if (ok) {
|
|
||||||
loadImage(image, {
|
|
||||||
file: name,
|
|
||||||
blur: parseInt($('png-blur').value) || 0,
|
|
||||||
base: parseInt($('png-base').value) || 0,
|
|
||||||
border: parseInt($('png-border').value) || 0,
|
|
||||||
inv_image: $('png-inv').checked,
|
|
||||||
inv_alpha: $('alpha-inv').checked
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadImage(image, opt = {}) {
|
|
||||||
const info = Object.assign({settings: settings.get(), png:image}, opt);
|
|
||||||
api.client.image2mesh(info, progress => {
|
|
||||||
api.show.progress(progress, "converting");
|
|
||||||
}, vertices => {
|
|
||||||
api.show.progress(0);
|
|
||||||
const widget = newWidget().loadVertices(vertices);
|
|
||||||
widget.meta.file = opt.file;
|
|
||||||
platform.add(widget);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/** ******************************************************************
|
|
||||||
* Selection Functions
|
|
||||||
******************************************************************* */
|
|
||||||
|
|
||||||
function groupMerge() {
|
|
||||||
Widget.Groups.merge(selection.widgets(true));
|
|
||||||
}
|
|
||||||
|
|
||||||
function groupSplit() {
|
|
||||||
Widget.Groups.split(selection.widgets(false));
|
|
||||||
}
|
|
||||||
|
|
||||||
/** ******************************************************************
|
|
||||||
* Settings Functions
|
|
||||||
******************************************************************* */
|
|
||||||
|
|
||||||
// convert any image type to png
|
|
||||||
function loadImageConvert(res, name) {
|
|
||||||
let url = URL.createObjectURL(new Blob([res]));
|
|
||||||
|
|
||||||
$('mod-any').innerHTML = `<img id="xsrc" src="${url}"><canvas id="xdst"></canvas>`;
|
|
||||||
|
|
||||||
let img = $('xsrc');
|
|
||||||
let can = $('xdst');
|
|
||||||
|
|
||||||
img.onload = () => {
|
|
||||||
can.width = img.width;
|
|
||||||
can.height = img.height;
|
|
||||||
let ctx = can.getContext('2d');
|
|
||||||
ctx.drawImage(img, 0, 0);
|
|
||||||
fetch(can.toDataURL()).then(r => r.arrayBuffer()).then(data => {
|
|
||||||
loadImageDialog(data, name);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadFile(ev) {
|
function loadFile(ev) {
|
||||||
// use modern Filesystem api when available
|
// use modern Filesystem api when available
|
||||||
if (false && window.showOpenFilePicker) {
|
if (false && window.showOpenFilePicker) {
|
||||||
|
|
@ -517,92 +155,6 @@ function loadFile(ev) {
|
||||||
api.ui.load.click();
|
api.ui.load.click();
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveWorkspace(quiet) {
|
|
||||||
api.conf.save();
|
|
||||||
const newWidgets = [];
|
|
||||||
const oldWidgets = js2o(SDB.getItem('ws-widgets'), []);
|
|
||||||
api.widgets.each(function(widget) {
|
|
||||||
if (widget.synth) return;
|
|
||||||
newWidgets.push(widget.id);
|
|
||||||
oldWidgets.remove(widget.id);
|
|
||||||
widget.saveState();
|
|
||||||
let ann = api.widgets.annotate(widget.id);
|
|
||||||
ann.file = widget.meta.file;
|
|
||||||
ann.url = widget.meta.url;
|
|
||||||
});
|
|
||||||
SDB.setItem('ws-widgets', o2js(newWidgets));
|
|
||||||
oldWidgets.forEach(wid => {
|
|
||||||
Widget.deleteFromState(wid);
|
|
||||||
});
|
|
||||||
// eliminate dangling saved widgets
|
|
||||||
FILES.deleteFilter(key => newWidgets.indexOf(key.substring(8)) < 0, "ws-save-", "ws-savf");
|
|
||||||
if (!quiet) {
|
|
||||||
api.show.alert("workspace saved", 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function restoreWorkspace(ondone, skip_widget_load) {
|
|
||||||
let newset = api.conf.restore(false),
|
|
||||||
camera = newset.controller.view,
|
|
||||||
toload = ls2o('ws-widgets',[]),
|
|
||||||
loaded = 0,
|
|
||||||
position = true;
|
|
||||||
|
|
||||||
api.conf.update_fields();
|
|
||||||
platform.update_size();
|
|
||||||
|
|
||||||
SPACE.view.reset();
|
|
||||||
if (camera) {
|
|
||||||
SPACE.view.load(camera);
|
|
||||||
} else {
|
|
||||||
SPACE.view.home();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (skip_widget_load) {
|
|
||||||
if (ondone) {
|
|
||||||
ondone();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove any widgets from platform
|
|
||||||
api.widgets.each(function(widget) {
|
|
||||||
platform.delete(widget);
|
|
||||||
});
|
|
||||||
|
|
||||||
// load any widget by name that was saved to the workspace
|
|
||||||
toload.forEach(function(widgetid) {
|
|
||||||
Widget.loadFromState(widgetid, function(widget) {
|
|
||||||
if (widget) {
|
|
||||||
platform.add(widget, 0, position, true);
|
|
||||||
let ann = api.widgets.annotate(widgetid);
|
|
||||||
widget.meta.file = ann.file;
|
|
||||||
widget.meta.url = ann.url;
|
|
||||||
}
|
|
||||||
if (++loaded === toload.length) {
|
|
||||||
platform.deselect();
|
|
||||||
if (ondone) {
|
|
||||||
ondone();
|
|
||||||
setTimeout(() => {
|
|
||||||
platform.update_bounds();
|
|
||||||
SPACE.update();
|
|
||||||
}, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, position);
|
|
||||||
});
|
|
||||||
|
|
||||||
return toload.length > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
function clearWorkspace() {
|
|
||||||
// free up worker cache/mem
|
|
||||||
api.client.clear();
|
|
||||||
platform.select_all();
|
|
||||||
platform.delete(selection.meshes());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function editSettings(e) {
|
function editSettings(e) {
|
||||||
let current = settings.get(),
|
let current = settings.get(),
|
||||||
mode = getMode(),
|
mode = getMode(),
|
||||||
|
|
@ -657,7 +209,7 @@ function deleteSettings(e) {
|
||||||
|
|
||||||
function updateProcessList() {
|
function updateProcessList() {
|
||||||
let current = settings.get();
|
let current = settings.get();
|
||||||
let list = [], s = current, sp = s.sproc[getMode()] || {}, table = api.ui.settingsList;
|
let list = [], s = current, sp = s.sproc[settings.mode()] || {}, table = api.ui.settingsList;
|
||||||
table.innerHTML = '';
|
table.innerHTML = '';
|
||||||
for (let k in sp) {
|
for (let k in sp) {
|
||||||
if (sp.hasOwnProperty(k)) list.push(k);
|
if (sp.hasOwnProperty(k)) list.push(k);
|
||||||
|
|
@ -706,150 +258,6 @@ function updateProcessList() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function showHelp() {
|
|
||||||
showHelpFile(`local`,() => {});
|
|
||||||
}
|
|
||||||
|
|
||||||
function showHelpFile(local,then) {
|
|
||||||
if (!local) {
|
|
||||||
WIN.open("//docs.grid.space/", "_help");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const LANG = api.language.current;
|
|
||||||
$('kiri-version').innerHTML = `${LANG.version} ${version}`;
|
|
||||||
modal.show('help');
|
|
||||||
api.event.emit('help.show', local);
|
|
||||||
}
|
|
||||||
|
|
||||||
function showLocal() {
|
|
||||||
// deprecated
|
|
||||||
}
|
|
||||||
|
|
||||||
function setViewMode(mode) {
|
|
||||||
const isCAM = settings.mode() === 'CAM';
|
|
||||||
viewMode = mode;
|
|
||||||
platform.deselect();
|
|
||||||
selection.update_info();
|
|
||||||
// clear any bounds forced by, for example, WireEDM previews
|
|
||||||
api.platform.set_bounds();
|
|
||||||
// disable clear in non-arrange modes
|
|
||||||
['view-arrange','act-slice','act-preview','act-animate'].forEach(el => {
|
|
||||||
$(el).classList.remove('selected')
|
|
||||||
});
|
|
||||||
$('render-tools').classList.add('hide');
|
|
||||||
switch (mode) {
|
|
||||||
case VIEWS.ARRANGE:
|
|
||||||
$('view-arrange').classList.add('selected');
|
|
||||||
$('render-tools').classList.remove('hide');
|
|
||||||
api.function.clear_progress();
|
|
||||||
api.client.clear();
|
|
||||||
STACKS.clear();
|
|
||||||
hideSlider();
|
|
||||||
updateSpeeds();
|
|
||||||
setVisibleLayer();
|
|
||||||
setWidgetVisibility(true);
|
|
||||||
api.widgets.setOpacity(1);
|
|
||||||
api.view.edges(api.local.getBoolean('model.edges'));
|
|
||||||
break;
|
|
||||||
case VIEWS.SLICE:
|
|
||||||
$('act-slice').classList.add('selected');
|
|
||||||
updateSpeeds();
|
|
||||||
updateSliderMax();
|
|
||||||
setWidgetVisibility(true);
|
|
||||||
!isCAM && api.view.edges(false);
|
|
||||||
break;
|
|
||||||
case VIEWS.PREVIEW:
|
|
||||||
$('act-preview').classList.add('selected');
|
|
||||||
setWidgetVisibility(true);
|
|
||||||
!isCAM && api.view.edges(false);
|
|
||||||
break;
|
|
||||||
case VIEWS.ANIMATE:
|
|
||||||
$('act-animate').classList.add('selected');
|
|
||||||
!isCAM && api.view.edges(false);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
console.log("invalid view mode: "+mode);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
api.event.emit('view.set', mode);
|
|
||||||
DOC.activeElement.blur();
|
|
||||||
}
|
|
||||||
|
|
||||||
function getMode() {
|
|
||||||
return settings.mode();
|
|
||||||
}
|
|
||||||
|
|
||||||
function getModeLower() {
|
|
||||||
return getMode().toLowerCase();
|
|
||||||
}
|
|
||||||
|
|
||||||
function switchMode(mode) {
|
|
||||||
setMode(mode, null, platform.update_size);
|
|
||||||
}
|
|
||||||
|
|
||||||
function setMode(mode, lock, then) {
|
|
||||||
if (!MODES[mode]) {
|
|
||||||
console.log("invalid mode: "+mode);
|
|
||||||
mode = 'FDM';
|
|
||||||
}
|
|
||||||
const current = settings.get();
|
|
||||||
// change mode constants
|
|
||||||
current.mode = mode;
|
|
||||||
MODE = MODES[mode];
|
|
||||||
// gcode edit area for any non-SLA mode
|
|
||||||
api.uc.setVisible($('gcode-edit'), mode !== 'SLA');
|
|
||||||
// highlight selected mode menu item
|
|
||||||
["FDM","CAM","SLA","LASER","DRAG","WJET","WEDM"].forEach(sm => {
|
|
||||||
const cl = $(`mode-${sm.toLowerCase()}`).classList;
|
|
||||||
if (sm === mode) {
|
|
||||||
cl.add('selected');
|
|
||||||
} else {
|
|
||||||
cl.remove('selected');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// restore cached device profile for this mode
|
|
||||||
if (current.cdev[mode]) {
|
|
||||||
current.device = clone(current.cdev[mode]);
|
|
||||||
api.event.emit('device.select', api.device.get());
|
|
||||||
}
|
|
||||||
// hide/show
|
|
||||||
api.uc.setVisible($('set-tools'), mode === 'CAM');
|
|
||||||
// updates right-hand menu by enabling/disabling fields
|
|
||||||
setViewMode(VIEWS.ARRANGE);
|
|
||||||
api.uc.setMode(MODE);
|
|
||||||
// sanitize and persist settings
|
|
||||||
api.conf.load();
|
|
||||||
api.conf.save();
|
|
||||||
// other housekeeping
|
|
||||||
triggerSettingsEvent();
|
|
||||||
platform.update_selected();
|
|
||||||
selection.update_bounds(api.widgets.all());
|
|
||||||
api.conf.update_fields();
|
|
||||||
// because device dialog, if showing, needs to be updated
|
|
||||||
if (modal.visible()) {
|
|
||||||
api.show.devices();
|
|
||||||
}
|
|
||||||
api.space.restore(null, true);
|
|
||||||
api.event.emit("mode.set", mode);
|
|
||||||
if (then) {
|
|
||||||
then();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function currentProcessName() {
|
|
||||||
return settings.get().cproc[getMode()];
|
|
||||||
}
|
|
||||||
|
|
||||||
function currentProcessCode() {
|
|
||||||
return settings.get().sproc[getMode()][currentProcessName()];
|
|
||||||
}
|
|
||||||
|
|
||||||
function setControlsVisible(show) {
|
|
||||||
// TODO fix
|
|
||||||
// $('mid-left').style.display = show ? 'flex' : 'none';
|
|
||||||
// $('mid-right').style.display = show ? 'flex' : 'none';
|
|
||||||
}
|
|
||||||
|
|
||||||
function downloadBlob(data, filename) {
|
function downloadBlob(data, filename) {
|
||||||
let url = WIN.URL.createObjectURL(new Blob([data], {type: "octet/stream"}));
|
let url = WIN.URL.createObjectURL(new Blob([data], {type: "octet/stream"}));
|
||||||
$('mod-any').innerHTML = `<a id="_dexport_" href="${url}" download="${filename}">x</a>`;
|
$('mod-any').innerHTML = `<a id="_dexport_" href="${url}" download="${filename}">x</a>`;
|
||||||
|
|
@ -857,3 +265,6 @@ function downloadBlob(data, filename) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export { FILES as catalog, LOCAL, SETUP, SECURE };
|
export { FILES as catalog, LOCAL, SETUP, SECURE };
|
||||||
|
|
||||||
|
// Import frame.js last to avoid circular dependency issues
|
||||||
|
// import './frame.js';
|
||||||
|
|
|
||||||
111
src/kiri/ui/mode.js
Normal file
111
src/kiri/ui/mode.js
Normal file
|
|
@ -0,0 +1,111 @@
|
||||||
|
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||||
|
|
||||||
|
import { $ } from '../../moto/webui.js';
|
||||||
|
import { api } from './api.js';
|
||||||
|
import { consts } from '../core/consts.js';
|
||||||
|
import { modal } from './modal.js';
|
||||||
|
import { platform } from './platform.js';
|
||||||
|
import { selection } from './select.js';
|
||||||
|
import { settings } from './config/manager.js';
|
||||||
|
|
||||||
|
const { MODES, VIEWS } = consts;
|
||||||
|
const clone = Object.clone;
|
||||||
|
|
||||||
|
let MODE = MODES.FDM;
|
||||||
|
|
||||||
|
function getMode() {
|
||||||
|
return settings.mode();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getModeLower() {
|
||||||
|
return getMode().toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
function switchMode(mode) {
|
||||||
|
setMode(mode, null, platform.update_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setMode(mode, lock, then) {
|
||||||
|
if (!MODES[mode]) {
|
||||||
|
console.log("invalid mode: "+mode);
|
||||||
|
mode = 'FDM';
|
||||||
|
}
|
||||||
|
const current = settings.get();
|
||||||
|
// change mode constants
|
||||||
|
current.mode = mode;
|
||||||
|
MODE = MODES[mode];
|
||||||
|
// gcode edit area for any non-SLA mode
|
||||||
|
api.uc.setVisible($('gcode-edit'), mode !== 'SLA');
|
||||||
|
// highlight selected mode menu item
|
||||||
|
["FDM","CAM","SLA","LASER","DRAG","WJET","WEDM"].forEach(sm => {
|
||||||
|
const cl = $(`mode-${sm.toLowerCase()}`).classList;
|
||||||
|
if (sm === mode) {
|
||||||
|
cl.add('selected');
|
||||||
|
} else {
|
||||||
|
cl.remove('selected');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// restore cached device profile for this mode
|
||||||
|
if (current.cdev[mode]) {
|
||||||
|
current.device = clone(current.cdev[mode]);
|
||||||
|
api.event.emit('device.select', api.device.get());
|
||||||
|
}
|
||||||
|
// hide/show
|
||||||
|
api.uc.setVisible($('set-tools'), mode === 'CAM');
|
||||||
|
// updates right-hand menu by enabling/disabling fields
|
||||||
|
api.view.set(VIEWS.ARRANGE);
|
||||||
|
api.uc.setMode(MODE);
|
||||||
|
// sanitize and persist settings
|
||||||
|
api.conf.load();
|
||||||
|
api.conf.save();
|
||||||
|
// trigger settings event
|
||||||
|
api.event.emit('settings', settings.get());
|
||||||
|
// other housekeeping
|
||||||
|
platform.update_selected();
|
||||||
|
selection.update_bounds(api.widgets.all());
|
||||||
|
api.conf.update_fields();
|
||||||
|
// because device dialog, if showing, needs to be updated
|
||||||
|
if (modal.visible()) {
|
||||||
|
api.show.devices();
|
||||||
|
}
|
||||||
|
api.space.restore(null, true);
|
||||||
|
api.event.emit("mode.set", mode);
|
||||||
|
if (then) {
|
||||||
|
then();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function currentProcessName() {
|
||||||
|
return settings.get().cproc[getMode()];
|
||||||
|
}
|
||||||
|
|
||||||
|
function currentProcessCode() {
|
||||||
|
return settings.get().sproc[getMode()][currentProcessName()];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const mode = {
|
||||||
|
get_id() { return MODE },
|
||||||
|
get_lower: getModeLower,
|
||||||
|
get: getMode,
|
||||||
|
set: setMode,
|
||||||
|
switch: switchMode,
|
||||||
|
set_expert() {}, // noop
|
||||||
|
is_fdm() { return MODE === MODES.FDM },
|
||||||
|
is_cam() { return MODE === MODES.CAM },
|
||||||
|
is_sla() { return MODE === MODES.SLA },
|
||||||
|
is_drag() { return MODE === MODES.DRAG },
|
||||||
|
is_wedm() { return MODE === MODES.WEDM },
|
||||||
|
is_wjet() { return MODE === MODES.WJET },
|
||||||
|
is_laser() { return MODE === MODES.LASER },
|
||||||
|
is_2d() { return false ||
|
||||||
|
mode.is_drag() ||
|
||||||
|
mode.is_wedm() ||
|
||||||
|
mode.is_wjet() ||
|
||||||
|
mode.is_laser()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const process = {
|
||||||
|
code: currentProcessCode,
|
||||||
|
get: currentProcessName
|
||||||
|
};
|
||||||
77
src/kiri/ui/view-state.js
Normal file
77
src/kiri/ui/view-state.js
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||||
|
|
||||||
|
import { $ } from '../../moto/webui.js';
|
||||||
|
import { api } from './api.js';
|
||||||
|
import { consts } from '../core/consts.js';
|
||||||
|
import { platform } from './platform.js';
|
||||||
|
import { selection } from './select.js';
|
||||||
|
import { settings } from './config/manager.js';
|
||||||
|
import STACKS from './stacks.js';
|
||||||
|
|
||||||
|
const { VIEWS } = consts;
|
||||||
|
const DOC = self.document;
|
||||||
|
|
||||||
|
let viewMode = VIEWS.ARRANGE;
|
||||||
|
|
||||||
|
function setViewMode(mode) {
|
||||||
|
const isCAM = settings.mode() === 'CAM';
|
||||||
|
viewMode = mode;
|
||||||
|
platform.deselect();
|
||||||
|
selection.update_info();
|
||||||
|
// clear any bounds forced by, for example, WireEDM previews
|
||||||
|
api.platform.set_bounds();
|
||||||
|
// disable clear in non-arrange modes
|
||||||
|
['view-arrange','act-slice','act-preview','act-animate'].forEach(el => {
|
||||||
|
$(el).classList.remove('selected')
|
||||||
|
});
|
||||||
|
$('render-tools').classList.add('hide');
|
||||||
|
switch (mode) {
|
||||||
|
case VIEWS.ARRANGE:
|
||||||
|
$('view-arrange').classList.add('selected');
|
||||||
|
$('render-tools').classList.remove('hide');
|
||||||
|
api.function.clear_progress();
|
||||||
|
api.client.clear();
|
||||||
|
STACKS.clear();
|
||||||
|
api.visuals.hide_slider();
|
||||||
|
api.visuals.update_speeds();
|
||||||
|
api.visuals.set_visible_layer();
|
||||||
|
api.visuals.set_widget_visibility(true);
|
||||||
|
api.widgets.setOpacity(1);
|
||||||
|
api.view.edges(api.local.getBoolean('model.edges'));
|
||||||
|
break;
|
||||||
|
case VIEWS.SLICE:
|
||||||
|
$('act-slice').classList.add('selected');
|
||||||
|
api.visuals.update_speeds();
|
||||||
|
api.visuals.update_slider_max();
|
||||||
|
api.visuals.set_widget_visibility(true);
|
||||||
|
!isCAM && api.view.edges(false);
|
||||||
|
break;
|
||||||
|
case VIEWS.PREVIEW:
|
||||||
|
$('act-preview').classList.add('selected');
|
||||||
|
api.visuals.set_widget_visibility(true);
|
||||||
|
!isCAM && api.view.edges(false);
|
||||||
|
break;
|
||||||
|
case VIEWS.ANIMATE:
|
||||||
|
$('act-animate').classList.add('selected');
|
||||||
|
!isCAM && api.view.edges(false);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.log("invalid view mode: "+mode);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
api.event.emit('view.set', mode);
|
||||||
|
DOC.activeElement.blur();
|
||||||
|
}
|
||||||
|
|
||||||
|
export const view = {
|
||||||
|
get() { return viewMode },
|
||||||
|
set() { setViewMode(...arguments) },
|
||||||
|
set_arrange() { setViewMode(VIEWS.ARRANGE) },
|
||||||
|
set_slice() { setViewMode(VIEWS.SLICE) },
|
||||||
|
set_preview() { setViewMode(VIEWS.PREVIEW) },
|
||||||
|
set_animate() { setViewMode(VIEWS.ANIMATE) },
|
||||||
|
is_arrange() { return viewMode === VIEWS.ARRANGE },
|
||||||
|
is_slice() { return viewMode === VIEWS.SLICE },
|
||||||
|
is_preview() { return viewMode === VIEWS.PREVIEW },
|
||||||
|
is_animate() { return viewMode === VIEWS.ANIMATE }
|
||||||
|
};
|
||||||
181
src/kiri/ui/visuals.js
Normal file
181
src/kiri/ui/visuals.js
Normal file
|
|
@ -0,0 +1,181 @@
|
||||||
|
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||||
|
|
||||||
|
import { api } from './api.js';
|
||||||
|
import { consts } from '../core/consts.js';
|
||||||
|
import { settings } from './config/manager.js';
|
||||||
|
import { space as SPACE } from '../../moto/space.js';
|
||||||
|
import STACKS from './stacks.js';
|
||||||
|
|
||||||
|
const { COLOR, VIEWS } = consts;
|
||||||
|
|
||||||
|
// Note: viewMode is imported dynamically via api.view.get()
|
||||||
|
function bound(v,min,max) {
|
||||||
|
return Math.max(min,Math.min(max,v));
|
||||||
|
}
|
||||||
|
|
||||||
|
function unitScale() {
|
||||||
|
return api.mode.is_cam() && settings.ctrl().units === 'in' ? 25.4 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateSpeeds(maxSpeed, minSpeed) {
|
||||||
|
const { ui } = api;
|
||||||
|
const viewMode = api.view.get();
|
||||||
|
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)));
|
||||||
|
}
|
||||||
|
api.client.colors(colors, maxSpeed, speedColors => {
|
||||||
|
const list = [];
|
||||||
|
Object.keys(speedColors).map(v => parseInt(v)).sort((a, b) => b - a).forEach(speed => {
|
||||||
|
const color = speedColors[speed];
|
||||||
|
const hex = color.toString(16).padStart(6, 0);
|
||||||
|
const r = (color >> 16) & 0xff;
|
||||||
|
const g = (color >> 8) & 0xff;
|
||||||
|
const b = (color >> 0) & 0xff;
|
||||||
|
const style = `background-color:#${hex}`;
|
||||||
|
list.push(`<label style="${style}">${speed}</label>`);
|
||||||
|
});
|
||||||
|
ui.speedbar.innerHTML = list.join('');
|
||||||
|
});
|
||||||
|
api.event.emit('preview.speeds', {
|
||||||
|
min: minSpeed,
|
||||||
|
max: maxSpeed
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateSlider() {
|
||||||
|
const { lo, hi, max } = api.slider.getRange();
|
||||||
|
if (max > 0) {
|
||||||
|
api.slider.updatePosition(lo / max, hi / max);
|
||||||
|
}
|
||||||
|
api.conf.update_fields_from_range();
|
||||||
|
}
|
||||||
|
|
||||||
|
function setVisibleLayer(h, l) {
|
||||||
|
const { lo, hi, max } = api.slider.getRange();
|
||||||
|
h = h >= 0 ? h : hi;
|
||||||
|
l = l >= 0 ? l : lo;
|
||||||
|
const newHi = bound(h, 0, max);
|
||||||
|
const newLo = bound(l, 0, newHi);
|
||||||
|
api.slider.setRange(newLo, newHi); // Notify callbacks (STACKS, scene updates)
|
||||||
|
api.slider.showLabels();
|
||||||
|
showSlices();
|
||||||
|
}
|
||||||
|
|
||||||
|
function setWireframe(bool, color, opacity) {
|
||||||
|
api.widgets.each(function(w) { w.setWireframe(bool, color, opacity) });
|
||||||
|
SPACE.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
function setEdges(bool) {
|
||||||
|
if (bool && bool.toggle) {
|
||||||
|
api.local.toggle('model.edges');
|
||||||
|
} else {
|
||||||
|
api.local.set('model.edges', bool);
|
||||||
|
}
|
||||||
|
bool = api.local.getBoolean('model.edges');
|
||||||
|
api.widgets.each(w => w.setEdges(bool));
|
||||||
|
SPACE.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateSliderMax(set) {
|
||||||
|
let max = STACKS.getRange().tallest - 1;
|
||||||
|
api.slider.setMax(max);
|
||||||
|
const { hi } = api.slider.getRange();
|
||||||
|
if (set || max < hi) {
|
||||||
|
api.slider.setRange(max, max);
|
||||||
|
api.slider.showLabels();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideSlices() {
|
||||||
|
STACKS.clear();
|
||||||
|
api.widgets.setOpacity(COLOR.model_opacity);
|
||||||
|
api.widgets.each(function(widget) {
|
||||||
|
widget.setWireframe(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function setWidgetVisibility(bool) {
|
||||||
|
api.widgets.each(w => {
|
||||||
|
if (bool) {
|
||||||
|
w.show();
|
||||||
|
} else {
|
||||||
|
w.hide();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* hide or show slice-layers and their sub-elements
|
||||||
|
*
|
||||||
|
* @param {number} [layer]
|
||||||
|
*/
|
||||||
|
function showSlices(layer) {
|
||||||
|
const viewMode = api.view.get();
|
||||||
|
if (viewMode === VIEWS.ARRANGE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
showSlider();
|
||||||
|
|
||||||
|
const { lo, hi, max } = api.slider.getRange();
|
||||||
|
|
||||||
|
if (typeof(layer) === 'string' || typeof(layer) === 'number') {
|
||||||
|
layer = parseInt(layer);
|
||||||
|
} else {
|
||||||
|
layer = hi;
|
||||||
|
}
|
||||||
|
|
||||||
|
layer = bound(layer, 0, max);
|
||||||
|
const newLo = layer < lo ? layer : lo;
|
||||||
|
api.slider.setRange(newLo, layer);
|
||||||
|
api.slider.showLabels();
|
||||||
|
|
||||||
|
updateSlider();
|
||||||
|
// STACKS.setRange is called by slider callback
|
||||||
|
|
||||||
|
SPACE.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
function showSlider() {
|
||||||
|
api.slider.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideSlider() {
|
||||||
|
api.slider.hide();
|
||||||
|
api.ui.speeds.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateStackLabelState() {
|
||||||
|
const settings = api.conf.get();
|
||||||
|
// match label checkboxes to preference
|
||||||
|
for (let label of api.stacks.getLabels()) {
|
||||||
|
let check = `${settings.mode}-${api.view.get()}-${label}`;
|
||||||
|
api.stacks.setVisible(label, settings.labels[check] !== false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const visuals = {
|
||||||
|
wireframe: setWireframe,
|
||||||
|
edges: setEdges,
|
||||||
|
unit_scale: unitScale,
|
||||||
|
update_speeds: updateSpeeds,
|
||||||
|
update_slider: updateSlider,
|
||||||
|
update_slider_max: updateSliderMax,
|
||||||
|
update_stack_labels: updateStackLabelState,
|
||||||
|
show_slices: showSlices,
|
||||||
|
hide_slices: hideSlices,
|
||||||
|
show_slider: showSlider,
|
||||||
|
hide_slider: hideSlider,
|
||||||
|
set_visible_layer: setVisibleLayer,
|
||||||
|
set_widget_visibility: setWidgetVisibility
|
||||||
|
};
|
||||||
177
src/kiri/ui/workspace.js
Normal file
177
src/kiri/ui/workspace.js
Normal file
|
|
@ -0,0 +1,177 @@
|
||||||
|
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||||
|
|
||||||
|
import { THREE } from '../../ext/three.js';
|
||||||
|
import { api } from './api.js';
|
||||||
|
import { local as SDB } from '../../data/local.js';
|
||||||
|
import { openFiles } from './files.js';
|
||||||
|
import { platform } from './platform.js';
|
||||||
|
import { selection } from './select.js';
|
||||||
|
import { settings } from './config/manager.js';
|
||||||
|
import { space as SPACE } from '../../moto/space.js';
|
||||||
|
import { utils } from '../core/utils.js';
|
||||||
|
import { Widget } from '../core/widget.js';
|
||||||
|
import { Index } from '../../data/index.js';
|
||||||
|
|
||||||
|
const { o2js, js2o, ls2o, parseOpt } = utils;
|
||||||
|
const LOC = self.location;
|
||||||
|
const SETUP = parseOpt(LOC.search.substring(1));
|
||||||
|
const FILES = openFiles(new Index(SETUP.d ? SETUP.d[0] : 'kiri'));
|
||||||
|
|
||||||
|
let autoSaveTimer = null;
|
||||||
|
|
||||||
|
function reload() {
|
||||||
|
api.event.emit('reload');
|
||||||
|
do_reload(100);
|
||||||
|
}
|
||||||
|
|
||||||
|
function do_reload(time) {
|
||||||
|
// allow time for async saves to complete and busy to to to zero
|
||||||
|
setTimeout(() => {
|
||||||
|
if (api.busy.val() === 0) {
|
||||||
|
LOC.reload();
|
||||||
|
} else {
|
||||||
|
console.log(`reload deferred on busy=${api.busy.val()}`);
|
||||||
|
do_reload(250);
|
||||||
|
}
|
||||||
|
}, time || 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
function auto_save() {
|
||||||
|
if (!settings.ctrl().autoSave) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
clearTimeout(autoSaveTimer);
|
||||||
|
autoSaveTimer = setTimeout(() => {
|
||||||
|
api.space.save(true);
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setFocus(sel, point) {
|
||||||
|
if (point) {
|
||||||
|
SPACE.platform.setCenter(point.x, point.z, point.y);
|
||||||
|
SPACE.view.setFocus(new THREE.Vector3(point.x, point.y, point.z));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (sel === undefined) {
|
||||||
|
sel = api.widgets.all();
|
||||||
|
} else if (!Array.isArray) {
|
||||||
|
sel = [ sel ];
|
||||||
|
} else if (sel.length === 0) {
|
||||||
|
sel = api.widgets.all();
|
||||||
|
}
|
||||||
|
let pos = { x:0, y:0, z:0 };
|
||||||
|
for (let widget of sel) {
|
||||||
|
pos.x += widget.track.pos.x;
|
||||||
|
pos.y += widget.track.pos.y;
|
||||||
|
pos.z += widget.track.pos.z;
|
||||||
|
}
|
||||||
|
if (sel.length) {
|
||||||
|
pos.x /= sel.length;
|
||||||
|
pos.y /= sel.length;
|
||||||
|
pos.z /= sel.length;
|
||||||
|
}
|
||||||
|
let cam_index = api.conf.get().process.camStockIndexed || false;
|
||||||
|
let focus_z = cam_index ? 0 : platform.top_z() / 2;
|
||||||
|
SPACE.platform.setCenter(pos.x, -pos.y, focus_z);
|
||||||
|
SPACE.view.setFocus(new THREE.Vector3(pos.x, focus_z, -pos.y));
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveWorkspace(quiet) {
|
||||||
|
api.conf.save();
|
||||||
|
const newWidgets = [];
|
||||||
|
const oldWidgets = js2o(SDB.getItem('ws-widgets'), []);
|
||||||
|
api.widgets.each(function(widget) {
|
||||||
|
if (widget.synth) return;
|
||||||
|
newWidgets.push(widget.id);
|
||||||
|
oldWidgets.remove(widget.id);
|
||||||
|
widget.saveState();
|
||||||
|
let ann = api.widgets.annotate(widget.id);
|
||||||
|
ann.file = widget.meta.file;
|
||||||
|
ann.url = widget.meta.url;
|
||||||
|
});
|
||||||
|
SDB.setItem('ws-widgets', o2js(newWidgets));
|
||||||
|
oldWidgets.forEach(wid => {
|
||||||
|
Widget.deleteFromState(wid);
|
||||||
|
});
|
||||||
|
// eliminate dangling saved widgets
|
||||||
|
FILES.deleteFilter(key => newWidgets.indexOf(key.substring(8)) < 0, "ws-save-", "ws-savf");
|
||||||
|
if (!quiet) {
|
||||||
|
api.show.alert("workspace saved", 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function restoreWorkspace(ondone, skip_widget_load) {
|
||||||
|
let newset = api.conf.restore(false),
|
||||||
|
camera = newset.controller.view,
|
||||||
|
toload = ls2o('ws-widgets',[]),
|
||||||
|
loaded = 0,
|
||||||
|
position = true;
|
||||||
|
|
||||||
|
api.conf.update_fields();
|
||||||
|
platform.update_size();
|
||||||
|
|
||||||
|
SPACE.view.reset();
|
||||||
|
if (camera) {
|
||||||
|
SPACE.view.load(camera);
|
||||||
|
} else {
|
||||||
|
SPACE.view.home();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (skip_widget_load) {
|
||||||
|
if (ondone) {
|
||||||
|
ondone();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove any widgets from platform
|
||||||
|
api.widgets.each(function(widget) {
|
||||||
|
platform.delete(widget);
|
||||||
|
});
|
||||||
|
|
||||||
|
// load any widget by name that was saved to the workspace
|
||||||
|
toload.forEach(function(widgetid) {
|
||||||
|
Widget.loadFromState(widgetid, function(widget) {
|
||||||
|
if (widget) {
|
||||||
|
platform.add(widget, 0, position, true);
|
||||||
|
let ann = api.widgets.annotate(widgetid);
|
||||||
|
widget.meta.file = ann.file;
|
||||||
|
widget.meta.url = ann.url;
|
||||||
|
}
|
||||||
|
if (++loaded === toload.length) {
|
||||||
|
platform.deselect();
|
||||||
|
if (ondone) {
|
||||||
|
ondone();
|
||||||
|
setTimeout(() => {
|
||||||
|
platform.update_bounds();
|
||||||
|
SPACE.update();
|
||||||
|
}, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, position);
|
||||||
|
});
|
||||||
|
|
||||||
|
return toload.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearWorkspace() {
|
||||||
|
// free up worker cache/mem
|
||||||
|
api.client.clear();
|
||||||
|
platform.select_all();
|
||||||
|
platform.delete(selection.meshes());
|
||||||
|
}
|
||||||
|
|
||||||
|
function is_dark() {
|
||||||
|
return settings.ctrl().dark;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const workspace = {
|
||||||
|
reload,
|
||||||
|
auto_save,
|
||||||
|
restore: restoreWorkspace,
|
||||||
|
clear: clearWorkspace,
|
||||||
|
save: saveWorkspace,
|
||||||
|
set_focus: setFocus,
|
||||||
|
update: SPACE.update,
|
||||||
|
is_dark
|
||||||
|
};
|
||||||
Loading…
Reference in a new issue