/** Copyright Stewart Allen -- All Rights Reserved */ "use strict"; var gs_kiri_init = exports; (function() { if (!self.kiri) self.kiri = { }; if (self.kiri.init) return; let KIRI = self.kiri, MOTO = self.moto, CONF = KIRI.conf, WIN = self.window, DOC = self.document, LOC = self.location, API = KIRI.api, SDB = API.sdb, UI = API.ui, UC = API.uc, SEED = API.const.SEED, LANG = API.const.LANG, VIEWS = API.const.VIEWS, MODES = API.const.MODES, LOCAL = API.const.LOCAL, SETUP = API.const.SETUP, DEFMODE = SETUP.dm && SETUP.dm.length === 1 ? SETUP.dm[0] : 'FDM', STARTMODE = SETUP.sm && SETUP.sm.length === 1 ? SETUP.sm[0] : null, SPACE = KIRI.space, STATS = API.stats, DEG = Math.PI/180, ALL = [MODES.FDM, MODES.LASER, MODES.CAM, MODES.SLA], CAM = [MODES.CAM], SLA = [MODES.SLA], FDM = [MODES.FDM], FDM_SLA = [MODES.FDM,MODES.SLA], FDM_CAM = [MODES.CAM,MODES.FDM], FDM_LASER = [MODES.LASER,MODES.FDM], FDM_LASER_SLA = [MODES.LASER,MODES.FDM,MODES.SLA], CAM_LASER = [MODES.LASER,MODES.CAM], GCODE = [MODES.FDM, MODES.LASER, MODES.CAM], LASER = [MODES.LASER], CATALOG = API.catalog, platform = API.platform, selection = API.selection, deviceLock = false, js2o = API.js2o, o2js = API.o2js, selectedTool = null, editTools = null, maxTool = 0; // extend KIRI API with local functions API.show.devices = showDevices; API.device.set = selectDevice; function settings() { return API.conf.get(); } function checkSeed(then) { // skip sample object load in onshape (or any script postload) if (!SDB[SEED]) { SDB[SEED] = new Date().getTime(); if (!SETUP.s && API.feature.seed) { platform.load_stl("/obj/cube.stl", function(vert) { CATALOG.putFile("sample cube.stl", vert); platform.compute_max_z(); SPACE.view.home(); setTimeout(API.space.save,500); then(); API.help.show(); }); return true; } } return false; } function booleanSave() { let control = settings().controller; let isCompact = control.compact; let isDark = control.dark; control.expert = UI.expert.checked; control.showOrigin = UI.showOrigin.checked; control.autoLayout = UI.autoLayout.checked; control.freeLayout = UI.freeLayout.checked; control.alignTop = UI.alignTop.checked; control.reverseZoom = UI.reverseZoom.checked; control.compact = UI.compact.checked; control.dark = UI.dark.checked; SPACE.view.setZoom(control.reverseZoom, control.zoomSpeed); platform.layout(); platform.update_stock(); API.conf.save(); // if compact mode changed, reload UI if (isCompact !== control.compact) { UC.setDefaults(isCompact); LOC.reload(); } // if dark mode changed, reload UI if (isDark !== control.dark) { LOC.reload(); } UC.setExpert(control.expert); } function onLayerToggle() { API.conf.update(); API.show.slices(); } function onBooleanClick() { API.conf.update(); DOC.activeElement.blur(); } function inputHasFocus() { let active = DOC.activeElement; return active && (active.nodeName === "INPUT" || active.nodeName === "TEXTAREA"); } function inputTextOK() { return DOC.activeElement === UI.deviceName; } function textAreaHasFocus() { let active = DOC.activeElement; return active && active.nodeName === "TEXTAREA"; } function inputSize() { return parseInt(DOC.activeElement.size); } function cca(c) { return c.charCodeAt(0); } function keyUpHandler(evt) { if (API.feature.on_key) { if (API.feature.on_key({up:evt})) return; } switch (evt.keyCode) { // escape case 27: // blur text input focus DOC.activeElement.blur(); // dismiss modals API.modal.hide(); // deselect widgets platform.deselect(); // hide all dialogs API.dialog.hide(); // cancel slicing if (KIRI.work.isSlicing()) KIRI.work.restart(); // kill any poppers in compact mode UC.hidePoppers(); // hide layers menu UI.layers.style.display = 'none'; break; } return false; } function keyDownHandler(evt) { if (API.modal.visible()) { return false; } if (API.feature.on_key) { if (API.feature.on_key({down:evt})) return; } let move = evt.altKey ? 5 : 0, deg = move ? 0 : -Math.PI / (evt.shiftKey ? 36 : 2); switch (evt.keyCode) { case 8: // apple: delete/backspace case 46: // others: delete if (inputHasFocus()) return false; platform.delete(API.selection.meshes()); evt.preventDefault(); break; case 37: // left arrow if (inputHasFocus()) return false; if (deg) API.selection.rotate(0, 0, -deg); if (move > 0) moveSelection(-move, 0, 0); evt.preventDefault(); break; case 39: // right arrow if (inputHasFocus()) return false; if (deg) API.selection.rotate(0, 0, deg); if (move > 0) moveSelection(move, 0, 0); evt.preventDefault(); break; case 38: // up arrow if (inputHasFocus()) return false; if (evt.metaKey) return API.show.layer(API.var.layer_at+1); if (deg) API.selection.rotate(deg, 0, 0); if (move > 0) moveSelection(0, move, 0); evt.preventDefault(); break; case 40: // down arrow if (inputHasFocus()) return false; if (evt.metaKey) return API.show.layer(API.var.layer_at-1); if (deg) API.selection.rotate(-deg, 0, 0); if (move > 0) moveSelection(0, -move, 0); evt.preventDefault(); break; case 65: // 'a' for select all if (evt.metaKey) { if (inputHasFocus()) return false; evt.preventDefault(); platform.deselect(); platform.select_all(); } break; case 83: // 's' for save workspace if (evt.ctrlKey) { evt.preventDefault(); API.conf.save(); log("settings saved"); } else if (evt.metaKey) { evt.preventDefault(); API.space.save(); } break; case 76: // 'l' for restore workspace if (evt.metaKey) { evt.preventDefault(); API.space.restore(); } break; } } function keyHandler(evt) { let handled = true, current = settings(), style, sel, i, m, bb, ncc = evt.charCode - 48; if (API.modal.visible() || inputHasFocus()) { return false; } if (API.feature.on_key) { if (API.feature.on_key({key:evt})) return; } switch (evt.charCode) { case cca('`'): API.show.slices(0); break; case cca('0'): API.show.slices(API.var.layer_max); break; case cca('1'): // toggle control left if (evt.ctrlKey) { style = UI.ctrlLeft.style; style.display = style.display === 'none' ? 'block' : 'none'; } else { API.show.slices(API.var.layer_max/10); } break; case cca('2'): // toggle control right if (evt.ctrlKey) { style = UI.ctrlRight.style; style.display = style.display === 'none' ? 'block' : 'none'; } else { API.show.slices(API.var.layer_max*2/10); } break; case cca('3'): if (evt.ctrlKey) { style = !SPACE.platform.isHidden(); SPACE.platform.setHidden(style); SPACE.platform.showGrid(!style); SPACE.update(); } else { API.show.slices(API.var.layer_max*3/10); } break; case cca('4'): API.show.slices(API.var.layer_max*4/10); break; case cca('5'): API.show.slices(API.var.layer_max*5/10); break; case cca('6'): API.show.slices(API.var.layer_max*6/10); break; case cca('7'): API.show.slices(API.var.layer_max*7/10); break; case cca('8'): API.show.slices(API.var.layer_max*8/10); break; case cca('9'): API.show.slices(API.var.layer_max*9/10); break; case cca('?'): API.help.show(); break; case cca('Z'): // reset stored state if (confirm('clear all settings?')) { SDB.clear(); } break; case cca('C'): // refresh catalog CATALOG.refresh(); break; case cca('i'): // single settings edit let v = prompt('edit "'+current.process.processName+'"', JSON.stringify(current.process)); if (v) { try { current.process = JSON.parse(v); API.view.update_fields(); } catch (e) { console.log(e); API.show.alert("invalid settings format"); } } break; case cca('U'): // full settings url storeSettingsToServer(true); break; case cca('u'): // full settings url loadSettingsFromServer(prompt("settings id to load")); break; case cca('s'): // complete slice API.function.slice(); break; case cca('p'): // prepare print API.function.print(); break; case cca('P'): // position widget positionSelection(); break; case cca('R'): // position widget rotateInputSelection(); break; case cca('x'): // export print API.function.export(); break; case cca('e'): // devices showDevices(); break; case cca('o'): // tools showTools(); break; case cca('c'): // local devices API.show.local(); break; case cca('v'): // toggle single slice view mode UI.layerRange.checked = !UI.layerRange.checked; API.show.slices(); break; case cca('d'): // duplicate object sel = API.selection.meshes(); platform.deselect(); for (i=0; ix`; $('sexport').click(); } function settingsSave() { API.dialog.hide(); let mode = API.mode.get(), s = settings(), def = "default", cp = s.process, pl = s.sproc[mode], // pt = sf[mode.toLowerCase()].p, // process field mask name = WIN.prompt("Save Settings As", cp ? cp.processName || def : def); if (!name) return; let np = pl[name] = {}; cp.processName = name; for (let k in cp) { if (!cp.hasOwnProperty(k)) continue; // if (!pt.hasOwnProperty(k)) continue; // mask out invalid fields np[k] = cp[k]; } s.cproc[API.mode.get()] = name; API.conf.save(); API.event.settings(); } function settingsLoad() { API.conf.show(); } function putLocalDevice(devicename, obj) { settings().devices[devicename] = obj; API.conf.save(); } function removeLocalDevice(devicename) { delete settings().devices[devicename]; API.conf.save(); } function isLocalDevice(devicename) { return settings().devices[devicename] ? true : false; } function isFavoriteDevice(devicename) { return settings().favorites[devicename] ? true : false; } function getSelectedDevice() { return UI.deviceSelect.options[UI.deviceSelect.selectedIndex].text; } function selectDevice(devicename, lock) { deviceLock = lock; if (lock) UI.setupDevices.style.display = 'none'; if (isLocalDevice(devicename)) { setDeviceCode(settings().devices[devicename], devicename); } else { API.ajax("/kiri/filter/"+API.mode.get()+"/"+devicename, function(code) { setDeviceCode(code, devicename); }); } $('selected-device').innerHTML = devicename; } // only for local filters function cloneDevice() { let name = `${getSelectedDevice()}.copy`; let code = API.clone(settings().device); code.mode = API.mode.get(); putLocalDevice(name, code); setDeviceCode(code, name); } function setDeviceCode(code, devicename) { try { STATS.set(`ud_${API.mode.get_lower()}`, devicename); if (typeof(code) === 'string') code = js2o(code) || {}; let mode = API.mode.get(), current = settings(), local = isLocalDevice(devicename), dproc = current.devproc[devicename], dev = current.device = CONF.device_from_code(code,mode), proc = current.process; proc.outputOriginCenter = dev.outputOriginCenter; UI.deviceName.value = devicename; UI.deviceOrigin.checked = proc.outputOriginCenter; // add extruder selection buttons if (dev.extruders) { let ext = API.lists.extruders = []; dev.internal = 0; let selext = $('sel-ext'); selext.innerHTML = ''; for (let i=dev.extruders.length-1; i>=0; i--) { let b = DOC.createElement('button'); b.appendChild(DOC.createTextNode(i)); b.setAttribute('id', `sel-ext-${i}`); b.onclick = function() { API.selection.for_widgets(w => { current.widget[w.id] = {extruder: i}; }); API.platform.update_selected(); }; selext.appendChild(b); ext.push({id:i, name:i}); } } if (mode === 'CAM') { proc.camOriginTop = dev.outputOriginTop; } // disable editing for non-local devices [ UI.deviceName, UI.gcodePre, UI.gcodePost, UI.gcodePause, UI.bedDepth, UI.bedWidth, UI.maxHeight, UI.extrudeAbs, UI.deviceOrigin, UI.deviceOriginTop, UI.deviceRound, UI.gcodeFan, UI.gcodeTrack, UI.gcodeLayer, UI.extFilament, UI.extNozzle, UI.deviceMaxSpindle, UI.gcodeSpindle, UI.gcodeDwell, UI.gcodeChange, UI.gcodeFExt, UI.gcodeSpace, UI.gcodeStrip, UI.gcodeLaserOn, UI.gcodeLaserOff, UI.extPrev, UI.extNext, UI.extAdd, UI.extDel, UI.extOffsetX, UI.extOffsetY, UI.extSelect ].forEach(function(e) { e.disabled = !local; }); // hide spindle fields when device doens't support it if (mode === 'CAM') [ UI.extrudeAbs, UI.roughingSpindle, UI.finishingSpindle, UI.drillSpindle ].forEach(function(e) { e.parentNode.style.display = dev.spindleMax >= 0 ? 'none' : 'block'; }); UI.deviceSave.disabled = !local; UI.deviceDelete.disabled = !local; UI.deviceAdd.disabled = dev.noclone; API.view.update_fields(); platform.update_size(); current.filter[mode] = devicename; current.cdev[mode] = dev; // restore last process associated with this device if (dproc) API.conf.load(null, dproc); API.conf.save(); } catch (e) { console.log({error:e, device:code, devicename}); API.show.alert(`invalid or deprecated device: "${devicename}"`, 10); API.show.alert(`please select a new device`, 10); throw e; showDevices(); } API.function.clear(); API.event.settings(); } function renderDevices(devices) { UI.devices.onclick = UC.hidePop; UC.hidePop(); let selectedIndex = -1, selected = API.device.get(), devs = settings().devices; for (let local in devs) { if (!(devs.hasOwnProperty(local) && devs[local])) { continue; } let dev = devs[local], fdmCode = dev.cmd, fdmMode = (API.mode.get() === 'FDM'); if (dev.mode ? (dev.mode === API.mode.get()) : (fdmCode ? fdmMode : !fdmMode)) { devices.push(local); } }; devices = devices.sort(); UI.deviceClose.onclick = API.dialog.hide; UI.deviceSave.onclick = function() { API.function.clear(); API.conf.save(); showDevices(); }; UI.deviceAdd.onclick = function() { API.function.clear(); cloneDevice(); showDevices(); }; UI.deviceDelete.onclick = function() { API.function.clear(); removeLocalDevice(getSelectedDevice()); showDevices(); }; UI.deviceAll.onclick = function() { API.show.favorites(false); showDevices(); }; UI.deviceFavorites.onclick = function() { API.show.favorites(true); showDevices(); }; UI.deviceSelect.innerHTML = ''; let incr = 0; let faves = API.show.favorites(); let found = false; let first = devices[0]; // run through the list up to twice forcing faves off // the second time if incr === 0 (no devices shown) // if incr > 0, second loop is avoided for (let rep=0; rep<2; rep++) if (incr === 0) devices.forEach(function(device, index) { // force faves off for second try if (rep === 1) faves = false; let fav = isFavoriteDevice(device), loc = isLocalDevice(device); if (faves && !(fav || loc)) { return; } if (incr === 0) { first = device; } let opt = DOC.createElement('option'); opt.appendChild(DOC.createTextNode(device)); opt.onclick = function() { selectDevice(device); }; opt.ondblclick = function() { if (settings().favorites[device]) { delete settings().favorites[device]; API.show.alert(`removed "${device}" from favorites`, 3); } else { settings().favorites[device] = true; API.show.alert(`added "${device}" to favorites`, 3); } showDevices(); }; // if (API.show.favorites()) { if (loc) opt.setAttribute("local", 1); // } else { if (loc || fav) opt.setAttribute("favorite", 1); // } UI.deviceSelect.appendChild(opt); if (device === selected) { selectedIndex = incr; found = true; } incr++; }); if (selectedIndex >= 0) { UI.deviceSelect.selectedIndex = selectedIndex; selectDevice(selected); } else { UI.deviceSelect.selectedIndex = 0; selectDevice(first); } API.dialog.show('devices', true); API.dialog.update(); UI.deviceSelect.focus(); } function renderTools() { UI.toolSelect.innerHTML = ''; maxTool = 0; editTools.forEach(function(tool, index) { maxTool = Math.max(maxTool, tool.number); tool.order = index; let opt = DOC.createElement('option'); opt.appendChild(DOC.createTextNode(tool.name)); opt.onclick = function() { selectTool(tool) }; UI.toolSelect.appendChild(opt); }); } function selectTool(tool) { selectedTool = tool; UI.toolName.value = tool.name; UI.toolNum.value = tool.number; UI.toolFluteDiam.value = tool.flute_diam; UI.toolFluteLen.value = tool.flute_len; UI.toolShaftDiam.value = tool.shaft_diam; UI.toolShaftLen.value = tool.shaft_len; // UI.toolTaperAngle.value = tool.taper_angle || 70; UI.toolTaperTip.value = tool.taper_tip || 0; UI.toolMetric.checked = tool.metric; UI.toolType.selectedIndex = ['endmill','ballmill','tapermill'].indexOf(tool.type); renderTool(tool); } function otag(o) { if (Array.isArray(o)) { let out = [] o.forEach(oe => out.push(otag(oe))); return out.join(''); } let tags = []; Object.keys(o).forEach(key => { let val = o[key]; let att = []; Object.keys(val).forEach(tk => { let tv = val[tk]; att.push(`${tk.replace(/_/g,'-')}="${tv}"`); }); tags.push(`<${key} ${att.join(' ')}>`); }); return tags.join(''); } function renderTool(tool) { let type = selectedTool.type; let taper = type === 'tapermill'; // UI.toolTaperAngle.disabled = taper ? undefined : 'true'; UI.toolTaperTip.disabled = taper ? undefined : 'true'; $('tool-view').innerHTML = ''; setTimeout(() => { let svg = $('tool-svg'); let pad = 10; let dim = { w: svg.clientWidth, h: svg.clientHeight } let max = { w: dim.w - pad * 2, h: dim.h - pad * 2}; let off = { x: pad, y: pad }; let shaft_fill = "#cccccc"; let flute_fill = "#dddddd"; let stroke = "#777777"; let stroke_width = 3; let shaft = tool.shaft_len || 1; let flute = tool.flute_len || 1; let tip_len = type === "ballmill" ? tool.flute_diam / 2 : 0; let total_len = shaft + flute + tip_len; let shaft_len = (shaft / total_len) * max.h; let flute_len = (flute / total_len) * max.h; let total_wid = Math.max(tool.flute_diam, tool.shaft_diam, total_len/4); let shaft_off = (max.w * (1 - (tool.shaft_diam / total_wid))) / 2; let flute_off = (max.w * (1 - (tool.flute_diam / total_wid))) / 2; let taper_off = (max.w * (1 - ((tool.taper_tip || 0) / total_wid))) / 2; let parts = [ { rect: { x:off.x + shaft_off, y:off.y, width:max.w - shaft_off * 2, height:shaft_len, stroke, fill: shaft_fill, stroke_width } } ]; if (type === "tapermill") { let yoff = off.y + shaft_len; let mid = dim.w / 2; parts.push({path: {stroke_width, stroke, fill:flute_fill, d:[ `M ${off.x + flute_off} ${yoff}`, `L ${off.x + taper_off} ${yoff + flute_len}`, `L ${dim.w - off.x - taper_off} ${yoff + flute_len}`, `L ${dim.w - off.x - flute_off} ${yoff}`, `z` ].join('\n')}}); } else { parts.push({ rect: { x:off.x + flute_off, y:off.y + shaft_len, width:max.w - flute_off * 2, height:flute_len, stroke, fill: flute_fill, stroke_width } }); } if (type === "ballmill") { let rad = (max.w - flute_off * 2) / 2; let xend = dim.w - off.x - flute_off; let yoff = off.y + shaft_len + flute_len + stroke_width/2; parts.push({path: {stroke_width, stroke, fill:flute_fill, d:[ `M ${off.x + flute_off} ${yoff}`, `A ${rad} ${rad} 0 0 0 ${xend} ${yoff}`, // `L ${off.x + flute_off} ${yoff}` ].join('\n')}}) } svg.innerHTML = otag(parts); }, 10); } function updateTool() { selectedTool.name = UI.toolName.value; selectedTool.number = parseInt(UI.toolNum.value); selectedTool.flute_diam = parseFloat(UI.toolFluteDiam.value); selectedTool.flute_len = parseFloat(UI.toolFluteLen.value); selectedTool.shaft_diam = parseFloat(UI.toolShaftDiam.value); selectedTool.shaft_len = parseFloat(UI.toolShaftLen.value); // selectedTool.taper_angle = parseFloat(UI.toolTaperAngle.value); selectedTool.taper_tip = parseFloat(UI.toolTaperTip.value); selectedTool.metric = UI.toolMetric.checked; selectedTool.type = ['endmill','ballmill','tapermill'][UI.toolType.selectedIndex]; renderTools(); UI.toolSelect.selectedIndex = selectedTool.order; setToolChanged(true); renderTool(selectedTool); } function setToolChanged(changed) { editTools.changed = changed; UI.toolsSave.disabled = !changed; } function showTools() { if (API.mode.get_id() !== MODES.CAM) return; let selectedIndex = null; editTools = settings().tools.slice().sort((a,b) => { return a.name > b.name ? 1 : -1; }); setToolChanged(false); UI.toolsClose.onclick = function() { if (editTools.changed && !confirm("abandon changes?")) return; API.dialog.hide(); }; UI.toolAdd.onclick = function() { editTools.push({ id: Date.now(), number: maxTool + 1, name: "new", type: "endmill", shaft_diam: 0.25, shaft_len: 1, flute_diam: 0.25, flute_len: 2, // taper_angle: 70, taper_tip: 0, metric: false }); setToolChanged(true); renderTools(); UI.toolSelect.selectedIndex = editTools.length-1; selectTool(editTools[editTools.length-1]); }; UI.toolDelete.onclick = function() { editTools.remove(selectedTool); setToolChanged(true); renderTools(); }; UI.toolsSave.onclick = function() { if (selectedTool) updateTool(); settings().tools = editTools.sort((a,b) => { return a.name < b.name ? -1 : 1; }); setToolChanged(false); API.conf.save(); API.view.update_fields(); API.event.settings(); }; renderTools(); if (editTools.length > 0) { selectTool(editTools[0]); UI.toolSelect.selectedIndex = 0; } else { UI.toolAdd.onclick(); } API.dialog.show('tools'); UI.toolSelect.focus(); STATS.add('ua_get_tools'); } function showDevices() { if (deviceLock) return; API.modal.hide(); API.ajax("/api/filters-"+API.mode.get_lower(), function(flvalue) { if (!flvalue) return; renderDevices(js2o(flvalue)); STATS.add('ua_get_devs'); }); } function dragOverHandler(evt) { evt.stopPropagation(); evt.preventDefault(); evt.dataTransfer.dropEffect = 'copy'; SPACE.platform.setColor(0x00ff00); } function dragLeave() { SPACE.platform.setColor(0x555555); } function dropHandler(evt) { evt.stopPropagation(); evt.preventDefault(); SPACE.platform.setColor(0x555555); let files = evt.dataTransfer.files, plate = files.length < 5 || confirm(`add ${files.length} objects to workspace?`), group = files.length < 2 ? undefined : confirm('group files?') ? [] : undefined; if (plate) API.platform.load_files(files,group); } function loadCatalogFile(e) { API.widgets.load(e.target.getAttribute('load'), function(widget) { platform.add(widget); API.dialog.hide(); }); } function deleteCatalogFile(e) { CATALOG.deleteFile(e.target.getAttribute('del')); } 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 0) API.var.layer_at--; else if (delta < 0) API.var.layer_at++; API.show.slices(); }); SPACE.platform.onMove(API.conf.save); SPACE.platform.setRound(true); SPACE.useDefaultKeys(API.feature.on_key === undefined || API.feature.on_key_defaults); UC.setCompact(compact); Object.assign(UI, { // from static HTML alert: { dialog: $('alert-area'), text: $('alert-text') }, container: container, assets: assets, control: control, ctrlLeft: $('control-left'), ctrlRight: $('control-right'), layerView: $('layer-view'), layerSlider: $('layer-slider'), modelOpacity: $('opacity'), modal: $('modal'), print: $('print'), local: $('local'), help: $('help'), devices: $('devices'), deviceAdd: $('device-add'), deviceDelete: $('device-del'), deviceSave: $('device-save'), deviceClose: $('device-close'), deviceSelect: $('device-select'), deviceFavorites: $('device-favorites'), deviceAll: $('device-all'), tools: $('tools'), toolsSave: $('tools-save'), toolsClose: $('tools-close'), toolSelect: $('tool-select'), toolAdd: $('tool-add'), toolDelete: $('tool-del'), toolType: $('tool-type'), toolName: $('tool-name'), toolNum: $('tool-num'), toolFluteDiam: $('tool-fdiam'), toolFluteLen: $('tool-flen'), toolShaftDiam: $('tool-sdiam'), toolShaftLen: $('tool-slen'), // toolTaperAngle: $('tool-tangle'), toolTaperTip: $('tool-ttip'), toolMetric: $('tool-metric'), catalog: $('catalog'), catalogBody: $('catalogBody'), catalogList: $('catalogList'), settings: $('settings'), settingsBody: $('settingsBody'), settingsList: $('settingsList'), layerID: $('layer-id'), layerSpan: $('layer-span'), layerRange: $('layer-range'), loading: $('loading').style, progress: $('progress').style, prostatus: $('prostatus'), selection: $('selection'), sizeX: $('size_x'), sizeY: $('size_y'), sizeZ: $('size_z'), scaleX: $('scale_x'), scaleY: $('scale_y'), scaleZ: $('scale_z'), lockX: $('lock_x'), lockY: $('lock_y'), lockZ: $('lock_z'), stock: $('stock'), stockWidth: $('stock-width'), stockDepth: $('stock-width'), stockHeight: $('stock-width'), device: UC.newGroup(LANG.dv_gr_dev, $('device'), {group:"ddev", nocompact:true}), deviceName: UC.newInput(LANG.dv_name_s, {title:LANG.dv_name_l, size:"60%", text:true}), bedWidth: UC.newInput(LANG.dv_bedw_s, {title:LANG.dv_bedw_l, convert:UC.toInt}), bedDepth: UC.newInput(LANG.dv_bedd_s, {title:LANG.dv_bedd_l, convert:UC.toInt}), maxHeight: UC.newInput(LANG.dv_bedh_s, {title:LANG.dv_bedh_l, convert:UC.toInt, modes:FDM_SLA}), deviceMaxSpindle: UC.newInput(LANG.dv_spmx_s, {title:LANG.dv_spmx_l, convert:UC.toInt, modes:CAM}), deviceOrigin: UC.newBoolean(LANG.dv_orgc_s, onBooleanClick, {title:LANG.dv_orgc_l, modes:GCODE}), deviceOriginTop: UC.newBoolean(LANG.dv_orgt_s, onBooleanClick, {title:LANG.dv_orgt_l, modes:CAM}), deviceRound: UC.newBoolean(LANG.dv_bedc_s, onBooleanClick, {title:LANG.dv_bedc_l, modes:FDM}), extruder: UC.newGroup(LANG.dv_gr_ext, $('device'), {group:"dext", nocompact:true, modes:FDM}), extFilament: UC.newInput(LANG.dv_fila_s, {title:LANG.dv_fila_l, convert:UC.toFloat, modes:FDM}), extNozzle: UC.newInput(LANG.dv_nozl_s, {title:LANG.dv_nozl_l, convert:UC.toFloat, modes:FDM}), extOffsetX: UC.newInput(LANG.dv_exox_s, {title:LANG.dv_exox_l, convert:UC.toFloat, modes:FDM, expert:true}), extOffsetY: UC.newInput(LANG.dv_exoy_s, {title:LANG.dv_exoy_l, convert:UC.toFloat, modes:FDM, expert:true}), extSelect: UC.newText(LANG.dv_exts_s, {title:LANG.dv_exts_l, modes:FDM, size:14, height:3, modes:FDM, expert:true}), extrudeAbs: UC.newBoolean(LANG.dv_xtab_s, onBooleanClick, {title:LANG.dv_xtab_l, modes:FDM}), extActions: UC.newTableRow([[ UI.extPrev = UC.newButton("<"), UI.extAdd = UC.newButton("+"), UI.extDel = UC.newButton("-"), UI.extNext = UC.newButton(">") ]], {modes:FDM, expert:true}), gcode: UC.newGroup(LANG.dv_gr_gco, $('device'), {group:"dgco", nocompact:true, modes:GCODE}), gcodeFan: UC.newInput(LANG.dv_fanp_s, {title:LANG.dv_fanp_l, modes:FDM, size:"40%", text:true}), gcodeTrack: UC.newInput(LANG.dv_prog_s, {title:LANG.dv_prog_l, modes:FDM, size:"40%", text:true}), gcodeLayer: UC.newText(LANG.dv_layr_s, {title:LANG.dv_layr_l, modes:FDM, size:14, height: 2}), gcodeSpace: UC.newBoolean(LANG.dv_tksp_s, null, {title:LANG.dv_tksp_l, modes:CAM_LASER}), gcodeStrip: UC.newBoolean(LANG.dv_strc_s, null, {title:LANG.dv_strc_l, modes:CAM}), gcodeFExt: UC.newInput(LANG.dv_fext_s, {title:LANG.dv_fext_l, modes:CAM_LASER, size:7, text:true}), gcodeDwell: UC.newText(LANG.dv_dwll_s, {title:LANG.dv_dwll_l, modes:CAM, size:14, height:2}), gcodeChange: UC.newText(LANG.dv_tool_s, {title:LANG.dv_tool_l, modes:CAM, size:14, height:2}), gcodeSpindle: UC.newText(LANG.dv_sspd_s, {title:LANG.dv_sspd_l, modes:CAM, size:14, height:2}), gcodePause: UC.newText(LANG.dv_paus_s, {title:LANG.dv_paus_l, modes:FDM, size:14, height:3}), gcodeLaserOn: UC.newText(LANG.dv_lzon_s, {title:LANG.dv_lzon_l, modes:LASER, size:14, height:3}), gcodeLaserOff: UC.newText(LANG.dv_lzof_s, {title:LANG.dv_lzof_l, modes:LASER, size:14, height:3}), gcodePre: UC.newText(LANG.dv_head_s, {title:LANG.dv_head_l, modes:GCODE, size:14, height:3}), gcodePost: UC.newText(LANG.dv_foot_s, {title:LANG.dv_foot_l, modes:GCODE, size:14, height:3}), mode: UC.newGroup(LANG.mo_menu, assets, {region:"left"}), modeTable: UC.newTableRow([ [ UI.modeFDM = UC.newButton(LANG.mo_fdmp, function() { API.mode.set('FDM',null,platform.update_size) }), ],[ UI.modeSLA = UC.newButton(LANG.mo_slap, function() { API.mode.set('SLA',null,platform.update_size) }), ],[ UI.modeLASER = UC.newButton(LANG.mo_lazr, function() { API.mode.set('LASER',null,platform.update_size) }), ],[ UI.modeCAM = UC.newButton(LANG.mo_cncm, function() { API.mode.set('CAM',null,platform.update_size) }, {id:"modeCAM"}), ] ]), system: UC.newGroup(LANG.su_menu), sysTable: UC.newTableRow([ [ UI.setupDevices = UC.newButton(LANG.su_devi, showDevices) ],[ UI.setupTools = UC.newButton(LANG.su_tool, showTools, {modes:CAM}) ],[ UI.setupExport = UC.newButton(LANG.su_xprt, settingsExport, {modes:ALL, expert:true}) ],[ UI.localButton = UC.newButton(LANG.su_locl, API.show.local, {modes:FDM_CAM, expert:true}) ],[ UI.helpButton = UC.newButton(LANG.su_help, API.help.show) ] ]), wsFunc: UC.newGroup(LANG.fn_menu), wsFuncTable: UC.newTableRow([ [ UI.load = UC.newButton(LANG.fn_impo, function() { API.event.import() }, {class:"asym"}), UI.import = UC.newButton("+", undefined, {class:"asym"}) ],[ UI.modeArrange = UC.newButton(LANG.fn_arra, platform.layout), ],[ UI.modeSlice = UC.newButton(LANG.fn_slic, API.function.slice) ],[ UI.modePreview = UC.newButton(LANG.fn_prev, API.function.print), ],[ UI.modeExport = UC.newButton(LANG.fn_expo, API.function.export) ] ]), camera: UC.newGroup(LANG.vu_menu), camTable: UC.newTableRow([ [ UC.newButton(LANG.vu_home, SPACE.view.home), UC.newButton(LANG.vu_rset, SPACE.view.reset) ],[ UC.newButton(LANG.vu_sptp, SPACE.view.top), UC.newButton(LANG.vu_spfr, SPACE.view.front), ],[ UC.newButton(LANG.vu_splt, SPACE.view.left), UC.newButton(LANG.vu_sprt, SPACE.view.right) ] ]), workspace: UC.newGroup(LANG.ws_menu), wsTable: UC.newTableRow([ [ UI.saveButton = UC.newButton(LANG.ws_save, API.space.save), ],[ UC.newButton(LANG.ws_cler, API.space.clear) ] ]), layout: UC.newGroup(LANG.op_menu), expert: UC.newBoolean(LANG.op_xprt_s, booleanSave, {title:LANG.op_xprt_l}), compact: UC.newBoolean(LANG.op_comp_s, booleanSave, {title:LANG.op_comp_l}), dark: UC.newBoolean(LANG.op_dark_s, booleanSave, {title:LANG.op_dark_l}), showOrigin: UC.newBoolean(LANG.op_show_s, booleanSave, {title:LANG.op_show_l}), alignTop: UC.newBoolean(LANG.op_alig_s, booleanSave, {title:LANG.op_alig_l, modes:CAM}), autoLayout: UC.newBoolean(LANG.op_auto_s, booleanSave, {title:LANG.op_auto_l}), freeLayout: UC.newBoolean(LANG.op_free_s, booleanSave, {title:LANG.op_free_l, modes:ALL, expert:true}), reverseZoom: UC.newBoolean(LANG.op_invr_s, booleanSave, {title:LANG.op_invr_l, modes:ALL, expert:true}), units: UC.newSelect(LANG.op_unit_s, {title: LANG.op_unit_l, modes:CAM}, "units"), // allow modules to insert new items at the bottom of the left menu appendLeft: UC.checkpoint(), layers: UC.setGroup($("layers")), layerOutline: UC.newBoolean(LANG.la_olin, onLayerToggle, {modes:LOCAL ? ALL : FDM_LASER}), layerTrace: UC.newBoolean(LANG.la_trce, onLayerToggle, {modes:FDM_LASER_SLA}), layerFacing: UC.newBoolean(LANG.la_face, onLayerToggle, {modes:CAM}), layerRough: UC.newBoolean(LANG.la_ruff, onLayerToggle, {modes:CAM}), layerFinish: UC.newBoolean(LANG.la_fini, onLayerToggle, {modes:CAM}), layerFinishX: UC.newBoolean(LANG.la_finx, onLayerToggle, {modes:CAM}), layerFinishY: UC.newBoolean(LANG.la_finy, onLayerToggle, {modes:CAM}), layerDelta: UC.newBoolean(LANG.la_dlta, onLayerToggle, {modes:FDM_SLA}), layerSolid: UC.newBoolean(LANG.la_slds, onLayerToggle, {modes:FDM_SLA}), layerFill: UC.newBoolean(LANG.la_fill, onLayerToggle, {modes:FDM}), layerSparse: UC.newBoolean(LANG.la_sprs, onLayerToggle, {modes:FDM}), layerSupport: UC.newBoolean(LANG.la_sprt, onLayerToggle, {modes:FDM}), layerPrint: UC.newBoolean(LANG.la_prnt, onLayerToggle), layerMoves: UC.newBoolean(LANG.la_move, onLayerToggle), settingsGroup: UC.newGroup(LANG.se_menu, control, {region:"right"}), settingsTable: UC.newTableRow([ [ UI.settingsLoad = UC.newButton(LANG.se_load, settingsLoad), UI.settingsSave = UC.newButton(LANG.se_save, settingsSave) ] ]), process: UC.newGroup(LANG.sl_menu, control, {modes:FDM_LASER}), sliceHeight: UC.newInput(LANG.sl_lahi_s, {title:LANG.sl_lahi_l, convert:UC.toFloat, modes:FDM}), sliceShells: UC.newInput(LANG.sl_shel_s, {title:LANG.sl_shel_l, convert:UC.toInt, modes:FDM}), sliceTopLayers: UC.newInput(LANG.sl_ltop_s, {title:LANG.sl_ltop_l, convert:UC.toInt, modes:FDM}), sliceSolidLayers: UC.newInput(LANG.sl_lsld_s, {title:LANG.sl_lsld_l, convert:UC.toInt, modes:FDM}), sliceBottomLayers: UC.newInput(LANG.sl_lbot_s, {title:LANG.sl_lbot_l, convert:UC.toInt, modes:FDM}), process: UC.newGroup(LANG.fi_menu, control, {modes:FDM}), sliceFillType: UC.newSelect(LANG.fi_type, {modes:FDM}, "infill"), sliceFillSparse: UC.newInput(LANG.fi_pcnt_s, {title:LANG.fi_pcnt_l, convert:UC.toFloat, bound:UC.bound(0.0,1.0), modes:FDM}), sliceFillAngle: UC.newInput(LANG.fi_angl_s, {title:LANG.fi_angl_l, convert:UC.toFloat, modes:FDM, expert:true}), sliceFillOverlap: UC.newInput(LANG.fi_over_s, {title:LANG.fi_over_l, convert:UC.toFloat, bound:UC.bound(0.0,2.0), modes:FDM, expert:true}), firstLayer: UC.newGroup(LANG.fl_menu, null, {modes:FDM}), firstSliceHeight: UC.newInput(LANG.fl_lahi_s, {title:LANG.fl_lahi_l, convert:UC.toFloat, modes:FDM}), firstLayerRate: UC.newInput(LANG.fl_rate_s, {title:LANG.fl_rate_l, convert:UC.toFloat, modes:FDM}), firstLayerFillRate: UC.newInput(LANG.fl_frat_s, {title:LANG.fl_frat_l, convert:UC.toFloat, modes:FDM}), firstLayerPrintMult: UC.newInput(LANG.fl_mult_s, {title:LANG.fl_mult_l, convert:UC.toFloat, modes:FDM, expert:true}), outputBrimCount: UC.newInput(LANG.fl_skrt_s, {title:LANG.fl_skrt_l, convert:UC.toInt, modes:FDM}), outputBrimOffset: UC.newInput(LANG.fl_skro_s, {title:LANG.fl_skro_l, convert:UC.toFloat, modes:FDM}), firstLayerNozzleTemp:UC.newInput(LANG.fl_nozl_s, {title:LANG.fl_nozl_l, convert:UC.toInt, modes:FDM, expert:true}), firstLayerBedTemp: UC.newInput(LANG.fl_bedd_s, {title:LANG.fl_bedd_l, convert:UC.toInt, modes:FDM, expert:true}), support: UC.newGroup(LANG.sp_menu, null, {modes:FDM}), sliceSupportDensity: UC.newInput(LANG.sp_dens_s, {title:LANG.sp_dens_l, convert:UC.toFloat, bound:UC.bound(0.05,1.0), modes:FDM}), sliceSupportSize: UC.newInput(LANG.sp_size_s, {title:LANG.sp_size_l, bound:UC.bound(1.0,200.0), convert:UC.toFloat, modes:FDM}), sliceSupportOffset: UC.newInput(LANG.sp_offs_s, {title:LANG.sp_offs_l, bound:UC.bound(0.0,200.0), convert:UC.toFloat, modes:FDM}), sliceSupportGap: UC.newInput(LANG.sp_gaps_s, {title:LANG.sp_gaps_l, bound:UC.bound(0,5), convert:UC.toInt, modes:FDM, expert:true}), sliceSupportSpan: UC.newInput(LANG.sp_span_s, {title:LANG.sp_span_l, bound:UC.bound(0.0,200.0), convert:UC.toFloat, modes:FDM}), sliceSupportArea: UC.newInput(LANG.sp_area_s, {title:LANG.sp_area_l, bound:UC.bound(0.1,200.0), convert:UC.toFloat, modes:FDM}), sliceSupportExtra: UC.newInput(LANG.sp_xpnd_s, {title:LANG.sp_xpnd_l, bound:UC.bound(0.0,200.0), convert:UC.toFloat, modes:FDM, expert:true}), sliceSupportNozzle: UC.newSelect(LANG.sp_nozl_s, {title:LANG.sp_nozl_l, modes:FDM, expert:true}, "extruders"), sliceSupportEnable: UC.newBoolean(LANG.enable, onBooleanClick, {modes:FDM}), laserOffset: UC.newInput(LANG.ls_offs_s, {title:LANG.ls_offs_l, convert:UC.toFloat, modes:LASER}), laserSliceHeight: UC.newInput(LANG.ls_lahi_s, {title:LANG.ls_lahi_l, convert:UC.toFloat, modes:LASER}), laserSliceSingle: UC.newBoolean(LANG.ls_sngl_s, onBooleanClick, {title:LANG.ls_sngl_l, modes:LASER}), camCommon: UC.newGroup(LANG.cc_menu, null, {modes:CAM}), camFastFeed: UC.newInput(LANG.cc_rapd_s, {title:LANG.cc_rapd_l, convert:UC.toInt, modes:CAM}), roughing: UC.newGroup(LANG.cr_menu, null, {modes:CAM}), roughingTool: UC.newSelect(LANG.cc_tool, {modes:CAM}), roughingSpindle: UC.newInput(LANG.cc_spnd_s, {title:LANG.cc_spnd_l, convert:UC.toInt, modes:CAM}), roughingOver: UC.newInput(LANG.cc_sovr_s, {title:LANG.cc_sovr_l, convert:UC.toFloat, bound:UC.bound(0.1,1.0), modes:CAM}), roughingDown: UC.newInput(LANG.cc_sdwn_s, {title:LANG.cc_sdwn_l, convert:UC.toFloat, modes:CAM}), roughingSpeed: UC.newInput(LANG.cc_feed_s, {title:LANG.cc_feed_l, convert:UC.toInt, modes:CAM}), roughingPlunge: UC.newInput(LANG.cc_plng_s, {title:LANG.cc_plng_l, convert:UC.toInt, modes:CAM}), roughingStock: UC.newInput(LANG.cr_lsto_s, {title:LANG.cr_lsto_l, convert:UC.toFloat, modes:CAM}), camPocketOnlyRough: UC.newBoolean(LANG.cc_pock_s, onBooleanClick, {title:LANG.cc_pock_l, modes:CAM}), camEaseDown: UC.newBoolean(LANG.cr_ease_s, onBooleanClick, {title:LANG.cr_ease_l, modes:CAM}), roughingOn: UC.newBoolean(LANG.enable, onBooleanClick, {modes:CAM}), finishing: UC.newGroup(LANG.cf_menu, null, {modes:CAM}), finishingTool: UC.newSelect(LANG.cc_tool, {modes:CAM}), finishingSpindle: UC.newInput(LANG.cc_spnd_s, {title:LANG.cc_spnd_l, convert:UC.toInt, modes:CAM}), finishingOver: UC.newInput(LANG.cc_sovr_s, {title:LANG.cc_sovr_l, convert:UC.toFloat, bound:UC.bound(0.05,1.0), modes:CAM}), finishingDown: UC.newInput(LANG.cc_sdwn_s, {title:LANG.cc_sdwn_l, convert:UC.toFloat, modes:CAM}), finishingAngle: UC.newInput(LANG.cf_angl_s, {title:LANG.cf_angl_l, convert:UC.toFloat, bound:UC.bound(45,90), modes:CAM}), finishingSpeed: UC.newInput(LANG.cc_feed_s, {title:LANG.cc_feed_l, convert:UC.toInt, modes:CAM}), finishingPlunge: UC.newInput(LANG.cc_plng_s, {title:LANG.cc_plng_l, convert:UC.toInt, modes:CAM}), camPocketOnlyFinish: UC.newBoolean(LANG.cc_pock_s, onBooleanClick, {title:LANG.cc_pock_l, modes:CAM}), finishingOn: UC.newBoolean(LANG.cf_watr_s, onBooleanClick, {title:LANG.cf_watr_l, modes:CAM}), finishingXOn: UC.newBoolean(LANG.cf_linx_s, onBooleanClick, {title:LANG.cf_linx_l, modes:CAM}), finishingYOn: UC.newBoolean(LANG.cf_liny_s, onBooleanClick, {title:LANG.cf_liny_l, modes:CAM}), finishCurvesOnly: UC.newBoolean(LANG.cf_curv_s, onBooleanClick, {title:LANG.cf_curv_l, modes:CAM}), drilling: UC.newGroup(LANG.cd_menu, null, {modes:CAM}), drillTool: UC.newSelect(LANG.cc_tool, {modes:CAM}), drillSpindle: UC.newInput(LANG.cc_spnd_s, {title:LANG.cc_spnd_l, convert:UC.toInt, modes:CAM}), drillDown: UC.newInput(LANG.cd_plpr_s, {title:LANG.cd_plpr_l, convert:UC.toFloat, modes:CAM}), drillDownSpeed: UC.newInput(LANG.cc_plng_s, {title:LANG.cc_plng_l, convert:UC.toFloat, modes:CAM}), drillDwell: UC.newInput(LANG.cd_dwll_s, {title:LANG.cd_dwll_l, convert:UC.toFloat, modes:CAM}), drillLift: UC.newInput(LANG.cd_lift_s, {title:LANG.cd_lift_l, convert:UC.toFloat, modes:CAM}), drillingOn: UC.newBoolean(LANG.enable, onBooleanClick, {modes:CAM}), camTabs: UC.newGroup(LANG.ct_menu, null, {modes:CAM}), camTabsAngle: UC.newInput(LANG.ct_angl_s, {title:LANG.ct_angl_l, convert:UC.toInt, bound:UC.bound(0,360), modes:CAM}), camTabsCount: UC.newInput(LANG.ct_numb_s, {title:LANG.ct_numb_l, convert:UC.toInt, bound:UC.bound(1,20), modes:CAM}), camTabsWidth: UC.newInput(LANG.ct_wdth_s, {title:LANG.ct_wdth_l, convert:UC.toFloat, bound:UC.bound(0.1,100), modes:CAM}), camTabsHeight: UC.newInput(LANG.ct_hght_s, {title:LANG.ct_hght_l, convert:UC.toFloat, bound:UC.bound(0.1,100), modes:CAM}), camTabsOn: UC.newBoolean(LANG.enable, onBooleanClick, {title:LANG.ct_nabl_l, modes:CAM}), output: UC.newGroup(LANG.fr_menu, null, {modes:FDM}), outputRaftSpacing: UC.newInput(LANG.fr_spac_s, {title:LANG.fr_spac_l, convert:UC.toFloat, bound:UC.bound(0.0,3.0), modes:FDM}), outputRaft: UC.newBoolean(LANG.enable, onBooleanClick, {title:LANG.fr_nabl_l, modes:FDM}), output: UC.newGroup(LANG.ou_menu, null, {modes:GCODE}), outputTileSpacing: UC.newInput(LANG.ou_spac_c, {title:LANG.ou_spac_l, convert:UC.toInt, modes:LASER}), outputTileScaling: UC.newInput(LANG.ou_scal_s, {title:LANG.ou_scal_l, convert:UC.toInt, bound:UC.bound(0.1,100), modes:LASER}), outputLaserPower: UC.newInput(LANG.ou_powr_s, {title:LANG.ou_powr_l, convert:UC.toInt, bound:UC.bound(1,100), modes:LASER}), outputLaserSpeed: UC.newInput(LANG.ou_sped_s, {title:LANG.ou_sped_l, convert:UC.toInt, modes:LASER}), outputLaserMerged: UC.newBoolean(LANG.ou_mrgd_s, onBooleanClick, {title:LANG.ou_mrgd_l, modes:LASER}), outputLaserGroup: UC.newBoolean(LANG.ou_grpd_s, onBooleanClick, {title:LANG.ou_grpd_l, modes:LASER}), outputTemp: UC.newInput(LANG.ou_nozl_s, {title:LANG.ou_nozl_l, convert:UC.toInt, modes:FDM}), outputBedTemp: UC.newInput(LANG.ou_bedd_s, {title:LANG.ou_bedd_l, convert:UC.toInt, modes:FDM}), outputFeedrate: UC.newInput(LANG.ou_feed_s, {title:LANG.ou_feed_l, convert:UC.toInt, modes:FDM}), outputFinishrate: UC.newInput(LANG.ou_fini_s, {title:LANG.ou_fini_l, convert:UC.toInt, modes:FDM}), outputSeekrate: UC.newInput(LANG.ou_move_s, {title:LANG.ou_move_l, convert:UC.toInt, modes:FDM}), outputShellMult: UC.newInput(LANG.ou_shml_s, {title:LANG.ou_exml_l, convert:UC.toFloat, bound:UC.bound(0.0,2.0), modes:FDM}), outputFillMult: UC.newInput(LANG.ou_flml_s, {title:LANG.ou_exml_l, convert:UC.toFloat, bound:UC.bound(0.0,2.0), modes:FDM}), outputSparseMult: UC.newInput(LANG.ou_spml_s, {title:LANG.ou_exml_l, convert:UC.toFloat, bound:UC.bound(0.0,2.0), modes:FDM}), outputFanLayer: UC.newInput(LANG.ou_fanl_s, {title:LANG.ou_fanl_l, convert:UC.toInt, bound:UC.bound(0,100), modes:FDM, expert:true}), camTolerance: UC.newInput(LANG.ou_toll_s, {title:LANG.ou_toll_l, convert:UC.toFloat, bound:UC.bound(0.001,1.0), modes:CAM}), camZTopOffset: UC.newInput(LANG.ou_ztof_s, {title:LANG.ou_ztof_l, convert:UC.toFloat, modes:CAM}), camZBottom: UC.newInput(LANG.ou_zbot_s, {title:LANG.ou_zbot_l, convert:UC.toFloat, modes:CAM}), camZClearance: UC.newInput(LANG.ou_zclr_s, {title:LANG.ou_zclr_l, convert:UC.toFloat, bound:UC.bound(0.01,100), modes:CAM}), outputClockwise: UC.newBoolean(LANG.ou_conv_s, onBooleanClick, {title:LANG.ou_conv_l, modes:CAM}), camDepthFirst: UC.newBoolean(LANG.ou_depf_s, onBooleanClick, {title:LANG.ou_depf_l, modes:CAM}), camStock: UC.newGroup(LANG.cs_menu, null, {modes:CAM}), camStockX: UC.newInput(LANG.cs_wdth_s, {title:LANG.cs_wdth_l, convert:UC.toFloat, bound:UC.bound(0,9999), modes:CAM}), camStockY: UC.newInput(LANG.cs_dpth_s, {title:LANG.cs_dpth_l, convert:UC.toFloat, bound:UC.bound(0,9999), modes:CAM}), camStockZ: UC.newInput(LANG.cs_hght_s, {title:LANG.cs_hght_l, convert:UC.toFloat, bound:UC.bound(0,9999), modes:CAM}), camStockOffset: UC.newBoolean(LANG.cs_offs_s, onBooleanClick, {title:LANG.cs_offs_l, modes:CAM}), outputOriginBounds: UC.newBoolean(LANG.or_bnds_s, onBooleanClick, {title:LANG.or_bnds_l, modes:LASER}), outputOriginCenter: UC.newBoolean(LANG.or_cntr_s, onBooleanClick, {title:LANG.or_cntr_l, modes:CAM_LASER}), camOriginTop: UC.newBoolean(LANG.or_topp_s, onBooleanClick, {title:LANG.or_topp_l, modes:CAM}), advanced: UC.newGroup(LANG.ad_menu, null, {modes:FDM, expert:true}), outputRetractDist: UC.newInput(LANG.ad_rdst_s, {title:LANG.ad_rdst_l, convert:UC.toFloat, modes:FDM, expert:true}), outputRetractSpeed: UC.newInput(LANG.ad_rrat_s, {title:LANG.ad_rrat_l, convert:UC.toInt, modes:FDM, expert:true}), outputRetractDwell: UC.newInput(LANG.ad_rdwl_s, {title:LANG.ad_rdwl_l, convert:UC.toInt, modes:FDM, expert:true}), outputCoastDist: UC.newInput(LANG.ad_scst_s, {title:LANG.ad_scst_l, bound:UC.bound(0.0,10), convert:UC.toFloat, modes:FDM, expert:true}), // outputWipeDistance: UC.newInput("wipe", {title:"non-printing move at\close of polygon\nin millimeters", bound:UC.bound(0.0,10), convert:UC.toFloat, modes:FDM, expert:true}), sliceSolidMinArea: UC.newInput(LANG.ad_msol_s, {title:LANG.ad_msol_l, convert:UC.toFloat, modes:FDM, expert:true}), sliceMinHeight: UC.newInput(LANG.ad_minl_s, {title:LANG.ad_minl_l, bound:UC.bound(0,3.0), convert:UC.toFloat, modes:FDM, expert:true}), outputMinSpeed: UC.newInput(LANG.ad_mins_s, {title:LANG.ad_mins_l, bound:UC.bound(5,200), convert:UC.toFloat, modes:FDM, expert:true}), outputShortPoly: UC.newInput(LANG.ad_spol_s, {title:LANG.ad_spol_l, bound:UC.bound(0,200), convert:UC.toFloat, modes:FDM, expert:true}), zHopDistance: UC.newInput(LANG.ad_zhop_s, {title:LANG.ad_zhop_l, bound:UC.bound(0,3.0), convert:UC.toFloat, modes:FDM, expert:true}), antiBacklash: UC.newInput(LANG.ad_abkl_s, {title:LANG.ad_abkl_l, bound:UC.bound(0,3), convert:UC.toInt, modes:FDM, expert:true}), // detectThinWalls: UC.newBoolean("thin wall fill", onBooleanClick, {title: "detect and fill thin openings\nbetween shells walls", modes:FDM, expert:true}) gcodePauseLayers: UC.newInput(LANG.ag_paws_s, {title:LANG.ag_paws_l, modes:FDM, expert:true}), outputLayerRetract: UC.newBoolean(LANG.ad_lret_s, onBooleanClick, {title:LANG.ad_lret_l, modes:FDM, expert:true}), // SLA slaProc: UC.newGroup('slice', null, {modes:SLA, group:"sla-slice"}), slaSlice: UC.newInput('layer height', {title:'slice height', convert:UC.toFloat, modes:SLA}), slaShell: UC.newInput('hollow shell', {title:'shell thickness in mm\nuse multiple of layer height\nuse 0 for solid', convert:UC.toFloat, modes:SLA}), slaOpenTop: UC.newBoolean('open top', onBooleanClick, {title:'solid layers', modes:SLA}), slaOpenBase: UC.newBoolean('open base', onBooleanClick, {title:'bottom layers', modes:SLA}), slaOutput: UC.newGroup('output', null, {modes:SLA, group:"sla-output"}), slaLayerOn: UC.newInput('layer on', {title:'layer light on\ntime in seconds', convert:UC.toFloat, modes:SLA}), slaLayerOff: UC.newInput('layer off', {title:'layer light off\ntime in seconds', convert:UC.toFloat, modes:SLA}), slaBaseLayers: UC.newInput('base count', {title:'number of\nbase layers', convert:UC.toInt, modes:SLA}), slaBaseOn: UC.newInput('base on', {title:'base layer light on\ntime in seconds', convert:UC.toFloat, modes:SLA}), slaPeelDist: UC.newInput('peel dist', {title:'peel distance\nin millimeters', convert:UC.toFloat, modes:SLA}), slaPeelLift: UC.newInput('peel lift', {title:'peel lift speed\nin mm/sec', convert:UC.toFloat, modes:SLA}), slaPeelDrop: UC.newInput('peel drop', {title:'peel drop speed\nin mm/sec', convert:UC.toFloat, modes:SLA}), }); if (!LOCAL) { UI.modeSLA.style.display = 'none'; } if (!lang_set) { // for english only, add underlined labels for hotkeys UI.setupDevices.innerHTML = "Devices"; UI.setupTools.innerHTML = "Tools"; UI.modeArrange.innerHTML = "Arrange"; UI.modeSlice.innerHTML = "Slice"; UI.modePreview.innerHTML = "Preview"; UI.modeExport.innerHTML = "Export"; } // populate language drop-down let lp = $('langpop'); let elp = DOC.createElement("div"); let dlp = DOC.createElement("div"); elp.appendChild(DOC.createTextNode('english')); dlp.appendChild(DOC.createTextNode('danish')); lp.appendChild(elp); lp.appendChild(dlp); elp.onclick = function() { SDB.setItem('kiri-lang', 'en-us'); LOC.reload(); }; dlp.onclick = function() { SDB.setItem('kiri-lang', 'da-dk'); LOC.reload(); }; SPACE.addEventHandlers(self, [ 'keyup', keyUpHandler, 'keydown', keyDownHandler, 'keypress', keyHandler, 'dragover', dragOverHandler, 'dragleave', dragLeave, 'drop', 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), yv = parseFloat(UI.sizeY.was), zv = parseFloat(UI.sizeZ.was), xr = (UI.lockX.checked ? ra : 1), yr = (UI.lockY.checked ? ra : 1), zr = (UI.lockZ.checked ? ra : 1); API.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), yv = parseFloat(UI.scaleY.was), zv = parseFloat(UI.scaleZ.was), xr = (UI.lockX.checked ? ra : 1), yr = (UI.lockY.checked ? ra : 1), zr = (UI.lockZ.checked ? ra : 1); API.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 deg = parseFloat(e.target.value) * DEG; e.target.value = 0; switch (e.target.id.split('').pop()) { case 'x': return API.selection.rotate(deg,0,0); case 'y': return API.selection.rotate(0,deg,0); case 'z': return API.selection.rotate(0,0,deg); } } SPACE.onEnterKey([ UI.layerSpan, function() { API.show.slices() }, UI.layerID, function() { API.show.layer(UI.layerID.value) }, UI.scaleX, selectionScale, UI.scaleY, selectionScale, UI.scaleZ, selectionScale, UI.sizeX, selectionSize, UI.sizeY, selectionSize, UI.sizeZ, selectionSize, UI.toolName, updateTool, UI.toolNum, updateTool, UI.toolFluteDiam, updateTool, UI.toolFluteLen, updateTool, UI.toolShaftDiam, updateTool, UI.toolShaftLen, updateTool, UI.toolTaperTip, updateTool, $('rot_x'), selectionRotate, $('rot_y'), selectionRotate, $('rot_z'), selectionRotate ]); UI.layerID.convert = UC.toFloat.bind(UI.layerID); UI.layerSpan.convert = UC.toFloat.bind(UI.layerSpan); UI.layerRange.onclick = function() { UI.layerRange.checked = !(UI.layerRange.checked || false); API.show.slices(); }; $('layer-toggle').onclick = function(ev) { let ls = UI.layers.style; ls.display = ls.display !== 'block' ? 'block' : 'none'; UI.layers.style.left = ev.target.getBoundingClientRect().left + 'px'; }; UI.modelOpacity.onchange = UI.modelOpacity.onclick = function(ev) { API.widgets.opacity(parseInt(UI.modelOpacity.value)/100); }; UI.layerSlider.ondblclick = function() { UI.layerRange.checked = !UI.layerRange.checked; API.show.slices(); }; UI.layerSlider.onmousedown = function(ev) { if (ev.shiftKey) UI.layerRange.checked = !UI.layerRange.checked; }; UI.layerSlider.onclick = function() { API.show.layer(UI.layerSlider.value); }; UI.layerSlider.onmousemove = UI.layerSlider.onchange = function() { API.show.layer(UI.layerSlider.value); }; UI.layerSlider.onmouseup = function() { API.focus() }; UI.import.setAttribute("import","1"); UI.import.onclick = function() { UC.hidePoppers(); API.dialog.show("catalog"); }; UI.toolMetric.onclick = updateTool; UI.toolType.onchange = updateTool; $('apphelp').onclick = API.help.show; SPACE.platform.setSize( settings().device.bedWidth, settings().device.bedDepth, settings().device.bedHeight ); if (compact) { UI.selection.classList.add('compact'); } if (dark) { SPACE.platform.setGrid(25, 5, 0x999999, 0x333333); SPACE.platform.opacity(0.8); DOC.body.classList.add('dark'); } else { SPACE.platform.setGrid(25, 5, 0x999999, 0xcccccc); SPACE.platform.opacity(0.3); } SPACE.mouse.downSelect(function(int,event) { // lay flat with meta or ctrl clicking a selected face if (int && (event.ctrlKey || event.metaKey || API.feature.on_face_select)) { let q = new THREE.Quaternion(); q.setFromUnitVectors(int.face.normal, new THREE.Vector3(0,0,-1)); API.selection.rotate(q); } if (API.view.get() !== VIEWS.ARRANGE) return null; return API.selection.meshes(); }); SPACE.mouse.upSelect(function(selection, event) { if (event && event.target.nodeName === "CANVAS") { if (selection) { platform.select(selection.object.widget, event.shiftKey); } else { platform.deselect(); } } else { return API.widgets.meshes(); } }); // SPACE.mouse.onHover(function(d,me) { // if (d) { // // console.log(d,d.object.widget.id,d.face.materialIndex) // // d.object.material.vertexColors = true; // // d.object.material.needsUpdate = true; // // d.face.vertexColors = [ // // new THREE.Color(0xffaa88), // // new THREE.Color(0xffaa88), // // new THREE.Color(0xffaa88) // // ]; // // d.face.materialIndex = 1; // } else { // return API.widgets.meshes(); // } // }); SPACE.mouse.onDrag(function(delta) { let dev = settings().device; let bmaxx = dev.bedWidth/2; let bminx = -bmaxx; let bmaxy = dev.bedDepth/2; let bminy = -bmaxy if (delta && UI.freeLayout.checked) { API.selection.for_widgets(function(widget) { let wbnd = widget.getBoundingBox(); let wwid = wbnd.max.x - wbnd.min.x; let wminx = widget.track.pos.x + delta.x - wwid / 2; let wmaxx = wminx + wwid + delta.x; if (wminx < bminx || wmaxx > bmaxx) return; let whei = wbnd.max.y - wbnd.min.y; let wminy = widget.track.pos.y + delta.y - whei / 2; let wmaxy = wminy + whei + delta.y; if (wminy < bminy || wmaxy > bmaxy) return; widget.move(delta.x, delta.y, 0); API.event.emit('widget.move', {widget, delta}); }); platform.update_stock(); } else { return API.selection.meshes().length > 0; } }); API.space.restore(init_two) || checkSeed(init_two) || init_two(); }; // SECOND STAGE INIT AFTER UI RESTORED function init_two() { let current = settings(), control = current.controller; SPACE.scene.freeze(true); platform.deselect(); CATALOG.addFileListener(updateCatalog); SPACE.view.setZoom(control.reverseZoom, control.zoomSpeed); SPACE.platform.setZOff(0.2); // restore UI state from settings UI.showOrigin.checked = control.showOrigin; UI.freeLayout.checked = control.freeLayout; UI.autoLayout.checked = control.autoLayout; UI.alignTop.checked = control.alignTop; UI.reverseZoom.checked = control.reverseZoom; UI.compact.checked = control.compact; // load script extensions if (SETUP.s) SETUP.s.forEach(function(lib) { let scr = DOC.createElement('script'); scr.setAttribute('defer',true); scr.setAttribute('src','/code/'+lib+'.js'); DOC.body.appendChild(scr); STATS.add('load_'+lib); }); // load CSS extensions if (SETUP.ss) SETUP.ss.forEach(function(style) { style = style.charAt(0) === '/' ? style : `/kiri/style-${style}`; let ss = DOC.createElement('link'); ss.setAttribute("type", "text/css"); ss.setAttribute("rel", "stylesheet"); ss.setAttribute("href", `${style}.css`); DOC.body.appendChild(ss); }); // override stored settings if (SETUP.v) SETUP.v.forEach(function(kv) { kv = kv.split('='); SDB.setItem(kv[0],kv[1]); }); // import octoprint settings if (SETUP.ophost) { let ohost = API.const.OCTO = { host: SETUP.ophost[0], apik: SETUP.opkey ? SETUP.opkey[0] : '' }; SDB['octo-host'] = ohost.host; SDB['octo-apik'] = ohost.apik; console.log({octoprint:ohost}); } // optional set-and-lock mode (hides mode menu) let SETMODE = SETUP.mode ? SETUP.mode[0] : null; // optional set-and-lock device (hides device menu) let DEVNAME = SETUP.dev ? SETUP.dev[0] : null; // setup default mode and enable mode locking, if set API.mode.set(SETMODE || STARTMODE || current.mode, SETMODE); // update everything dependent on the platform size platform.update_size(); // ensure hot keys work even in iframes API.focus(); // place version number a couple of places to help users UI.helpButton.title = `${LANG.version} ` + KIRI.version; // restore expert setting preference UC.setExpert(control.expert); // setup tab visibility watcher // DOC.addEventListener('visibilitychange', function() { document.title = document.hidden }); // ensure settings has gcode selectDevice(DEVNAME || API.device.get(), DEVNAME); // ensure field data propagation API.conf.update(); // load settings provided in url hash loadSettingsFromServer(); // clear alerts as they build up setInterval(API.event.alerts, 1000); // add hide-alerts-on-alert-click UI.alert.dialog.onclick = function() { API.event.alerts(true); }; // default to ARRANGE view mode API.view.set(VIEWS.ARRANGE); // add ability to override API.show.controls(API.feature.controls); // set initial layer slider size API.dialog.update(); // show version on startup API.show.alert(`${LANG.version} ${KIRI.version}`); if (!SETUP.s) console.log(`kiri | init main | ${KIRI.version}`); // send init-done event API.event.emit('init-done', STATS); SPACE.scene.freeze(false); } // if a language needs to load, the script is injected and loaded // first. once this loads, or doesn't, the initialization begins let lang_set = undefined; let lang = SETUP.ln ? SETUP.ln[0] : SDB.getItem('kiri-lang') || KIRI.lang.get(); // inject language script if not english if (lang && lang !== 'en' && lang !== 'en-us') { lang_set = lang; let scr = DOC.createElement('script'); // scr.setAttribute('defer',true); scr.setAttribute('src',`/kiri/lang/${lang}.js`); (DOC.body || DOC.head).appendChild(scr); STATS.set('ll',lang); scr.onload = function() { KIRI.lang.set(lang); init_one(); }; scr.onerror = function(err) { console.log({language_load_error: err, lang}) init_one(); } } // set to browser default which will be overridden // by any future script loads (above) KIRI.lang.set(); // schedule init_one to run after all page content is loaded // unless a languge script is loading first, in which case it // will call init once it's done (or failed) if (!lang_set) { if (document.readyState === 'loading') { SPACE.addEventListener(DOC, 'DOMContentLoaded', init_one); } else { // happens in debug mode when scripts are chain loaded init_one(); } } })();