/** Copyright Stewart Allen -- All Rights Reserved */ "use strict"; (function () { const { data, kiri, moto, noop } = self; const { api, conf, consts, utils } = kiri; const { space } = moto; const { local } = data; const { clone } = Object; const { areEqual, ls2o, js2o } = utils; const localFilterKey ='kiri-gcode-filters'; const localFilters = js2o(local.getItem(localFilterKey)) || []; let settings = clone(conf.template); function normalize(set) { return conf.normalize(set); } function getSettings() { return settings; } function putSettings(newset) { settings = normalize(newset); saveSettings(); api.space.restore(null, true); } function saveSettings() { const { ui } = api; const view = space.view.save(); if (view.left || view.up) { settings.controller.view = view; } const mode = settings.mode; settings.sproc[mode].default = settings.process; settings.sproc[mode][settings.process.processName] = settings.process; settings.device.bedBelt = ui.deviceBelt.checked; settings.device.bedRound = ui.deviceRound.checked; settings.device.originCenter = ui.deviceOrigin.checked || ui.deviceRound.checked; settings.device.fwRetract = ui.fwRetract.checked; local.setItem('ws-settings', JSON.stringify(settings)); api.event.emit('settings.saved', settings); } function loadSettings(evt, named) { let mode = settings.mode, name = evt ? evt.target.getAttribute("load") : named || api.process.get() || "default", load = settings.sproc[mode][name]; if (!load) return; // cloning loaded process into settings requires user to save // process before switching devices or risk losing any changes settings.process = clone(load); // update process name settings.process.processName = name; // save named process with the current device settings.devproc[api.device.get()] = name; // preserve name of last library loaded if (name !== 'default') { settings.cproc[mode] = name; } // allow mode driver to take any necessary actions api.event.emit("settings.load", settings); // update UI fields to reflect current settings api.view.update_fields(); api.conf.update(); // publish event when loading from ui event if (evt) api.event.settings(); } function showSettings() { api.dialog.update_process_list(); api.modal.show("saves"); api.ui.settingsName.focus(); } function updateSettings(opt = {}) { const { controller, device, process, mode, sproc, cproc } = settings; const { uc } = api; updateSettingsFromFields(controller); switch (controller.units) { case 'mm': uc.setUnits(1); break; case 'in': uc.setUnits(25.4); break; } if (opt.controller) { return; } updateSettingsFromFields(device, undefined, undefined, true); // range-specific values if (settings.mode === 'FDM' && api.view.is_slice()) { let changes = {}; let values = process; let { layer_lo, layer_hi, layer_max } = api.var; let range = { lo: layer_lo, hi: layer_hi }; let add = false; if (layer_lo > 0 || layer_hi < layer_max) { values = Object.clone(process); add = true; } updateSettingsFromFields(values, undefined, changes); if (range) { updateRange(range.lo, range.hi, changes, add); } } else { updateSettingsFromFields(process); } if (device.extruders && device.extruders[device.internal]) { updateSettingsFromFields(device.extruders[device.internal]); } api.conf.save(); let compare = sproc[mode][cproc[mode]]; let same = true; if (compare) for (let [key, val] of Object.entries(compare).filter(v => v[0] !== 'processName')) { let tval = process[key]; // outputLoopLayers misbehaving and setting null on empty if (val === '' && tval == null) { continue; } if (Array.isArray(tval) && Array.isArray(val)) { if (JSON.stringify(tval) == JSON.stringify(val)) { continue; } } if (tval != val) { // console.log(key, 'expected', val, 'got', tval); same = false; } } $('mode-device').innerText = device.deviceName; $('mode-profile').innerText = `${cproc[mode]}${same ? '' : ' *'}`; } function updateSettingsFromFields(setrec, uirec = api.ui, changes) { if (!setrec) { return console.trace("missing scope"); } let lastChange = api.uc.lastChange(); // for each key in setrec object for (let key in setrec) { if (!setrec.hasOwnProperty(key)) { // console.log({no_setrec: key}); continue; } if (!uirec.hasOwnProperty(key)) { // console.log({no_uirec: key}); continue; } let nval = null, uie = uirec[key]; // skip empty UI values if (!uie || uie === '') { // console.log({uie_empty: key}); continue; } if (uie.type === 'text') { nval = uirec[key].convert(); } else if (uie.type === 'checkbox') { nval = uirec[key].checked; } else if (uie.type === 'select-one') { if (uie.selectedIndex >= 0) { nval = uie.options[uie.selectedIndex].value; let src = uie.parentNode.getAttribute('source'); if (src === 'tools') { nval = parseInt(nval); } } else { nval = setrec[key]; } } else if (uie.type === 'textarea') { nval = uie.value.trim().split('\n').filter(v => v !== ''); } else { continue; } if (lastChange === uie) { if (changes) changes[key] = nval; } if (!areEqual(setrec[key], nval)) { setrec[key] = nval; if (changes) { changes[key] = nval; } } } return settings; } function restoreSettings(save) { const widgets = api.widgets.all(); const newset = ls2o('ws-settings') || settings; // extract legacy widget annotations into widgets if (newset.widget) { for (let id of Object.keys(newset.widget)) { let anno = newset.widget[id]; let wid = widgets.filter(w => w.id === id)[0]; if (wid && anno) { wid.anno = anno; console.log('transfer settings annotations to widget', id); wid.saveState(); } else { console.log('missing widget for annotations', id); } delete newset.widget[id]; } } settings = normalize(newset); // override camera from settings if (settings.controller.view) { local.removeItem('ws-camera'); } // merge custom filters from localstorage into settings localFilters.forEach(function(fname) { let fkey = "gcode-filter-"+fname, ov = ls2o(fkey); if (ov) settings.devices[fname] = ov; local.removeItem(fkey) }); local.removeItem(localFilterKey); // save updated settings if (save) api.conf.save(); return newset; } function settingsExport(opts = {}) { const widgets = api.widgets.all(); const note = opts.node || undefined; const shot = opts.work || opts.screen ? space.screenshot() : undefined; const work = opts.work ? kiri.codec.encode(widgets,{_json_:true}) : undefined; const view = opts.work ? space.view.save() : undefined; const setn = Object.clone(settings); // stuff in legacy annotations for re-import for (let w of widgets) { setn.widget[w.id] = w.anno; } const xprt = { settings: setn, version: kiri.version, screen: shot, space: space.info, note: note, work: work, view: view, moto: moto.id, init: local.getItem('kiri-init'), time: Date.now() }; return opts.clear ? xprt : api.util.b64enc(xprt); } function settingsImport(data, ask) { const { uc, ui } = api; if (typeof(data) === 'string') { try { data = api.util.b64dec(data); } catch (e) { uc.alert('invalid import format'); console.log('data',data); return; } } if (api.const.LOCAL) console.log('import', data); let isSettings = (data.settings && data.version && data.time); let isProcess = (data.process && data.version && data.time && data.mode && data.name); let isDevice = (data.device && data.version && data.time); let isWork = (data.work); if (!isSettings && !isDevice && !isProcess) { uc.alert('invalid settings or device format'); console.log('data',data); return; } function doit() { if (isDevice) { if (settings.devices[data.device]) { uc.confirm(`Replace device ${data.device}?`).then(yes => { if (yes) { settings.devices[data.device] = data.code; api.show.devices(); } }); } else { settings.devices[data.device] = data.code; api.show.devices(); } } if (isProcess) { if (settings.sproc[data.mode][data.name]) { uc.confirm(`Replace process ${data.name}?`).then(yes => { if (yes) { settings.sproc[data.mode][data.name] = data.process; api.conf.show(); } }); } else { settings.sproc[data.mode][data.name] = data.process; api.conf.show(); } } if (isSettings) { api.space.clear(); settings = normalize(data.settings); local.setItem('ws-settings', JSON.stringify(settings)); if (api.const.LOCAL) console.log('settings', Object.clone(settings)); if (isWork) { api.platform.clear(); kiri.codec.decode(data.work).forEach(widget => { platform.add(widget, 0, true); }); if (data.view) { space.view.load(data.view); } } restoreSettings(); api.space.restore(() => { ui.sync() }, true); } } if (ask) { let opt = {}; let prompt = isDevice ? `Import device "${data.device}"?` : isProcess ? `Import process "${data.name}"?` : `Import settings made in Kiri:Moto version ${data.version} on
${new Date(data.time)}?`; if (data.screen) { opt.pre = [ '
', ``, '
' ]; } uc.confirm(prompt,undefined,undefined,opt).then((yes) => { if (yes) doit(); }); } else { doit(); } } function settingsImportZip(data, ask) { let alert = api.show.alert("Importing Workspace"); JSZip.loadAsync(data).then(zip => { for (let [key,value] of Object.entries(zip.files)) { if (key === "workspace.json") { value.async("string").then(json => { api.hide.alert(alert); settingsImport(JSON.parse(json), ask); }); } } }); } // import and convert prusa ini file function settingsPrusaConvert(data) { const { uc } = api; const map = {}; try { data.split('\n') .filter(l => l.charAt(0) !== '#') .map(l => l.split('=').map(v => v.trim())) .map(l => { // convert gcode string into a string array if (l[0].indexOf('_gcode') > 0) { l[1] = l[1].replaceAll('\\n','\n').split('\n'); } return l; }) .forEach(l => { map[l[0]] = l[1]; }); } catch (e) { return uc.alert('invalid file'); } // device setup let device = Object.clone(conf.defaults.fdm.d); let dname = device.deviceName = map.printer_model; if (dname) { device.mode = "FDM"; device.extruders[0].extNozzle = parseFloat(map.nozzle_diameter); device.gcodePre = map.start_gcode; device.gcodePost = map.end_gcode; device.gcodeLayer = map.layer_gcode || []; device.maxHeight = parseInt(map.max_print_height || device.maxHeight); if (map.bed_shape) { let shape = map.bed_shape.split(',').map(l => l.split('x')); device.bedWidth = parseInt(shape[2][0]); device.bedDepth = parseInt(shape[2][1]); } } // profile setup let process = Object.clone(conf.defaults.fdm.p); let pname = process.processName = map.print_settings_id; if (pname) { process.sliceShells = parseInt(map.perimeters); process.sliceHeight = parseFloat(map.layer_height); process.outputFeedrate = parseInt(map.perimeter_speed); process.outputSeekrate = parseInt(map.travel_speed); process.outputTemp = parseInt(map.temperature); process.outputBedTemp = parseInt(map.bed_temperature); process.sliceTopLayers = parseInt(map.top_solid_layers); process.sliceBottomLayers = parseInt(map.bottom_solid_layers); process.firstSliceHeight = parseFloat(map.first_layer_height); process.firstLayerNozzleTemp = parseInt(map.first_layer_temperature); process.firstLayerRate = ( (parseFloat(map.first_layer_speed) / 100) * process.outputFeedrate); process.firstLayerBedTemp = parseInt(map.first_layer_bed_temperature); process.outputRetractDist = parseFloat(map.retract_length); process.outputRetractSpeed = parseFloat(map.retract_speed); } uc.confirm(`Import "${dname}"?`).then(yes => { if (yes) { // create device, associated profile, set as current and show dialog settings.devices[dname] = device; settings.devproc[dname] = pname; settings.process = settings.sproc.FDM[pname] = process; settings.filter.FDM = dname; settings.cproc.FDM = pname; api.show.devices(); } }); } api.conf = { dbo: () => { return ls2o('ws-settings') }, get: getSettings, put: putSettings, load: loadSettings, save: saveSettings, show: showSettings, update: updateSettings, update_from: updateSettingsFromFields, restore: restoreSettings, export: settingsExport, import: settingsImport, }; api.settings = { get: getSettings, import: settingsImport, import_zip: settingsImportZip, import_prusa: settingsPrusaConvert, dev() { return settings.device }, proc() { return settings.process }, ctrl() { return settings.controller }, mode() { return settings.mode }, }; })();