From 57a2129dba8e5ec8c60bd5d0f24a2f6785b91728 Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Sun, 20 Feb 2022 22:47:04 -0500 Subject: [PATCH] convert bulk of root kiri packages to gapp.register() loading --- app.js | 2 +- src/cli/kiri-source.json | 2 + src/kiri-mode/fdm/export.js | 1 + src/kiri/alerts.js | 2 +- src/kiri/api.js | 2 +- src/kiri/codec.js | 641 ++++++++-------- src/kiri/conf.js | 1408 +++++++++++++++++------------------ src/kiri/consts.js | 10 +- src/kiri/do.js | 316 ++++---- src/kiri/export.js | 1296 ++++++++++++++++---------------- src/kiri/files.js | 418 +++++------ src/kiri/frame.js | 11 +- src/kiri/function.js | 13 +- src/kiri/init.js | 18 +- src/kiri/lang.js | 143 ++-- src/kiri/layers.js | 629 ++++++++-------- src/kiri/main.js | 7 +- src/kiri/pack.js | 149 ++-- src/kiri/platform.js | 11 +- src/kiri/print.js | 23 +- src/kiri/selection.js | 10 +- src/kiri/settings.js | 12 +- src/kiri/slice.js | 184 +++-- src/kiri/stack.js | 446 +++++------ src/kiri/stacks.js | 298 ++++---- src/kiri/stats.js | 9 +- src/kiri/tools.js | 192 ++--- src/kiri/ui.js | 4 +- src/kiri/utils.js | 10 +- src/kiri/widget.js | 10 +- src/kiri/widgets.js | 6 +- web/kiri/index.css | 15 +- web/kiri/index.html | 2 +- 33 files changed, 3181 insertions(+), 3119 deletions(-) diff --git a/app.js b/app.js index 1daa0cde..9a87a335 100644 --- a/app.js +++ b/app.js @@ -365,7 +365,6 @@ const script = { "kiri/stats", "kiri/api", "kiri/ui", - "kiri/do", "kiri/pack", "kiri/lang", "kiri/lang-en", @@ -395,6 +394,7 @@ const script = { "kiri/codec", "kiri/main", "kiri/init", + "kiri/do", "kiri/export", "kiri/tools", "kiri/frame", diff --git a/src/cli/kiri-source.json b/src/cli/kiri-source.json index c5e3d0cc..0b359b32 100644 --- a/src/cli/kiri-source.json +++ b/src/cli/kiri-source.json @@ -22,8 +22,10 @@ "geo/polygon", "geo/gyroid", "geo/slicer", + "data/local", "load/obj", "load/stl", + "kiri/consts", "kiri/conf", "kiri/pack", "kiri/client", diff --git a/src/kiri-mode/fdm/export.js b/src/kiri-mode/fdm/export.js index 29af6982..ea75cb05 100644 --- a/src/kiri-mode/fdm/export.js +++ b/src/kiri-mode/fdm/export.js @@ -8,6 +8,7 @@ const { base, kiri } = self; const { util, config } = base; const { FDM } = kiri.driver; const { getRangeParameters } = FDM; +const debug = false; FDM.export = function(print, online, ondone, ondebug) { const { settings, belty, tools } = print; diff --git a/src/kiri/alerts.js b/src/kiri/alerts.js index 6fa33bc9..f3900af7 100644 --- a/src/kiri/alerts.js +++ b/src/kiri/alerts.js @@ -5,7 +5,7 @@ // dep: kiri.api gapp.register("kiri.alerts", [], (root, exports) => { -const { kiri } = self; +const { kiri } = root; const { api } = kiri; let alerts = []; diff --git a/src/kiri/api.js b/src/kiri/api.js index 9dfe96e5..65e167fc 100644 --- a/src/kiri/api.js +++ b/src/kiri/api.js @@ -9,7 +9,7 @@ // use: kiri.settings gapp.register("kiri.api", [], (root, exports) => { -const { data, kiri, noop } = self; +const { data, kiri, noop } = root; const { consts, utils } = kiri; const { areEqual, parseOpt, encodeOpt, ajax, o2js, js2o } = utils; const { LISTS } = consts; diff --git a/src/kiri/codec.js b/src/kiri/codec.js index 8dfbd56f..f76af5f8 100644 --- a/src/kiri/codec.js +++ b/src/kiri/codec.js @@ -6,387 +6,388 @@ // dep: geo.base gapp.register("kiri.codec", [], (root, exports) => { - const BASE = self.base, KIRI = self.kiri, handlers = {}; - const freeMem = true; - const zeroOut = true; +const { base, kiri } = root; +const handlers = {}; +const freeMem = true; +const zeroOut = true; - const codec = exports({ - undef: undefined, - encode: encode, - decode: decode, - registerDecoder: registerDecoder, - allocFloat32Array: allocFloat32Array, - encodePointArray, - decodePointArray, - toCodable - }); +const codec = exports({ + undef: undefined, + encode: encode, + decode: decode, + registerDecoder: registerDecoder, + allocFloat32Array: allocFloat32Array, + encodePointArray, + decodePointArray, + toCodable +}); - function toCodable(object) { - switch (typeof object) { +function toCodable(object) { + switch (typeof object) { + case 'function': + return undefined; + case 'string': + case 'number': + return object; + case 'object': + if (Array.isArray(object)) { + return object.map(v => toCodable(v)); + } + break; + } + let o = {}; + for (let [key, val] of Object.entries(object)) { + switch (typeof val) { case 'function': - return undefined; - case 'string': - case 'number': - return object; + break; case 'object': - if (Array.isArray(object)) { - return object.map(v => toCodable(v)); + if (Array.isArray(val)) { + val = val.map(v => toCodable(v)); + } else { + val = toCodable(val); } + default: + o[key] = val; break; } - let o = {}; - for (let [key, val] of Object.entries(object)) { - switch (typeof val) { - case 'function': - break; - case 'object': - if (Array.isArray(val)) { - val = val.map(v => toCodable(v)); - } else { - val = toCodable(val); - } - default: - o[key] = val; - break; - } - } - return o; } + return o; +} - function allocFloat32Array(arg) { - if (arg.byteLength) { - // already a float array - return arg; - } - if (Array.isArray(arg)) { - // create float array from array - return new Float32Array(arg); - } - // usually a number (size) for array +function allocFloat32Array(arg) { + if (arg.byteLength) { + // already a float array + return arg; + } + if (Array.isArray(arg)) { + // create float array from array return new Float32Array(arg); } + // usually a number (size) for array + return new Float32Array(arg); +} - function encode(o, state) { - state = state || {}; - if (o === null) return null; - if (o === undefined) return undefined; - if (Array.isArray(o)) { - let arr = new Array(o.length), i=0; - while (i < o.length) arr[i] = encode(o[i++], state); - return arr; - } - switch (typeof(o)) { - case 'string': - case 'number': - return o; - case 'object': - if (o.encode) return o.encode(state); - return genOEncode(o, state); - } - return null; +function encode(o, state) { + state = state || {}; + if (o === null) return null; + if (o === undefined) return undefined; + if (Array.isArray(o)) { + let arr = new Array(o.length), i=0; + while (i < o.length) arr[i] = encode(o[i++], state); + return arr; } - - function decode(o, state) { - state = state || {}; - if (o === null) return null; - if (o === undefined) return undefined; - if (Array.isArray(o)) { - for (let i=0; i < o.length; i++) o[i] = decode(o[i],state); + switch (typeof(o)) { + case 'string': + case 'number': return o; - } - switch (typeof(o)) { - case 'string': - case 'number': - return o; - case 'object': - if (o.type && handlers[o.type]) return handlers[o.type](o,state); - return genODecode(o, state); - } - return null; + case 'object': + if (o.encode) return o.encode(state); + return genOEncode(o, state); } + return null; +} - function registerDecoder(type, handler) { - handlers[type] = handler; +function decode(o, state) { + state = state || {}; + if (o === null) return null; + if (o === undefined) return undefined; + if (Array.isArray(o)) { + for (let i=0; i < o.length; i++) o[i] = decode(o[i],state); + return o; } - - function genOEncode(o, state) { - if (o instanceof Float32Array) return o; - let out = {}; - for (let k in o) { - if (o.hasOwnProperty(k)) out[k] = encode(o[k], state); - } - return out; + switch (typeof(o)) { + case 'string': + case 'number': + return o; + case 'object': + if (o.type && handlers[o.type]) return handlers[o.type](o,state); + return genODecode(o, state); } + return null; +} - function genODecode(o, state) { - if (o instanceof Float32Array) return o; - let out = {}; - for (let k in o) { - if (o.hasOwnProperty(k)) out[k] = decode(o[k], state); - } - return out; +function registerDecoder(type, handler) { + handlers[type] = handler; +} + +function genOEncode(o, state) { + if (o instanceof Float32Array) return o; + let out = {}; + for (let k in o) { + if (o.hasOwnProperty(k)) out[k] = encode(o[k], state); } + return out; +} - /** ****************************************************************** - * Object Class CODEC Functions - ******************************************************************* */ +function genODecode(o, state) { + if (o instanceof Float32Array) return o; + let out = {}; + for (let k in o) { + if (o.hasOwnProperty(k)) out[k] = decode(o[k], state); + } + return out; +} - KIRI.Widget.prototype.encode = function(state) { - const json = state._json_; - const geo = this.getGeoVertices(); - const coded = { - type: 'widget', - id: this.id, - ver: 1, // for better future encodings - json: json, // safe for JSON (float32array mess) - group: this.group.id, - track: this.track, - geo: json ? Array.from(geo) : geo - }; - return coded; +/** ****************************************************************** + * Object Class CODEC Functions + ******************************************************************* */ + + kiri.Widget.prototype.encode = function(state) { + const json = state._json_; + const geo = this.getGeoVertices(); + const coded = { + type: 'widget', + id: this.id, + ver: 1, // for better future encodings + json: json, // safe for JSON (float32array mess) + group: this.group.id, + track: this.track, + geo: json ? Array.from(geo) : geo }; + return coded; + }; - registerDecoder('widget', function(v, state) { - const id = v.id, - group = v.group || id, - track = v.track || undefined, - widget = KIRI.newWidget(id, KIRI.Widget.Groups.forid(group)); - widget.loadVertices(v.json ? v.geo.toFloat32() : v.geo); - widget.saved = Date.now(); - if (track && track.pos) { - widget.track = track; - widget.move(track.pos.x, track.pos.y, track.pos.z, true); - } - return widget; - }); + registerDecoder('widget', function(v, state) { + const id = v.id, + group = v.group || id, + track = v.track || undefined, + widget = kiri.newWidget(id, kiri.Widget.Groups.forid(group)); + widget.loadVertices(v.json ? v.geo.toFloat32() : v.geo); + widget.saved = Date.now(); + if (track && track.pos) { + widget.track = track; + widget.move(track.pos.x, track.pos.y, track.pos.z, true); + } + return widget; +}); - KIRI.Slice.prototype.encode = function(state) { - const rv = { - type: 'slice', - z: this.z, - index: this.index, - layers: encode(this.layers, state) - }; - // aggressively free memory - if (freeMem) this.layers = undefined; - return rv; +kiri.Slice.prototype.encode = function(state) { + const rv = { + type: 'slice', + z: this.z, + index: this.index, + layers: encode(this.layers, state) }; + // aggressively free memory + if (freeMem) this.layers = undefined; + return rv; +}; - registerDecoder('slice', function(v, state) { - let slice = KIRI.newSlice(v.z, state.mesh ? state.mesh.newGroup() : null); +registerDecoder('slice', function(v, state) { + let slice = kiri.newSlice(v.z, state.mesh ? state.mesh.newGroup() : null); - slice.index = v.index; - slice.layers = decode(v.layers, state) + slice.index = v.index; + slice.layers = decode(v.layers, state) - return slice; - }); + return slice; +}); - KIRI.Layers.prototype.encode = function(state) { - let enc = { - type: 'layers', - layers: Object.keys(this.layers), - data: Object.values(this.layers).map(layer => { - const e = { - polys: encode(layer.polys, state), - lines: encodePointArray(layer.lines, state), - faces: codec.allocFloat32Array(layer.faces), - cface: layer.cface || codec.undef, - color: layer.color, - paths: layer.paths.map(lp => { - const pe = { - z: lp.z, - index: lp.index, - faces: codec.allocFloat32Array(lp.faces) - }; - if (zeroOut && state.zeros && pe.faces.length) { - state.zeros.push(pe.faces.buffer); - } - return pe; - }), - cpath: layer.cpath || codec.undef, - off: layer.off - }; - if (zeroOut && state.zeros && e.faces.length) { - state.zeros.push(e.faces.buffer); - } - // console.log('-->',layer,e); - return e; - }) - }; - // aggressively free memory - if (freeMem) this.init(); - return enc; - }; - - registerDecoder('layers', function(v, state) { - let render = new KIRI.Layers(); - for (let i=0; i { - return { +kiri.Layers.prototype.encode = function(state) { + let enc = { + type: 'layers', + layers: Object.keys(this.layers), + data: Object.values(this.layers).map(layer => { + const e = { + polys: encode(layer.polys, state), + lines: encodePointArray(layer.lines, state), + faces: codec.allocFloat32Array(layer.faces), + cface: layer.cface || codec.undef, + color: layer.color, + paths: layer.paths.map(lp => { + const pe = { z: lp.z, index: lp.index, faces: codec.allocFloat32Array(lp.faces) }; + if (zeroOut && state.zeros && pe.faces.length) { + state.zeros.push(pe.faces.buffer); + } + return pe; }), - cpath: v.data[i].cpath, - off: v.data[i].off + cpath: layer.cpath || codec.undef, + off: layer.off }; - // fixup null -> Infinity in material counts (JSON stringify sucks) - if (d.cface) { - d.cface.forEach(rec => { - if (rec.count === null) { - rec.count = Infinity; - } - }); + if (zeroOut && state.zeros && e.faces.length) { + state.zeros.push(e.faces.buffer); } - if (d.cpoly) { - d.cpoly.forEach(rec => { - if (rec.count === null) { - rec.count = Infinity; - } - }); - } - if (d.cpath) { - d.cpath.forEach(rec => { - if (rec.count === null) { - rec.count = Infinity; - } - }); - } - // console.log('<--',d); - } - return render; - }); - - KIRI.Top.prototype.encode = function(state) { - let obj = { - type: 'top', - poly: encode(this.poly, state) - }; - if (state.full) { - obj.last = encode(this.last, state); - obj.shells = encode(this.shells, state); - obj.fill_off = encode(this.fill_off, state); - obj.fill_lines = encode(this.fill_lines, state); - } - return obj; + // console.log('-->',layer,e); + return e; + }) }; + // aggressively free memory + if (freeMem) this.init(); + return enc; +}; - registerDecoder('top', function(v, state) { - let top = KIRI.newTop(decode(v.poly, state)); - if (state.full) { - top.last = decode(v.last, state); - top.shells = decode(v.shells, state); - top.fill_off = decode(v.fill_off, state); - top.fill_lines = decode(v.fill_lines, state); +registerDecoder('layers', function(v, state) { + let render = new kiri.Layers(); + for (let i=0; i { + return { + z: lp.z, + index: lp.index, + faces: codec.allocFloat32Array(lp.faces) + }; + }), + cpath: v.data[i].cpath, + off: v.data[i].off + }; + // fixup null -> Infinity in material counts (JSON stringify sucks) + if (d.cface) { + d.cface.forEach(rec => { + if (rec.count === null) { + rec.count = Infinity; + } + }); } - return top; + if (d.cpoly) { + d.cpoly.forEach(rec => { + if (rec.count === null) { + rec.count = Infinity; + } + }); + } + if (d.cpath) { + d.cpath.forEach(rec => { + if (rec.count === null) { + rec.count = Infinity; + } + }); + } + // console.log('<--',d); + } + return render; +}); + +kiri.Top.prototype.encode = function(state) { + let obj = { + type: 'top', + poly: encode(this.poly, state) + }; + if (state.full) { + obj.last = encode(this.last, state); + obj.shells = encode(this.shells, state); + obj.fill_off = encode(this.fill_off, state); + obj.fill_lines = encode(this.fill_lines, state); + } + return obj; +}; + +registerDecoder('top', function(v, state) { + let top = kiri.newTop(decode(v.poly, state)); + if (state.full) { + top.last = decode(v.last, state); + top.shells = decode(v.shells, state); + top.fill_off = decode(v.fill_off, state); + top.fill_lines = decode(v.fill_lines, state); + } + return top; +}); + +function encodePointArray(points, state, z) { + if (!points) return null; + + let array = codec.allocFloat32Array(points.length * 3), + pos = 0; + + points.forEach(function(point) { + if (state.rotate) { + if (state.centerz) { + point.z -= state.centerz; + } + let v = new THREE.Vector3(point.x, point.y, point.z); + v.applyMatrix4(state.rotate); + point = {x: v.x, y: v.y, z: v.z}; + if (state.centerz) { + point.z += state.centerz; + } + if (state.movez) { + point.z -= state.movez; + } + } + array[pos++] = point.x; + array[pos++] = point.y; + array[pos++] = z !== undefined ? z : point.z; }); - function encodePointArray(points, state, z) { - if (!points) return null; - - let array = codec.allocFloat32Array(points.length * 3), - pos = 0; - - points.forEach(function(point) { - if (state.rotate) { - if (state.centerz) { - point.z -= state.centerz; - } - let v = new THREE.Vector3(point.x, point.y, point.z); - v.applyMatrix4(state.rotate); - point = {x: v.x, y: v.y, z: v.z}; - if (state.centerz) { - point.z += state.centerz; - } - if (state.movez) { - point.z -= state.movez; - } - } - array[pos++] = point.x; - array[pos++] = point.y; - array[pos++] = z !== undefined ? z : point.z; - }); - - if (zeroOut && state.zeros) { - state.zeros.push(array.buffer); - } - - return array; + if (zeroOut && state.zeros) { + state.zeros.push(array.buffer); } - function decodePointArray(array) { - if (!array) return null; + return array; +} - let vid = 0, - pid = 0, - points = new Array(array.length/3); +function decodePointArray(array) { + if (!array) return null; - while (vid < array.length) { - points[pid++] = BASE.newPoint(array[vid++], array[vid++], array[vid++]); - } + let vid = 0, + pid = 0, + points = new Array(array.length/3); - return points; + while (vid < array.length) { + points[pid++] = base.newPoint(array[vid++], array[vid++], array[vid++]); } - BASE.Polygon.prototype.encode = function(state) { - if (!state.poly) state.poly = {}; + return points; +} - let cached = state.poly[this.id]; +base.Polygon.prototype.encode = function(state) { + if (!state.poly) state.poly = {}; - if (cached) { - return { type: 'poly', ref: this.id }; - } + let cached = state.poly[this.id]; - state.poly[this.id] = this; - return { - type: 'poly', - id: this.id, - array: encodePointArray(this.points, state, this.z), - inner: encode(this.inner, state), - parent: encode(this.parent, state), - depth: this.depth, - color: this.color, - open: this.open - }; + if (cached) { + return { type: 'poly', ref: this.id }; + } + + state.poly[this.id] = this; + return { + type: 'poly', + id: this.id, + array: encodePointArray(this.points, state, this.z), + inner: encode(this.inner, state), + parent: encode(this.parent, state), + depth: this.depth, + color: this.color, + open: this.open }; +}; - registerDecoder('poly', function(v, state) { - if (!state.poly) state.poly = {}; +registerDecoder('poly', function(v, state) { + if (!state.poly) state.poly = {}; - if (v.ref) return state.poly[v.ref]; + if (v.ref) return state.poly[v.ref]; - let poly = BASE.newPolygon(), - vid = 0; + let poly = base.newPolygon(), + vid = 0; - // if passed a normal array, convert to float32 - if (v.array.toFloat32) { - v.array = v.array.toFloat32(); - } + // if passed a normal array, convert to float32 + if (v.array.toFloat32) { + v.array = v.array.toFloat32(); + } - while (vid < v.array.length) { - poly.push(BASE.newPoint(v.array[vid++], v.array[vid++], v.array[vid++])); - } + while (vid < v.array.length) { + poly.push(base.newPoint(v.array[vid++], v.array[vid++], v.array[vid++])); + } - poly.id = v.id; - poly.open = v.open; + poly.id = v.id; + poly.open = v.open; - state.poly[v.id] = poly; + state.poly[v.id] = poly; - poly.inner = decode(v.inner, state); - poly.parent = decode(v.parent, state); - poly.depth = v.depth; - poly.color = v.color; + poly.inner = decode(v.inner, state); + poly.parent = decode(v.parent, state); + poly.depth = v.depth; + poly.color = v.color; - return poly; - }); + return poly; +}); }); diff --git a/src/kiri/conf.js b/src/kiri/conf.js index 9a3ff6b7..4a13af17 100644 --- a/src/kiri/conf.js +++ b/src/kiri/conf.js @@ -2,723 +2,723 @@ "use strict"; +// dep: data.local gapp.register("kiri.conf", [], (root, exports) => { - const { kiri } = root; +const { data } = root; +const { local } = data; +const { clone } = Object; +const CVER = 185; - const CVER = 185, clone = Object.clone; - - function genID() { - while (true) { - let k = Math.round(Math.random() * 9999999999).toString(36); - if (k.length >= 4 && k.length <= 8) return k; - } +function genID() { + while (true) { + let k = Math.round(Math.random() * 9999999999).toString(36); + if (k.length >= 4 && k.length <= 8) return k; } +} - // add fields to o(bject) from d(efault) that are missing - // remove fields from o(bject) that don't exist in d(efault) - function fill_cull_once(obj, def, debug) { - if (!obj) return; - // handle renaming - for (let k in obj) { - if (obj.hasOwnProperty(k)) { - let nam = renamed[k] || k; - if (nam !== k) { - // handle field renames - obj[nam] = obj[k]; - if (debug) console.log({rename: k, to: nam}); - delete obj[k]; - } - } - } - // fill missing - for (let k in def) { - if (def.hasOwnProperty(k)) { - let okv = obj[k]; - if ((okv === undefined || okv === null)) { - // handle fill - if (debug) console.log({fill: k, val: def[k]}); - if (typeof def[k] === 'object') { - obj[k] = clone(def[k]); - } else { - obj[k] = def[k]; - } - } - } - } - // remove invalid - for (let k in obj) { - if (!def.hasOwnProperty(k)) { - if (debug) console.log({cull: k}); +// add fields to o(bject) from d(efault) that are missing +// remove fields from o(bject) that don't exist in d(efault) +function fill_cull_once(obj, def, debug) { + if (!obj) return; + // handle renaming + for (let k in obj) { + if (obj.hasOwnProperty(k)) { + let nam = renamed[k] || k; + if (nam !== k) { + // handle field renames + obj[nam] = obj[k]; + if (debug) console.log({rename: k, to: nam}); delete obj[k]; } } } - - function fill_cull_many(map, def) { - forValues(map, (obj) => { fill_cull_once(obj, def) }); - } - - function objectMap(o, fn) { - for (let [key, val] of Object.entries(o)) { - o[key] = fn(val) || val; - } - } - - function valueOf(val, dv) { - if (typeof(val) === 'string' && Array.isArray(dv)) { - val = [val]; - } - return typeof(val) !== 'undefined' ? val : dv; - } - - function forValues(o, fn) { - Object.values(o).forEach(v => fn(v)); - } - - function device_v1_to_v2(device) { - if (device && device.filamentSize) { - device.extruders = [{ - extFilament: device.filamentSize, - extNozzle: device.nozzleSize, - extSelect: ["T0"], - extDeselect: [], - extOffsetX: 0, - extOffsetY: 0 - }]; - delete device.filamentSize; - delete device.nozzleSize; - } - } - - // convert default filter (from server) into device structure - function device_from_code(code,mode) { - // presence of internal field indicates already converted - if (code.internal >= 0) return code; - // if (self.navigator) console.log({mode, convert: code}); - let API = kiri.api, - cmd = code.cmd || {}, - set = code.settings || {}, - ext = code.extruders; - - // currently causes unecessary fills and culls because - // it's not mode and device type sensitive - let device = { - noclone: valueOf(code.no_clone, false), - mode: mode || code.mode || '', - internal: 0, - imageURL: valueOf(set.image_url, ""), - imageScale: valueOf(set.image_scale, 0.75), - imageAnchor: valueOf(set.image_anchor, 0), - bedHeight: valueOf(set.bed_height, 2.5), - bedWidth: valueOf(set.bed_width, 300), - bedDepth: valueOf(set.bed_depth, 175), - bedRound: valueOf(set.bed_circle, false), - bedBelt: valueOf(set.bed_belt, false), - resolutionX: valueOf(set.resolution_x, 1600), - resolutionY: valueOf(set.resolution_y, 900), - deviceZMax: valueOf(set.z_move_max, 0), - maxHeight: valueOf(set.build_height, 150), - originCenter: valueOf(set.origin_center, false), - extrudeAbs: valueOf(set.extrude_abs, false), - spindleMax: valueOf(set.spindle_max, 0), - gcodeFan: valueOf(cmd.fan_power || code.fan_power, []), - gcodeTrack: valueOf(cmd.progress || code.progress, []), - gcodeLayer: valueOf(cmd.layer || code.layer, []), - gcodePre: valueOf(code.pre, []), - gcodePost: valueOf(code.post, []), - // post processor script of which only one exists - // for XYZ.daVinci.Mini.w triggered in kiri.export - // in the fdm driver to turn gcode into base64 - gcodeProc: valueOf(code.proc, ''), - gcodeDwell: valueOf(code.dwell, []), - gcodeSpindle: valueOf(code.spindle || cmd.spindle, []), - gcodeChange: valueOf(code['tool-change'], []), - gcodeFExt: valueOf(code['file-ext'], 'gcode'), - gcodeSpace: valueOf(code['token-space'], true), - gcodeStrip: valueOf(code['strip-comments'], false), - gcodeLaserOn: valueOf(code['laser-on'], []), - gcodeLaserOff: valueOf(code['laser-off'], []), - extruders: [] - }; - - if (ext) { - // synthesize extruders from new style settings - ext.forEach(rec => { - let e = API.clone(conf.defaults.fdm.d.extruders[0]); - if (rec.nozzle) e.extNozzle = rec.nozzle; - if (rec.filament) e.extFilament = rec.filament; - if (rec.offset_x) e.extOffsetX = rec.offset_x; - if (rec.offset_y) e.extOffsetY = rec.offset_y; - if (rec.select) e.extSelect = rec.select; - if (rec.deselect) e.extDeselect = rec.deselect; - device.extruders.push(e); - }); - } else { - // synthesize extruders from old style settings - device.extruders = [API.clone(conf.defaults.fdm.d.extruders[0])]; - device.extruders[0].extNozzle = valueOf(set.nozzle_size, 0.4); - device.extruders[0].extFilament = valueOf(set.filament_diameter, 1.75); - } - - return device; - } - - // ensure settings structure is up-to-date - function normalize(settings) { - let API = kiri.api, - defaults = conf.defaults, - template = conf.template, - mode = settings.mode.toLowerCase(), - default_dev = defaults[mode].d, - default_pro = defaults[mode].p; - - // v1 to v2 changed FDM extruder / nozzle / filament structure - if (settings.ver != CVER) { - // backup settings before upgrade - API.sdb.setItem(`ws-settings-${Date.now()}`, JSON.stringify(settings)); - device_v1_to_v2(settings.device); - device_v1_to_v2(settings.cdev.FDM); - objectMap(settings.devices, dev => { - return dev ? device_from_code(dev) : dev; - }); - settings.ver = CVER; - } - - // fixup old/new detail settings - let detail = settings.controller.detail; - settings.controller.detail = { - "best": "100", - "good": "75", - "fair": "50", - "poor": "25" - }[detail] || detail; - - fill_cull_once(settings, template); - fill_cull_once(settings.device, default_dev); - fill_cull_once(settings.process, default_pro); - fill_cull_once(settings.cdev, template.cdev); - fill_cull_once(settings.cproc, template.cproc); - fill_cull_once(settings.sproc, template.sproc); - fill_cull_once(settings.defaults, template.defaults); - fill_cull_once(settings.cdev.FDM, defaults.fdm.d); - fill_cull_once(settings.cdev.SLA, defaults.sla.d); - fill_cull_once(settings.cdev.CAM, defaults.cam.d); - fill_cull_once(settings.cdev.LASER, defaults.laser.d); - fill_cull_many(settings.sproc.FDM, defaults.fdm.p); - fill_cull_many(settings.sproc.SLA, defaults.sla.p); - fill_cull_many(settings.sproc.CAM, defaults.cam.p); - fill_cull_many(settings.sproc.LASER, defaults.laser.p); - fill_cull_once(settings.controller, template.controller); - - return settings; - } - - // auto field renaming on import - const renamed = { - roughingTool: "camRoughTool", - roughingSpindle: "camRoughSpindle", - roughingDown: "camRoughDown", - roughingOver: "camRoughOver", - roughingSpeed: "camRoughSpeed", - roughingPlunge: "camRoughPlunge", - roughingStock: "camRoughStock", - roughingPocket: "camRoughVoid", - roughingOn: "camRoughOn", - finishingTool: "camOutlineTool", - finishingSpindle: "camOutlineSpindle", - finishingDown: "camOutlineDown", - finishingOver: "camContourOver", - finishingSpeed: "camOutlineSpeed", - finishingPlunge: "camOutlinePlunge", - finishingOn: "camOutlineOn", - finishingXOn: "camContourXOn", - finishingYOn: "camContourYOn", - drillTool: "camDrillTool", - drillSpindle: "camDrillSpindle", - drillDownSpeed: "camDrillDownSpeed", - drillDown: "camDrillDown", - drillDwell: "camDrillDwell", - drillLift: "camDrillLift", - drillingOn: "camDrillingOn", - camPocketOnlyFinish: "camOutlinePocket", - camWideCutout: "camOutlineWide", - outputClockwise: "camConventional" - }; - - const conf = exports({ - // --------------- helper functions - normalize, - device_from_code, - fill_cull_once, - // --------------- device and process defaults - defaults: { - fdm:{ - // device defaults FDM:Device - d:{ - new: true, - mode: "", - deviceName: "", - imageURL: "", - internal: 0, - bedWidth: 300, - bedDepth: 175, - bedHeight: 2.5, - bedRound: false, - bedBelt: false, - fwRetract: false, - filamentSource: "direct", - originCenter: false, - deviceZMax: 0, - maxHeight: 150, - gcodePre: [], - gcodePost: [], - gcodeProc: "", - gcodeFan: [], - gcodeTrack: [], - gcodeLayer: [], - gcodeFExt: "", - extruders:[{ - extFilament: 1.75, - extNozzle: 0.4, - extSelect: ["T0"], - extDeselect: [], - extOffsetX: 0, - extOffsetY: 0 - }], - // other stored info like palette3 config - extras: {} - }, - // process defaults FDM:Process - p:{ - processName: "default", - sliceHeight: 0.25, - sliceShells: 3, - sliceShellOrder: "in-out", - sliceLayerStart: "last", - sliceLineWidth: 0, - sliceFillAngle: 45, - sliceFillWidth: 1, - sliceFillOverlap: 0.35, - sliceFillSparse: 0.25, - sliceFillRepeat: 1, - sliceFillRate: 0, - sliceFillType: "hex", - sliceSupportDensity: 0.25, - sliceSupportOffset: 1.0, - sliceSupportGap: 1, - sliceSupportSize: 5, - sliceSupportArea: 0.1, - sliceSupportSpan: 5, - sliceSupportExtra: 0, - sliceSupportAngle: 50, - sliceSupportNozzle: 0, - sliceSupportEnable: false, - sliceSupportOutline: true, - sliceSolidMinArea: 1, - sliceSolidLayers: 3, - sliceBottomLayers: 3, - sliceTopLayers: 3, - firstSliceHeight: 0.25, - firstLayerRate: 30, - firstLayerFillRate: 35, - firstLayerPrintMult: 1.0, - firstLayerLineMult: 1.0, - firstLayerYOffset: 0, - firstLayerNozzleTemp: 0, - firstLayerBedTemp: 0, - firstLayerBrim: 0, - firstLayerBrimIn: 0, - firstLayerBrimTrig: 0, - firstLayerBrimGap: 0, - firstLayerBeltLead: 3, - firstLayerBeltBump: 0, - firstLayerFanSpeed: 0, - firstLayerFlatten: 0, - outputRaft: false, - outputRaftSpacing: 0.2, - outputDraftShield: false, - outputTemp: 200, - outputBedTemp: 60, - outputFeedrate: 50, - outputFinishrate: 50, - outputSeekrate: 80, - outputShellMult: 1.25, - outputFillMult: 1.25, - outputSparseMult: 1.25, - outputFanSpeed: 255, - outputRetractDist: 1.5, - outputRetractSpeed: 40, - outputRetractWipe: 0, - outputRetractDwell: 20, - outputBrimCount: 2, - outputBrimOffset: 2, - outputShortPoly: 100.0, - outputMinSpeed: 10.0, - outputCoastDist: 0, - outputPurgeTower: 0, - outputBeltFirst: false, - outputAvoidGaps: true, - outputLayerRetract: false, - outputOriginCenter: true, - outputLoops: 0, - outputInvertX: false, - outputInvertY: false, - detectThinWalls: false, - sliceMinHeight: 0, - sliceAdaptive: false, - zHopDistance: 0.2, - arcTolerance: 0, - antiBacklash: 1, - ranges: [] - } - }, - sla:{ - // device defaults SLA:Device - d:{ - new: true, - mode: "", - deviceName: "", - imageURL: "", - noclone: false, - internal: 0, - bedWidth: 150, - bedDepth: 150, - bedHeight: 1.5, - maxHeight: 150, - resolutionX: 1600, - resolutionY: 900 - }, - // process defaults SLA:Process - p:{ - processName: "default", - slaSlice: 0.05, - slaShell: 0.00, - slaOpenTop: false, - slaOpenBase: false, - slaAntiAlias: 1, - slaLayerOff: 0.1, - slaLayerOn: 7, - slaPeelDist: 6, - slaPeelLiftRate: 1.5, - slaPeelDropRate: 3, - slaBaseLayers: 5, - slaBaseOff: 0.1, - slaBaseOn: 30, - slaBasePeelDist: 6, - slaBasePeelLiftRate: 1.5, - slaFillDensity: 0, - slaFillLine: 0.5, - slaFirstOffset: 0, - slaSupportLayers: 10, - slaSupportDensity: 0.5, - slaSupportSize: 0.6, - slaSupportPoints: 4, - slaSupportGap: 10, - slaSupportEnable: false - } - }, - cam:{ - // device defaults CAM:Device - d:{ - new: true, - mode: "", - deviceName: "", - imageURL: "", - internal: 0, - bedWidth: 300, - bedDepth: 175, - bedHeight: 2.5, - originCenter: false, - spindleMax: 0, - gcodePre: [], - gcodePost: [], - gcodeFExt: "", - gcodeSpace: true, - gcodeStrip: true, - gcodeDwell: ["G4 P{time}"], - gcodeChange: ["M6 T{tool}"], - gcodeSpindle: ["M3 S{speed}"] - }, - // process defaults CAM:Process - p:{ - processName: "default", - camLevelTool: 1000, - camLevelSpindle: 1000, - camLevelOver: 0.75, - camLevelSpeed: 1000, - camLevelDown: 0, - camRoughTool: 1000, - camRoughSpindle: 1000, - camRoughDown: 2, - camRoughOver: 0.4, - camRoughSpeed: 1000, - camRoughPlunge: 250, - camRoughStock: 0, - camRoughVoid: false, - camRoughFlat: true, - camRoughTop: false, - camRoughIn: true, - camRoughOn: true, - camOutlineTool: 1000, - camOutlineSpindle: 1000, - camOutlineDown: 3, - camOutlineOver: 0.4, - camOutlineSpeed: 800, - camOutlinePlunge: 250, - camOutlineWide: false, - camOutlineDogbone: false, - camOutlineOmitThru: false, - camOutlineOut: true, - camOutlineIn: false, - camOutlineOn: true, - camContourTool: 1000, - camContourSpindle: 1000, - camContourOver: 0.5, - camContourSpeed: 1000, - camContourAngle: 85, - camContourCurves: false, - camContourIn: false, - camContourXOn: true, - camContourYOn: true, - camTraceTool: 1000, - camTraceSpindle: 1000, - camTraceType: "follow", - camTraceOver: 0.5, - camTraceDown: 0, - camTraceSpeed: 250, - camTracePlunge: 200, - camTraceLines: false, - camDrillTool: 1000, - camDrillSpindle: 1000, - camDrillDownSpeed: 250, - camDrillDown: 5, - camDrillDwell: 250, - camDrillLift: 2, - camDrillMark: false, - camDrillingOn: false, - camRegisterSpeed: 1000, - camFlipAxis: "X", - camFlipOther: "", - // camDrillReg: "none", - camTabsWidth: 5, - camTabsHeight: 5, - camTabsDepth: 5, - camTabsMidline: false, - camDepthFirst: false, - camEaseDown: false, - camOriginTop: true, - camZAnchor: "top", - camZOffset: 0, - camZBottom: 0, - camZClearance: 1, - camZThru: 0, - camFastFeed: 6000, - camFastFeedZ: 300, - camTolerance: 0, - camStockX: 5, - camStockY: 5, - camStockZ: 0, - camStockOffset: true, - camStockClipTo: false, - camStockOn: true, - camConventional: false, // outputClockwise - outputOriginCenter: false, - outputInvertX: false, - outputInvertY: false, - camExpertFast: false, - camTrueShadow: false, - ops: [], // current ops - op2: [] // flip ops - } - }, - laser: { - // device defaults Laser:Device - d:{ - new: true, - mode: "", - deviceName: "", - imageURL: "", - internal: 0, - bedWidth: 300, - bedDepth: 175, - bedHeight: 2.5, - gcodePre: [], - gcodePost: [], - gcodeFExt: "", - gcodeSpace: true, - gcodeLaserOn: ["M106 S{power}"], - gcodeLaserOff: ["M107"] - }, - // process defaults Laser:Process - p:{ - processName: "default", - laserOffset: 0.25, - laserSliceHeight: 1, - laserSliceHeightMin: 0, - laserSliceSingle: false, - outputTileSpacing: 1, - outputLaserPower: 100, - outputLaserSpeed: 1000, - outputLaserGroup: true, - outputLaserZColor: false, - outputLaserLayer: false, - outputLaserStack: false, - outputLaserMerged: false, - outputOriginCenter: true, - outputInvertX: false, - outputInvertY: false, - outputKnifeDepth: 1, - outputKnifePasses: 1, - outputKnifeTip: 2, - knifeOn: false + // fill missing + for (let k in def) { + if (def.hasOwnProperty(k)) { + let okv = obj[k]; + if ((okv === undefined || okv === null)) { + // handle fill + if (debug) console.log({fill: k, val: def[k]}); + if (typeof def[k] === 'object') { + obj[k] = clone(def[k]); + } else { + obj[k] = def[k]; } } - }, - // --------------- settings template - template: { - bounds: {}, - // CAM only - origin: {}, - stock: {}, - tools:[ - { - id: 1000, - number: 1, - type: "endmill", - name: "end 1/4", - metric: false, - shaft_diam: 0.25, - shaft_len: 1, - flute_diam: 0.25, - flute_len: 2, - // taper_angle: 70, - taper_tip: 0, - }, - { - id: 1001, - number: 2, - type: "endmill", - name: "end 1/8", - metric: false, - shaft_diam: 0.125, - shaft_len: 1, - flute_diam: 0.125, - flute_len: 1.5, - // taper_angle: 70, - taper_tip: 0, - }, - { - id: 1002, - number: 3, - type: "endmill", - name: "end 1/16", - metric: false, - shaft_diam: 0.0625, - shaft_len: 1, - flute_diam: 0.0625, - flute_len: 1.5, - // taper_angle: 70, - taper_tip: 0, - } - ], - // currently selected device - device:{}, - // currently selected process - process:{}, - // current process name by mode - cproc:{ - FDM: "default", - SLA: "default", - CAM: "default", - LASER: "default" - }, - // stored processes by mode - sproc:{ - FDM: {}, - SLA: {}, - CAM: {}, - LASER: {} - }, - // current device name by mode - filter:{ - FDM: "Any.Generic.Marlin", - SLA: "Anycubic.Photon", - CAM: "Any.Generic.Grbl", - LASER: "Any.Generic.Laser" - }, - // stored device by mode - cdev: { - FDM: null, - SLA: null, - CAM: null, - LASER: null - }, - // custom devices by name (all modes) - devices:{}, - // favorited devices (all modes) - favorites:{}, - // map of device to last process setting (name) - devproc: {}, - controller:{ - view: null, - dark: false, - decals: true, - danger: false, - zoomSpeed: 1.0, - lineType: "path", - autoSave: true, - antiAlias: true, - reverseZoom: true, - showOrigin: false, - showRulers: true, - showSpeeds: true, - freeLayout: true, - autoLayout: true, - spaceRandoX: false, - spaceLayout: 1, - units: "mm", - exportOcto: false, - exportGhost: false, - exportLocal: false, - exportThumb: false, - exportPreview: false, - decimate: true, - detail: "75", - animesh: "200", - healMesh: false, - threaded: true, - assembly: false, - ortho: false, - devel: false - }, - // label state preferences - labels: { - 'FDM-3-engage': false, - 'FDM-3-retract': false, - 'FDM-3-arrows': false, - 'FDM-3-move': false - }, - // for passing temporary slice hints (topo currently) - synth: {}, - // widget extra info for slicing (extruder mapping) - widget: {}, - mode: 'FDM', - id: genID(), - ver: CVER } - }); + } + // remove invalid + for (let k in obj) { + if (!def.hasOwnProperty(k)) { + if (debug) console.log({cull: k}); + delete obj[k]; + } + } +} - const settings = conf.template; +function fill_cull_many(map, def) { + forValues(map, (obj) => { fill_cull_once(obj, def) }); +} - // seed defaults. will get culled on save - settings.sproc.FDM.default = clone(settings.process); - settings.sproc.SLA.default = clone(settings.process); - settings.sproc.CAM.default = clone(settings.process); - settings.sproc.LASER.default = clone(settings.process); - settings.cdev.FDM = clone(settings.device); - settings.cdev.SLA = clone(settings.device); - settings.cdev.CAM = clone(settings.device); - settings.cdev.LASER = clone(settings.device); +function objectMap(o, fn) { + for (let [key, val] of Object.entries(o)) { + o[key] = fn(val) || val; + } +} + +function valueOf(val, dv) { + if (typeof(val) === 'string' && Array.isArray(dv)) { + val = [val]; + } + return typeof(val) !== 'undefined' ? val : dv; +} + +function forValues(o, fn) { + Object.values(o).forEach(v => fn(v)); +} + +function device_v1_to_v2(device) { + if (device && device.filamentSize) { + device.extruders = [{ + extFilament: device.filamentSize, + extNozzle: device.nozzleSize, + extSelect: ["T0"], + extDeselect: [], + extOffsetX: 0, + extOffsetY: 0 + }]; + delete device.filamentSize; + delete device.nozzleSize; + } +} + +// convert default filter (from server) into device structure +function device_from_code(code,mode) { + // presence of internal field indicates already converted + if (code.internal >= 0) return code; + // if (self.navigator) console.log({mode, convert: code}); + let cmd = code.cmd || {}, + set = code.settings || {}, + ext = code.extruders; + + // currently causes unecessary fills and culls because + // it's not mode and device type sensitive + let device = { + noclone: valueOf(code.no_clone, false), + mode: mode || code.mode || '', + internal: 0, + imageURL: valueOf(set.image_url, ""), + imageScale: valueOf(set.image_scale, 0.75), + imageAnchor: valueOf(set.image_anchor, 0), + bedHeight: valueOf(set.bed_height, 2.5), + bedWidth: valueOf(set.bed_width, 300), + bedDepth: valueOf(set.bed_depth, 175), + bedRound: valueOf(set.bed_circle, false), + bedBelt: valueOf(set.bed_belt, false), + resolutionX: valueOf(set.resolution_x, 1600), + resolutionY: valueOf(set.resolution_y, 900), + deviceZMax: valueOf(set.z_move_max, 0), + maxHeight: valueOf(set.build_height, 150), + originCenter: valueOf(set.origin_center, false), + extrudeAbs: valueOf(set.extrude_abs, false), + spindleMax: valueOf(set.spindle_max, 0), + gcodeFan: valueOf(cmd.fan_power || code.fan_power, []), + gcodeTrack: valueOf(cmd.progress || code.progress, []), + gcodeLayer: valueOf(cmd.layer || code.layer, []), + gcodePre: valueOf(code.pre, []), + gcodePost: valueOf(code.post, []), + // post processor script of which only one exists + // for XYZ.daVinci.Mini.w triggered in kiri.export + // in the fdm driver to turn gcode into base64 + gcodeProc: valueOf(code.proc, ''), + gcodeDwell: valueOf(code.dwell, []), + gcodeSpindle: valueOf(code.spindle || cmd.spindle, []), + gcodeChange: valueOf(code['tool-change'], []), + gcodeFExt: valueOf(code['file-ext'], 'gcode'), + gcodeSpace: valueOf(code['token-space'], true), + gcodeStrip: valueOf(code['strip-comments'], false), + gcodeLaserOn: valueOf(code['laser-on'], []), + gcodeLaserOff: valueOf(code['laser-off'], []), + extruders: [] + }; + + if (ext) { + // synthesize extruders from new style settings + ext.forEach(rec => { + let e = clone(conf.defaults.fdm.d.extruders[0]); + if (rec.nozzle) e.extNozzle = rec.nozzle; + if (rec.filament) e.extFilament = rec.filament; + if (rec.offset_x) e.extOffsetX = rec.offset_x; + if (rec.offset_y) e.extOffsetY = rec.offset_y; + if (rec.select) e.extSelect = rec.select; + if (rec.deselect) e.extDeselect = rec.deselect; + device.extruders.push(e); + }); + } else { + // synthesize extruders from old style settings + device.extruders = [ clone(conf.defaults.fdm.d.extruders[0]) ]; + device.extruders[0].extNozzle = valueOf(set.nozzle_size, 0.4); + device.extruders[0].extFilament = valueOf(set.filament_diameter, 1.75); + } + + return device; +} + +// ensure settings structure is up-to-date +function normalize(settings) { + let defaults = conf.defaults, + template = conf.template, + mode = settings.mode.toLowerCase(), + default_dev = defaults[mode].d, + default_pro = defaults[mode].p; + + // v1 to v2 changed FDM extruder / nozzle / filament structure + if (settings.ver != CVER) { + // backup settings before upgrade + local.setItem(`ws-settings-${Date.now()}`, JSON.stringify(settings)); + device_v1_to_v2(settings.device); + device_v1_to_v2(settings.cdev.FDM); + objectMap(settings.devices, dev => { + return dev ? device_from_code(dev) : dev; + }); + settings.ver = CVER; + } + + // fixup old/new detail settings + let detail = settings.controller.detail; + settings.controller.detail = { + "best": "100", + "good": "75", + "fair": "50", + "poor": "25" + }[detail] || detail; + + fill_cull_once(settings, template); + fill_cull_once(settings.device, default_dev); + fill_cull_once(settings.process, default_pro); + fill_cull_once(settings.cdev, template.cdev); + fill_cull_once(settings.cproc, template.cproc); + fill_cull_once(settings.sproc, template.sproc); + fill_cull_once(settings.defaults, template.defaults); + fill_cull_once(settings.cdev.FDM, defaults.fdm.d); + fill_cull_once(settings.cdev.SLA, defaults.sla.d); + fill_cull_once(settings.cdev.CAM, defaults.cam.d); + fill_cull_once(settings.cdev.LASER, defaults.laser.d); + fill_cull_many(settings.sproc.FDM, defaults.fdm.p); + fill_cull_many(settings.sproc.SLA, defaults.sla.p); + fill_cull_many(settings.sproc.CAM, defaults.cam.p); + fill_cull_many(settings.sproc.LASER, defaults.laser.p); + fill_cull_once(settings.controller, template.controller); + + return settings; +} + +// auto field renaming on import +const renamed = { + roughingTool: "camRoughTool", + roughingSpindle: "camRoughSpindle", + roughingDown: "camRoughDown", + roughingOver: "camRoughOver", + roughingSpeed: "camRoughSpeed", + roughingPlunge: "camRoughPlunge", + roughingStock: "camRoughStock", + roughingPocket: "camRoughVoid", + roughingOn: "camRoughOn", + finishingTool: "camOutlineTool", + finishingSpindle: "camOutlineSpindle", + finishingDown: "camOutlineDown", + finishingOver: "camContourOver", + finishingSpeed: "camOutlineSpeed", + finishingPlunge: "camOutlinePlunge", + finishingOn: "camOutlineOn", + finishingXOn: "camContourXOn", + finishingYOn: "camContourYOn", + drillTool: "camDrillTool", + drillSpindle: "camDrillSpindle", + drillDownSpeed: "camDrillDownSpeed", + drillDown: "camDrillDown", + drillDwell: "camDrillDwell", + drillLift: "camDrillLift", + drillingOn: "camDrillingOn", + camPocketOnlyFinish: "camOutlinePocket", + camWideCutout: "camOutlineWide", + outputClockwise: "camConventional" +}; + +const conf = exports({ + // --------------- helper functions + normalize, + device_from_code, + fill_cull_once, + // --------------- device and process defaults + defaults: { + fdm:{ + // device defaults FDM:Device + d:{ + new: true, + mode: "", + deviceName: "", + imageURL: "", + internal: 0, + bedWidth: 300, + bedDepth: 175, + bedHeight: 2.5, + bedRound: false, + bedBelt: false, + fwRetract: false, + filamentSource: "direct", + originCenter: false, + deviceZMax: 0, + maxHeight: 150, + gcodePre: [], + gcodePost: [], + gcodeProc: "", + gcodeFan: [], + gcodeTrack: [], + gcodeLayer: [], + gcodeFExt: "", + extruders:[{ + extFilament: 1.75, + extNozzle: 0.4, + extSelect: ["T0"], + extDeselect: [], + extOffsetX: 0, + extOffsetY: 0 + }], + // other stored info like palette3 config + extras: {} + }, + // process defaults FDM:Process + p:{ + processName: "default", + sliceHeight: 0.25, + sliceShells: 3, + sliceShellOrder: "in-out", + sliceLayerStart: "last", + sliceLineWidth: 0, + sliceFillAngle: 45, + sliceFillWidth: 1, + sliceFillOverlap: 0.35, + sliceFillSparse: 0.25, + sliceFillRepeat: 1, + sliceFillRate: 0, + sliceFillType: "hex", + sliceSupportDensity: 0.25, + sliceSupportOffset: 1.0, + sliceSupportGap: 1, + sliceSupportSize: 5, + sliceSupportArea: 0.1, + sliceSupportSpan: 5, + sliceSupportExtra: 0, + sliceSupportAngle: 50, + sliceSupportNozzle: 0, + sliceSupportEnable: false, + sliceSupportOutline: true, + sliceSolidMinArea: 1, + sliceSolidLayers: 3, + sliceBottomLayers: 3, + sliceTopLayers: 3, + firstSliceHeight: 0.25, + firstLayerRate: 30, + firstLayerFillRate: 35, + firstLayerPrintMult: 1.0, + firstLayerLineMult: 1.0, + firstLayerYOffset: 0, + firstLayerNozzleTemp: 0, + firstLayerBedTemp: 0, + firstLayerBrim: 0, + firstLayerBrimIn: 0, + firstLayerBrimTrig: 0, + firstLayerBrimGap: 0, + firstLayerBeltLead: 3, + firstLayerBeltBump: 0, + firstLayerFanSpeed: 0, + firstLayerFlatten: 0, + outputRaft: false, + outputRaftSpacing: 0.2, + outputDraftShield: false, + outputTemp: 200, + outputBedTemp: 60, + outputFeedrate: 50, + outputFinishrate: 50, + outputSeekrate: 80, + outputShellMult: 1.25, + outputFillMult: 1.25, + outputSparseMult: 1.25, + outputFanSpeed: 255, + outputRetractDist: 1.5, + outputRetractSpeed: 40, + outputRetractWipe: 0, + outputRetractDwell: 20, + outputBrimCount: 2, + outputBrimOffset: 2, + outputShortPoly: 100.0, + outputMinSpeed: 10.0, + outputCoastDist: 0, + outputPurgeTower: 0, + outputBeltFirst: false, + outputAvoidGaps: true, + outputLayerRetract: false, + outputOriginCenter: true, + outputLoops: 0, + outputInvertX: false, + outputInvertY: false, + detectThinWalls: false, + sliceMinHeight: 0, + sliceAdaptive: false, + zHopDistance: 0.2, + arcTolerance: 0, + antiBacklash: 1, + ranges: [] + } + }, + sla:{ + // device defaults SLA:Device + d:{ + new: true, + mode: "", + deviceName: "", + imageURL: "", + noclone: false, + internal: 0, + bedWidth: 150, + bedDepth: 150, + bedHeight: 1.5, + maxHeight: 150, + resolutionX: 1600, + resolutionY: 900 + }, + // process defaults SLA:Process + p:{ + processName: "default", + slaSlice: 0.05, + slaShell: 0.00, + slaOpenTop: false, + slaOpenBase: false, + slaAntiAlias: 1, + slaLayerOff: 0.1, + slaLayerOn: 7, + slaPeelDist: 6, + slaPeelLiftRate: 1.5, + slaPeelDropRate: 3, + slaBaseLayers: 5, + slaBaseOff: 0.1, + slaBaseOn: 30, + slaBasePeelDist: 6, + slaBasePeelLiftRate: 1.5, + slaFillDensity: 0, + slaFillLine: 0.5, + slaFirstOffset: 0, + slaSupportLayers: 10, + slaSupportDensity: 0.5, + slaSupportSize: 0.6, + slaSupportPoints: 4, + slaSupportGap: 10, + slaSupportEnable: false + } + }, + cam:{ + // device defaults CAM:Device + d:{ + new: true, + mode: "", + deviceName: "", + imageURL: "", + internal: 0, + bedWidth: 300, + bedDepth: 175, + bedHeight: 2.5, + originCenter: false, + spindleMax: 0, + gcodePre: [], + gcodePost: [], + gcodeFExt: "", + gcodeSpace: true, + gcodeStrip: true, + gcodeDwell: ["G4 P{time}"], + gcodeChange: ["M6 T{tool}"], + gcodeSpindle: ["M3 S{speed}"] + }, + // process defaults CAM:Process + p:{ + processName: "default", + camLevelTool: 1000, + camLevelSpindle: 1000, + camLevelOver: 0.75, + camLevelSpeed: 1000, + camLevelDown: 0, + camRoughTool: 1000, + camRoughSpindle: 1000, + camRoughDown: 2, + camRoughOver: 0.4, + camRoughSpeed: 1000, + camRoughPlunge: 250, + camRoughStock: 0, + camRoughVoid: false, + camRoughFlat: true, + camRoughTop: false, + camRoughIn: true, + camRoughOn: true, + camOutlineTool: 1000, + camOutlineSpindle: 1000, + camOutlineDown: 3, + camOutlineOver: 0.4, + camOutlineSpeed: 800, + camOutlinePlunge: 250, + camOutlineWide: false, + camOutlineDogbone: false, + camOutlineOmitThru: false, + camOutlineOut: true, + camOutlineIn: false, + camOutlineOn: true, + camContourTool: 1000, + camContourSpindle: 1000, + camContourOver: 0.5, + camContourSpeed: 1000, + camContourAngle: 85, + camContourCurves: false, + camContourIn: false, + camContourXOn: true, + camContourYOn: true, + camTraceTool: 1000, + camTraceSpindle: 1000, + camTraceType: "follow", + camTraceOver: 0.5, + camTraceDown: 0, + camTraceSpeed: 250, + camTracePlunge: 200, + camTraceLines: false, + camDrillTool: 1000, + camDrillSpindle: 1000, + camDrillDownSpeed: 250, + camDrillDown: 5, + camDrillDwell: 250, + camDrillLift: 2, + camDrillMark: false, + camDrillingOn: false, + camRegisterSpeed: 1000, + camFlipAxis: "X", + camFlipOther: "", + // camDrillReg: "none", + camTabsWidth: 5, + camTabsHeight: 5, + camTabsDepth: 5, + camTabsMidline: false, + camDepthFirst: false, + camEaseDown: false, + camOriginTop: true, + camZAnchor: "top", + camZOffset: 0, + camZBottom: 0, + camZClearance: 1, + camZThru: 0, + camFastFeed: 6000, + camFastFeedZ: 300, + camTolerance: 0, + camStockX: 5, + camStockY: 5, + camStockZ: 0, + camStockOffset: true, + camStockClipTo: false, + camStockOn: true, + camConventional: false, // outputClockwise + outputOriginCenter: false, + outputInvertX: false, + outputInvertY: false, + camExpertFast: false, + camTrueShadow: false, + ops: [], // current ops + op2: [] // flip ops + } + }, + laser: { + // device defaults Laser:Device + d:{ + new: true, + mode: "", + deviceName: "", + imageURL: "", + internal: 0, + bedWidth: 300, + bedDepth: 175, + bedHeight: 2.5, + gcodePre: [], + gcodePost: [], + gcodeFExt: "", + gcodeSpace: true, + gcodeLaserOn: ["M106 S{power}"], + gcodeLaserOff: ["M107"] + }, + // process defaults Laser:Process + p:{ + processName: "default", + laserOffset: 0.25, + laserSliceHeight: 1, + laserSliceHeightMin: 0, + laserSliceSingle: false, + outputTileSpacing: 1, + outputLaserPower: 100, + outputLaserSpeed: 1000, + outputLaserGroup: true, + outputLaserZColor: false, + outputLaserLayer: false, + outputLaserStack: false, + outputLaserMerged: false, + outputOriginCenter: true, + outputInvertX: false, + outputInvertY: false, + outputKnifeDepth: 1, + outputKnifePasses: 1, + outputKnifeTip: 2, + knifeOn: false + } + } + }, + // --------------- settings template + template: { + bounds: {}, + // CAM only + origin: {}, + stock: {}, + tools:[ + { + id: 1000, + number: 1, + type: "endmill", + name: "end 1/4", + metric: false, + shaft_diam: 0.25, + shaft_len: 1, + flute_diam: 0.25, + flute_len: 2, + // taper_angle: 70, + taper_tip: 0, + }, + { + id: 1001, + number: 2, + type: "endmill", + name: "end 1/8", + metric: false, + shaft_diam: 0.125, + shaft_len: 1, + flute_diam: 0.125, + flute_len: 1.5, + // taper_angle: 70, + taper_tip: 0, + }, + { + id: 1002, + number: 3, + type: "endmill", + name: "end 1/16", + metric: false, + shaft_diam: 0.0625, + shaft_len: 1, + flute_diam: 0.0625, + flute_len: 1.5, + // taper_angle: 70, + taper_tip: 0, + } + ], + // currently selected device + device:{}, + // currently selected process + process:{}, + // current process name by mode + cproc:{ + FDM: "default", + SLA: "default", + CAM: "default", + LASER: "default" + }, + // stored processes by mode + sproc:{ + FDM: {}, + SLA: {}, + CAM: {}, + LASER: {} + }, + // current device name by mode + filter:{ + FDM: "Any.Generic.Marlin", + SLA: "Anycubic.Photon", + CAM: "Any.Generic.Grbl", + LASER: "Any.Generic.Laser" + }, + // stored device by mode + cdev: { + FDM: null, + SLA: null, + CAM: null, + LASER: null + }, + // custom devices by name (all modes) + devices:{}, + // favorited devices (all modes) + favorites:{}, + // map of device to last process setting (name) + devproc: {}, + controller:{ + view: null, + dark: false, + decals: true, + danger: false, + zoomSpeed: 1.0, + lineType: "path", + autoSave: true, + antiAlias: true, + reverseZoom: true, + showOrigin: false, + showRulers: true, + showSpeeds: true, + freeLayout: true, + autoLayout: true, + spaceRandoX: false, + spaceLayout: 1, + units: "mm", + exportOcto: false, + exportGhost: false, + exportLocal: false, + exportThumb: false, + exportPreview: false, + decimate: true, + detail: "75", + animesh: "200", + healMesh: false, + threaded: true, + assembly: false, + ortho: false, + devel: false + }, + // label state preferences + labels: { + 'FDM-3-engage': false, + 'FDM-3-retract': false, + 'FDM-3-arrows': false, + 'FDM-3-move': false + }, + // for passing temporary slice hints (topo currently) + synth: {}, + // widget extra info for slicing (extruder mapping) + widget: {}, + mode: 'FDM', + id: genID(), + ver: CVER + } +}); + +const settings = conf.template; + +// seed defaults. will get culled on save +settings.sproc.FDM.default = clone(settings.process); +settings.sproc.SLA.default = clone(settings.process); +settings.sproc.CAM.default = clone(settings.process); +settings.sproc.LASER.default = clone(settings.process); +settings.cdev.FDM = clone(settings.device); +settings.cdev.SLA = clone(settings.device); +settings.cdev.CAM = clone(settings.device); +settings.cdev.LASER = clone(settings.device); }); diff --git a/src/kiri/consts.js b/src/kiri/consts.js index 6b36c070..c1c5cec6 100644 --- a/src/kiri/consts.js +++ b/src/kiri/consts.js @@ -2,9 +2,7 @@ "use strict"; -(function () { - -const { kiri } = self; +gapp.register("kiri.consts", [], (root, exports) => { const COLOR = { wireframe: 0x444444, @@ -124,7 +122,7 @@ const PMODES = { const SEED = 'kiri-seed'; -kiri.consts = { +exports({ PMODES, COLOR, LISTS, @@ -132,6 +130,6 @@ kiri.consts = { VIEWS, SEED, beltfact: Math.cos(Math.PI / 4) -}; +}); -})(); +}); diff --git a/src/kiri/do.js b/src/kiri/do.js index 8b22020d..6989c2be 100644 --- a/src/kiri/do.js +++ b/src/kiri/do.js @@ -1,171 +1,179 @@ /** Copyright Stewart Allen -- All Rights Reserved */ -kiri.load(function(API) { - let stack = []; - let stpos = 0; +// dep: kiri.api +// use: kiri.selection +// use: moto.space +gapp.register("kiri.do", [], (root, events) => { - let event = API.event; - let moved = {x:0, y:0}; - let msgid; +const { kiri, moto } = root; +const { api } = kiri; +const { space } = moto; +const { event } = api; - event.on("init-done", () => { - $('undo').onclick = undo; - $('redo').onclick = redo; - }); +let stack = []; +let stpos = 0; +let moved = { x: 0, y: 0 }; +let msgid; - let undo = API.doit.undo = function() { - if (stpos) { - action(stack[--stpos].undo); - } else { - message('nothing to undo'); - } - }; +event.on("init-done", () => { + $('undo').onclick = undo; + $('redo').onclick = redo; +}); - let redo = API.doit.redo = function() { - if (stpos < stack.length) { - action(stack[stpos++].redo); - } else { - message('nothing to redo'); - } - }; - - let clear = API.doit.clear = function() { - stack = []; - stpos = 0; - moved = {x:0, y:0}; - updateButtons(); - }; - - function updateButtons() { - let isArrange = API.view.get() === kiri.consts.VIEWS.ARRANGE; - $('doit').style.display = isArrange && stack.length ? 'flex' : 'none'; - $('undo').disabled = stpos === 0; - $('redo').disabled = stpos == stack.length; +let undo = api.doit.undo = function() { + if (stpos) { + action(stack[--stpos].undo); + } else { + message('nothing to undo'); } +}; - function message(txt) { - if (msgid) { - API.hide.alert(msgid); - } - msgid = API.show.alert(txt); +let redo = api.doit.redo = function() { + if (stpos < stack.length) { + action(stack[stpos++].redo); + } else { + message('nothing to redo'); } +}; - function action(rec) { - switch (rec.type) { - case 'move': - for (let w of rec.widgets) { - w._move(rec.dist.x, rec.dist.y, 0); - } - break; - case 'rotate': - for (let w of rec.widgets) { - w.rotate(rec.x, rec.y, rec.z); - } - break; - case 'scale': - for (let w of rec.widgets) { - w._scale(rec.x, rec.y, rec.z); - } - API.selection.update_info(); - break; - } - API.space.update(); - updateButtons(); +let clear = api.doit.clear = function() { + stack = []; + stpos = 0; + moved = {x:0, y:0}; + updateButtons(); +}; + +function updateButtons() { + let isArrange = api.view.get() === kiri.consts.VIEWS.ARRANGE; + $('doit').style.display = isArrange && stack.length ? 'flex' : 'none'; + $('undo').disabled = stpos === 0; + $('redo').disabled = stpos == stack.length; +} + +function message(txt) { + if (msgid) { + api.hide.alert(msgid); } + msgid = api.show.alert(txt); +} - function pushActions(ur) { - stack.length = stpos++; - stack.push(ur); - updateButtons(); - } - - event.on([ - 'platform.layout', - 'widget.add', - 'widget.delete' - ], (widget) => { - clear(); - }); - - event.on('view.set', mode => { - updateButtons(); - }); - - // selection event (store position) - event.on('selection.rotate', (rec) => { - let type = "rotate"; - let widgets = API.selection.widgets(true); - if (!widgets.length) { - return; - } - let {x, y, z} = rec; - if (!(x || y || z)) { - return; - } - pushActions({ - redo: { - type, - widgets, - x, y, z - }, - undo: { - type, - widgets, - x: -x, y: -y, z: -z +function action(rec) { + switch (rec.type) { + case 'move': + for (let w of rec.widgets) { + w._move(rec.dist.x, rec.dist.y, 0); } - }) - }); - - event.on('selection.scale', (rec) => { - let [x, y, z] = rec; - // ignore scale close to 1 - if (Math.abs(1 - (x * y * z)) < 0.0001) { - return; - } - let widgets = API.selection.widgets(true); - if (!widgets.length) { - return; - } - let type = "scale"; - pushActions({ - redo: { - type, - widgets, - x, y, z - }, - undo: { - type, - widgets, - x: 1/x, y: 1/y, z: 1/z + break; + case 'rotate': + for (let w of rec.widgets) { + w.rotate(rec.x, rec.y, rec.z); } - }); - }); - - // selection moved (accumulator) - event.on('selection.drag', (delta) => { - moved.x += delta.x; - moved.y += delta.y; - }); - - // move complete (store updated position) - event.on('mouse.drag.done', () => { - let widgets = API.selection.widgets(true); - if (!widgets.length) { - return; - } - let type = "move"; - pushActions({ - redo: { - type, - widgets, - dist: moved - }, - undo: { - type, - widgets, - dist: {x: -moved.x, y: -moved.y} + break; + case 'scale': + for (let w of rec.widgets) { + w._scale(rec.x, rec.y, rec.z); } - }); - moved = {x:0, y:0}; + api.selection.update_info(); + break; + } + space.update(); + updateButtons(); +} + +function pushActions(ur) { + stack.length = stpos++; + stack.push(ur); + updateButtons(); +} + +event.on([ + 'platform.layout', + 'widget.add', + 'widget.delete' +], (widget) => { + clear(); +}); + +event.on('view.set', mode => { + updateButtons(); +}); + +// selection event (store position) +event.on('selection.rotate', (rec) => { + let type = "rotate"; + let widgets = api.selection.widgets(true); + if (!widgets.length) { + return; + } + let {x, y, z} = rec; + if (!(x || y || z)) { + return; + } + pushActions({ + redo: { + type, + widgets, + x, y, z + }, + undo: { + type, + widgets, + x: -x, y: -y, z: -z + } + }) +}); + +event.on('selection.scale', (rec) => { + let [x, y, z] = rec; + // ignore scale close to 1 + if (Math.abs(1 - (x * y * z)) < 0.0001) { + return; + } + let widgets = api.selection.widgets(true); + if (!widgets.length) { + return; + } + let type = "scale"; + pushActions({ + redo: { + type, + widgets, + x, y, z + }, + undo: { + type, + widgets, + x: 1/x, y: 1/y, z: 1/z + } }); }); + +// selection moved (accumulator) +event.on('selection.drag', (delta) => { + moved.x += delta.x; + moved.y += delta.y; +}); + +// move complete (store updated position) +event.on('mouse.drag.done', () => { + let widgets = api.selection.widgets(true); + if (!widgets.length) { + return; + } + let type = "move"; + pushActions({ + redo: { + type, + widgets, + dist: moved + }, + undo: { + type, + widgets, + dist: {x: -moved.x, y: -moved.y} + } + }); + moved = {x:0, y:0}; +}); + +}); diff --git a/src/kiri/export.js b/src/kiri/export.js index a71c02ca..1b09dd51 100644 --- a/src/kiri/export.js +++ b/src/kiri/export.js @@ -1,678 +1,678 @@ -console.log/** Copyright Stewart Allen -- All Rights Reserved */ +/** Copyright Stewart Allen -- All Rights Reserved */ "use strict"; -(function() { +// dep: geo.base +// dep: data.local +// dep: kiri.consts +// dep: kiri.api +gapp.register("kiri.export", [], (root, exports) => { - if (self.kiri.export) return; +const { base, data, kiri } = root; +const { api, consts } = kiri; +const { local } = data; +const { util } = base; +const { stats, ui } = api; +const { MODES } = consts; - const KIRI = self.kiri, - BASE = self.base, - UTIL = BASE.util, - API = KIRI.api, - SDB = API.sdb, - UI = API.ui, - STATS = API.stats, - MODES = KIRI.consts.MODES; +kiri.export = exportFile; - KIRI.export = exportFile; +let printSeq = parseInt(local['kiri-print-seq'] || local['print-seq'] || "0") + 1; - let printSeq = parseInt(SDB['kiri-print-seq'] || SDB['print-seq'] || "0") + 1; +function exportFile(options) { + let mode = api.mode.get(); + let names = api.widgets.all().map(w => w.meta ? w.meta.file : undefined) + .filter(v => v) + .map(v => v.replace(/ /g,'-')) + .map(v => v.substring(0,20)) + .map(v => v.split('.')[0]); + api.event.emit('export', mode); + switch (mode) { + case 'LASER': return callExportLaser(options, names); + case 'FDM': return callExport(options, mode, names); + case 'CAM': return callExport(options, mode, names); + case 'SLA': return callExportSLA(options, names); + } +} - function exportFile(options) { - let mode = API.mode.get(); - let names = API.widgets.all().map(w => w.meta ? w.meta.file : undefined) - .filter(v => v) - .map(v => v.replace(/ /g,'-')) - .map(v => v.substring(0,20)) - .map(v => v.split('.')[0]); - API.event.emit('export', mode); - switch (mode) { - case 'LASER': return callExportLaser(options, names); - case 'FDM': return callExport(options, mode, names); - case 'CAM': return callExport(options, mode, names); - case 'SLA': return callExportSLA(options, names); +function callExport(callback, mode, names) { + let alert = api.feature.work_alerts ? api.show.alert("Exporting") : null; + let gcode = []; + let section = []; + let sections = { }; + kiri.client.export(api.conf.get(), (line) => { + if (typeof line !== 'string') { + if (line.section) { + sections[line.section] = section = []; + } + return; } + gcode.push(line); + section.push(line); + }, (output, error) => { + api.hide.alert(alert); + if (error) { + api.show.alert(error, 5); + } else if (callback) { + callback(gcode.join('\r\n'), output); + } else { + exportGCodeDialog(gcode, mode === 'CAM' ? sections : undefined, output, names); + } + }); +} + +function callExportLaser(options, names) { + kiri.client.export(api.conf.get(), (line) => { + console.log({unexpected_line: line}); + }, (output, error) => { + if (error) { + api.show.alert(error, 5); + } else { + exportLaserDialog(output, names); + } + }); +} + +function callExportSLA(options, names) { + kiri.client.export(api.conf.get(), (line) => { + api.show.progress(line.progress, "exporting"); + }, (output, error) => { + api.show.progress(0); + if (error) { + api.show.alert(error, 5); + } else { + kiri.driver.SLA.printDownload(output, api, names); + } + }); +} + +function exportLaserDialog(data, names) { + local['kiri-print-seq'] = printSeq++; + + const fileroot = names[0] || "laser"; + const filename = `${fileroot}-${(printSeq.toString().padStart(3,"0"))}`; + const settings = api.conf.get(); + const driver = kiri.driver.LASER; + + function download_svg() { + api.util.download( + driver.exportSVG(settings, data), + $('print-filename').value + ".svg" + ); } - function callExport(callback, mode, names) { - let alert = API.feature.work_alerts ? API.show.alert("Exporting") : null; - let gcode = []; - let section = []; - let sections = { }; - KIRI.client.export(API.conf.get(), (line) => { - if (typeof line !== 'string') { - if (line.section) { - sections[line.section] = section = []; - } - return; - } - gcode.push(line); - section.push(line); - }, (output, error) => { - API.hide.alert(alert); - if (error) { - API.show.alert(error, 5); - } else if (callback) { - callback(gcode.join('\r\n'), output); - } else { - exportGCodeDialog(gcode, mode === 'CAM' ? sections : undefined, output, names); - } - }); + function download_dxf() { + api.util.download( + driver.exportDXF(settings, data), + $('print-filename').value + ".dxf" + ); } - function callExportLaser(options, names) { - KIRI.client.export(API.conf.get(), (line) => { - console.log({unexpected_line: line}); - }, (output, error) => { - if (error) { - API.show.alert(error, 5); - } else { - exportLaserDialog(output, names); - } - }); + function download_gcode() { + api.util.download( + driver.exportGCode(settings, data), + $('print-filename').value + ".gcode" + ); } - function callExportSLA(options, names) { - KIRI.client.export(API.conf.get(), (line) => { - API.show.progress(line.progress, "exporting"); - }, (output, error) => { - API.show.progress(0); - if (error) { - API.show.alert(error, 5); - } else { - KIRI.driver.SLA.printDownload(output, API, names); - } - }); + api.ajax("/kiri/output-laser.html", function(html) { + let segments = 0; + data.forEach(layer => { segments += layer.length }); + ui.print.innerHTML = html; + $('print-filename').value = filename; + $('print-lines').value = util.comma(segments); + $('print-svg').onclick = download_svg; + $('print-dxf').onclick = download_dxf; + $('print-lg').onclick = download_gcode; + api.modal.show('print'); + }); +} + +function exportGCodeDialog(gcode, sections, info, names) { + local['kiri-print-seq'] = printSeq++; + + let settings = api.conf.get(), + MODE = api.mode.get_id(), + name = names[0] || (MODE === MODES.CAM ? "cnc" : "print"), + pre = `${name}-${(printSeq.toString().padStart(3,"0"))}`, + filename = pre,// + (new Date().getTime().toString(36)), + fileext = settings.device.gcodeFExt || "gcode", + codeproc = settings.device.gcodeProc, + octo_host, + octo_apik, + grid_host, + grid_apik, + grid_target, + grid_targets = {}, + grid_local, + grid_uuid; + + // join gcode array into a string + gcode = gcode.join('\r\n'); + + // run gcode post processor function (when supplied and valid) + if (codeproc && self[codeproc]) { + gcode = self[codeproc](gcode); } - function exportLaserDialog(data, names) { - SDB['kiri-print-seq'] = printSeq++; - - const fileroot = names[0] || "laser"; - const filename = `${fileroot}-${(printSeq.toString().padStart(3,"0"))}`; - const settings = API.conf.get(); - const driver = KIRI.driver.LASER; - - function download_svg() { - API.util.download( - driver.exportSVG(settings, data), - $('print-filename').value + ".svg" - ); - } - - function download_dxf() { - API.util.download( - driver.exportDXF(settings, data), - $('print-filename').value + ".dxf" - ); - } - - function download_gcode() { - API.util.download( - driver.exportGCode(settings, data), - $('print-filename').value + ".gcode" - ); - } - - API.ajax("/kiri/output-laser.html", function(html) { - let segments = 0; - data.forEach(layer => { segments += layer.length }); - UI.print.innerHTML = html; - $('print-filename').value = filename; - $('print-lines').value = UTIL.comma(segments); - $('print-svg').onclick = download_svg; - $('print-dxf').onclick = download_dxf; - $('print-lg').onclick = download_gcode; - API.modal.show('print'); - }); + function getBlob() { + return new Blob( + [gcode], + {type:"application/octet-stream"}); } - function exportGCodeDialog(gcode, sections, info, names) { - SDB['kiri-print-seq'] = printSeq++; + function sendto_octoprint() { + if (!(octo_host && octo_apik)) return; - let settings = API.conf.get(), - MODE = API.mode.get_id(), - name = names[0] || (MODE === MODES.CAM ? "cnc" : "print"), - pre = `${name}-${(printSeq.toString().padStart(3,"0"))}`, - filename = pre,// + (new Date().getTime().toString(36)), - fileext = settings.device.gcodeFExt || "gcode", - codeproc = settings.device.gcodeProc, - octo_host, - octo_apik, - grid_host, - grid_apik, - grid_target, - grid_targets = {}, - grid_local, - grid_uuid; + let form = new FormData(), + ajax = new XMLHttpRequest(), + host = octo_host.value.toLowerCase(), + apik = octo_apik.value; - // join gcode array into a string - gcode = gcode.join('\r\n'); - - // run gcode post processor function (when supplied and valid) - if (codeproc && self[codeproc]) { - gcode = self[codeproc](gcode); + if (host.indexOf("http") !== 0) { + api.show.alert("host missing protocol (http:// or https://)"); + return; + } + if (api.const.SECURE && !api.util.isSecure(host)) { + api.show.alert("host must begin with 'https' on a secure site"); + return; } - function getBlob() { - return new Blob( - [gcode], - {type:"application/octet-stream"}); - } + local['octo-host'] = host.trim(); + local['octo-apik'] = apik.trim(); - function sendto_octoprint() { - if (!(octo_host && octo_apik)) return; - - let form = new FormData(), - ajax = new XMLHttpRequest(), - host = octo_host.value.toLowerCase(), - apik = octo_apik.value; - - if (host.indexOf("http") !== 0) { - API.show.alert("host missing protocol (http:// or https://)"); - return; - } - if (API.const.SECURE && !API.util.isSecure(host)) { - API.show.alert("host must begin with 'https' on a secure site"); - return; - } - - SDB['octo-host'] = host.trim(); - SDB['octo-apik'] = apik.trim(); - - filename = $('print-filename').value; - form.append("file", getBlob(), filename+"."+fileext); - ajax.onreadystatechange = function() { - if (ajax.readyState === 4) { - let status = ajax.status; - STATS.add(`ua_${API.mode.get_lower()}_print_octo_${status}`); - if (status >= 200 && status < 300) { - API.modal.hide(); - } else { - API.show.alert("octoprint error\nstatus: "+status+"\nmessage: "+ajax.responseText); - } - API.show.progress(0); - } - API.show.progress(0); - }; - ajax.upload.addEventListener('progress', function(evt) { - API.show.progress(evt.loaded/evt.total, "sending"); - }); - ajax.open("POST", host+"/api/files/local"); - if (apik) { - ajax.setRequestHeader("X-Api-Key", apik); - } - ajax.send(form); - } - - function gridhost_tracker(host,key) { - ajax(host+"/api/check?key="+key, function(data) { - data = js2o(data); - console.log(data); - if (!(data.done || data.error)) { - setTimeout(function() { gridhost_tracker(host,key) }, 1000); - } - }); - } - - function gridlocal_probe(ev, devs) { - if (ev && ev.code !== 'Enter') return; - - if (!devs && API.probe.local(gridlocal_probe)) return; - - grid_local = devs; - - let gdev = SDB['grid-local']; - let gloc = $('grid-local'); - let html = []; - for (let uuid in devs) { - gdev = gdev || uuid; - let dev = devs[uuid]; - let sel = uuid === gdev ? ' selected' : ''; - html.push(``); - } - - if (html.length) { - gloc.innerHTML = html.join('\n'); - gloc.onchange = (ev) => { - SDB['grid-local'] = gloc.options[gloc.selectedIndex].value - }; - $('send-to-gridhead').style.display = 'flex'; - $('send-to-gridspool').style.display = 'flex'; - } else { - $('send-to-gridhead').style.display = ''; - $('send-to-gridspool').style.display = ''; - } - } - - function sendto_gridlocal() { - let uuid = $('grid-local').value; - let dev = grid_local[uuid]; - if (dev) { - let file = $('print-filename').value; - fetch( - `${API.probe.live}/api/grid_send?uuid=${uuid}&file=${encodeURIComponent(file + "." + fileext)}`, - {method: "POST", body: gcode} - ) - .then(t => t.text()) - .then(t => { - STATS.add(`ua_${API.mode.get_lower()}_print_local_ok`); - console.log({grid_spool_said: t}); - }) - .catch(e => { - STATS.add(`ua_${API.mode.get_lower()}_print_local_err`); - console.log({grid_local_spool_error: e}); - }) - .finally(() => { - API.modal.hide(); - }); - } - } - - function admin_gridlocal() { - let dev = grid_local[$('grid-local').value]; - if (dev && dev.stat && dev.stat.device) { - let dsd = dev.stat.device; - window.open(`http://${dsd.addr[0]}:${dsd.port || 4080}`, "_grid_admin"); - } - } - - function gridhost_probe(ev, set_host) { - if (ev && ev.code !== 'Enter') return; - if (!(grid_host && grid_apik)) return; - - if (set_host) grid_host.value = set_host; - - let xhtr = new XMLHttpRequest(), - host = grid_host.value, - apik = grid_apik.value, - target = grid_target.value; - - if (!apik) $('gpapik').style.display = 'none'; - - if (!host && API.probe.grid(gridhost_probe)) return; - - if (!host) return; - - xhtr.onreadystatechange = function() { - if (xhtr.readyState === 4) { - if (xhtr.status >= 200 && xhtr.status < 300) { - SDB['grid-host'] = host; - SDB['grid-apik'] = apik; - let res = JSON.parse(xhtr.responseText); - let sel = false; - let match = false; - let first = null; - let html = []; - grid_targets = {}; - for (let key in res) { - first = first || key; - if (!SDB['grid-target']) { - SDB['grid-target'] = key; - sel = true; - } else { - sel = SDB['grid-target'] === key; - } - match = match || sel; - grid_targets[html.length] = key; - html.push( - "" - ); - } - if (!match) { - SDB['grid-target'] = first; - } - grid_target.innerHTML = html.join('\n'); - } else if (xhtr.status === 401) { - $('gpapik').style.display = ''; - } else { - SDB.removeItem('grid-host'); - SDB.removeItem('grid-apik'); - console.log("invalid grid:host url"); - } - } - }; - - xhtr.open("GET", host + "/api/active?key=" + apik); - xhtr.send(); - } - - function sendto_gridhost() { - if (!(grid_host && grid_apik)) return; - - let xhtr = new XMLHttpRequest(), - host = grid_host.value, - apik = grid_apik.value, - target = SDB['grid-target'] || ''; - - if (target === '') { - API.show.alert('invalid or missing target'); - return; - } - if (host.indexOf("http") !== 0) { - API.show.alert("host missing protocol (http:// or https://)"); - return; - } - if (host.indexOf("://") < 0) { - API.show.alert("host:port malformed"); - return; - } - if (API.const.SECURE && !API.util.isSecure(host)) { - API.show.alert("host must begin with 'https' on a secure site"); - return; - } - - SDB['grid-host'] = host.trim(); - SDB['grid-apik'] = apik.trim(); - - xhtr.onreadystatechange = function() { - if (xhtr.readyState === 4) { - let status = xhtr.status; - STATS.add(`ua_${API.mode.get_lower()}_print_grid_${status}`); - if (status >= 200 && status < 300) { - let json = js2o(xhtr.responseText); - gridhost_tracker(host,json.key); - API.ajax(host+"/api/wait?key="+json.key, function(data) { - data = js2o(data); - console.log(data); - API.show.alert("print to "+target+": "+data.status, 600); - }); - } else { - API.show.alert("grid:host error\nstatus: "+status+"\nmessage: "+xhtr.responseText, 10000); - } - API.show.progress(0); - } - }; - xhtr.upload.addEventListener('progress', function(evt) { - API.show.progress(evt.loaded/evt.total, "sending"); - }); - filename = $('print-filename').value; - xhtr.open("POST", - host + "/api/print?" + - "filename=" + filename + - "&target=" + target + - "&key=" + apik + - "&time=" + Math.round(info.time) + - "&length=" + Math.round(info.distance) + - "&image=" + filename - ); - xhtr.setRequestHeader("Content-Type", "text/plain"); - let snapshot = API.view.snapshot; - xhtr.send(snapshot ? [gcode,snapshot].join("\0") : gcode); - API.modal.hide(); - } - - function download() { - filename = $('print-filename').value; - API.util.download(gcode, filename + "." + fileext); - // saveAs(getBlob(), filename + "." + fileext); - } - - function pad(v) { - v = v.toString(); - return v.length < 2 ? '0' + v : v; - } - - function calcWeight() { - try { - let density = $('print-density'); - $('print-weight').value = ( - (Math.PI * UTIL.sqr( - info.settings.device.extruders[0].extFilament / 2 - )) * - info.distance * - (parseFloat(density.value) || 1.25) / - 1000 - ).toFixed(2); - density.onkeyup = (ev) => { - if (ev.key === 'Enter') calcWeight(); - }; - } catch (e) { } - } - - function calcTime() { - let floor = Math.floor, - time = floor(info.time), - hours = floor(time / 3600), - newtime = time - hours * 3600, - mins = floor(newtime / 60), - secs = newtime - mins * 60; - - $('output-time').value = [pad(hours),pad(mins),pad(secs)].join(':'); - } - - fetch("/kiri/output-gcode.html").then(r => r.text()).then(html => { - UI.print.innerHTML = html; - let set = API.conf.get(); - let fdm = MODE === MODES.FDM; - let octo = set.controller.exportOcto && MODE !== MODES.CAM; - let ghost = set.controller.exportGhost; - let local = set.controller.exportLocal; - let preview = set.controller.exportPreview; - $('code-preview-head').style.display = preview ? '' : 'none'; - $('code-preview').style.display = preview ? '' : 'none'; - $('print-download').onclick = download; - $('print-filament-head').style.display = fdm ? '' : 'none'; - $('print-filament-info').style.display = fdm ? '' : 'none'; - $('print-filename').value = filename; - $('print-filesize').value = UTIL.comma(info.bytes); - $('print-filament').value = Math.round(info.distance); - calcTime(); - if (fdm) { - calcWeight(); - } - - // in cam mode, show zip file option - let downloadZip = $('print-zip'); - downloadZip.style.display = sections ? 'flex' : 'none'; - downloadZip.onclick = function() { - let files = []; - for (let [ name, data ] of Object.entries(sections)) { - if (name.indexOf('op-') === 0) { - let head = sections.header || []; - let foot = sections.footer || []; - files.push({ - name: `${name}.${fileext}`, - data: [ ...head, ...data, ...foot ].join('\r\n') - }) - } - } - KIRI.client.zip(files, progress => { - API.show.progress(progress.percent/100, "generating zip files"); - }, output => { - API.show.progress(0); - API.util.download(output, `${$('print-filename').value}.zip`); - }) - }; - - // in palette mode, show download button - let downloadPalette = $('print-palette'); - downloadPalette.style.display = info.segments ? 'flex' : 'none'; - // generate MAFX downloadble file - if (info.segments) { - // todo: reduce segments to eliminate 0 lenghts and transitions before 150mm - let { settings, segments } = info; - let { device, bounds } = settings; - let { min, max } = bounds; - let extras = device.extras || {}; - let pinfo = extras.palette || {}; - // filter pings to those occuring after all tubes combined - let pings = info.purges || []; - let driveInfo = {}; - let volume = {}; - let length = {}; - // clean up and round pings - pings.forEach(p => { - p.length = p.length.round(3); - // p.length = (p.length - pinfo.offset).round(2); - // p.length = (p.length + pinfo.offset).round(2); - p.extrusion = p.extrusion.round(3); - }); - // add length of push filament to the last segment - segments.peek().emitted += pinfo.push; - let lastEmit = 0; - let ratioVolume = (0.4 * 0.4) / (1.75 * 1.75); - for (let seg of segments) { - let seginfo = driveInfo[seg.tool] = driveInfo[seg.tool] || { length: 0, volume: 0 }; - seg.emitted += pinfo.offset; - seginfo.length += seg.emitted - lastEmit; - seginfo.volume = seginfo.length * ratioVolume; - volume[seg.tool+1] = seginfo.volume.round(2); - length[seg.tool+1] = seginfo.length.round(2); - lastEmit = seg.emitted; - } - let totalLength = Object.values(length).reduce((a,v) => a+v).round(2); - let totalVolume = Object.values(volume).reduce((a,v) => a+v).round(2); - // console.log({info, device, pinfo, segments, volume, length, totalLength}); - let meta = { - version: "3.2", - printerProfile: { - id: pinfo.printer, - name: device.deviceName || "My Printer" - }, - preheatTemperature: { nozzle: [0], bed: 0 }, - paletteNozzle: 0, - time: info.time.round(1), - volume, - length, - totalLength, - totalVolume, - inputsUsed: Object.keys(driveInfo).length, - splices: segments.length, - pings: pings.length, - boundingBox: { - min: [ min.x, min.y, min.z ], - max: [ max.x, max.y, max.z ] - }, - filaments: Object.keys(driveInfo).map(v => { return { - name: `Color${v}`, - type: "Filament", - color: `#${v}0${v}0${v}0`, - materialId: parseInt(v) + 1, - filamentId: parseInt(v) + 1 - }}), - }; - let lastDrive; - let algokeys = {}; - let algorithms = []; - let defaultSplice = { - compression: pinfo.press, - cooling: pinfo.cool, - heat: pinfo.heat - }; - for (let key of Object.keys(driveInfo)) { - key = parseInt(key) + 1; - algorithms.push({ - ingoingId: key, - outgoingId: key, - ...defaultSplice - }); - } - let palette = { - version: "3.0", - drives: [0, 0, 0, 0, 0, 0, 0, 0].map((v,i) => { - return driveInfo[i] ? i+1 : 0 - }), - splices: segments.filter(r => { - return r.emitted >= 150; - }).map(r => { - if (lastDrive >= 0 && lastDrive !== r.tool) { - let key = `${lastDrive}+${r.tool}`; - if (!algokeys[key]) { - let rec = algokeys[key] = { - ingoingId: lastDrive + 1, - outgoingId: r.tool + 1, - ...defaultSplice - }; - algorithms.push(rec); - } - } - lastDrive = r.tool; - return { id: r.tool + 1, length: r.emitted.round(2) } - }), - pings, - algorithms - }; - let png; - KIRI.work.png({}, data => { - png = data.png; - }); - console.log({meta,palette}); - downloadPalette.onclick = function() { - KIRI.client.zip([ - {name:"meta.json", data:JSON.stringify(meta,undefined,4)}, - {name:"palette.json", data:JSON.stringify(palette,undefined,4)}, - {name:"thumbnail.png", data:png.buffer} - ], progress => { - API.show.progress(progress.percent/100, "generating palette files"); - }, output => { - API.show.progress(0); - API.util.download(output, `${$('print-filename').value}.mafx`); - }) - }; - } - - // octoprint setup - $('send-to-octohead').style.display = octo ? '' : 'none'; - $('send-to-octoprint').style.display = octo ? '' : 'none'; - if (octo) try { - $('print-octoprint').onclick = sendto_octoprint; - octo_host = $('octo-host'); - octo_apik = $('octo-apik'); - if (MODE === MODES.CAM) { - $('send-to-octohead').style.display = 'none'; - $('send-to-octoprint').style.display = 'none'; + filename = $('print-filename').value; + form.append("file", getBlob(), filename+"."+fileext); + ajax.onreadystatechange = function() { + if (ajax.readyState === 4) { + let status = ajax.status; + stats.add(`ua_${api.mode.get_lower()}_print_octo_${status}`); + if (status >= 200 && status < 300) { + api.modal.hide(); } else { - $('send-to-octohead').style.display = ''; - $('send-to-octoprint').style.display = ''; + api.show.alert("octoprint error\nstatus: "+status+"\nmessage: "+ajax.responseText); } - // hide octoprint when hard-coded in the url - if (API.const.OCTO) { - $('ophost').style.display = 'none'; - $('opapik').style.display = 'none'; - $('oph1nt').style.display = 'none'; - $('send-to-gridhost').style.display = 'none'; - } - octo_host.value = SDB['octo-host'] || ''; - octo_apik.value = SDB['octo-apik'] || ''; - } catch (e) { console.log(e) } + api.show.progress(0); + } + api.show.progress(0); + }; + ajax.upload.addEventListener('progress', function(evt) { + api.show.progress(evt.loaded/evt.total, "sending"); + }); + ajax.open("POST", host+"/api/files/local"); + if (apik) { + ajax.setRequestHeader("X-Api-Key", apik); + } + ajax.send(form); + } - // grid:host setup - $('send-to-hosthead').style.display = ghost ? '' : 'none'; - $('send-to-gridhost').style.display = ghost ? '' : 'none'; - if (ghost) try { - $('print-gridhost').onclick = sendto_gridhost; - $('grid-host').onkeyup = gridhost_probe; - $('grid-apik').onkeyup = gridhost_probe; - grid_host = $('grid-host'); - grid_apik = $('grid-apik'); - grid_target = $('grid-target'); - grid_target.onchange = function(ev) { - SDB['grid-target'] = grid_targets[grid_target.selectedIndex]; - }; - grid_host.value = SDB['grid-host'] || ''; - grid_apik.value = SDB['grid-apik'] || ''; - gridhost_probe(); - } catch (e) { console.log(e) } - - // grid:local setup - if (local) try { - gridlocal_probe(); - $('print-gridlocal').onclick = sendto_gridlocal; - $('admin-gridlocal').onclick = admin_gridlocal; - } catch (e) { console.log(e) } - - // preview of the generated GCODE (first 64k max) - if (preview && gcode) $('code-preview-textarea').value = gcode.substring(0,65535); - - // show dialog - API.modal.show('print'); + function gridhost_tracker(host,key) { + ajax(host+"/api/check?key="+key, function(data) { + data = js2o(data); + console.log(data); + if (!(data.done || data.error)) { + setTimeout(function() { gridhost_tracker(host,key) }, 1000); + } }); } -})(); + function gridlocal_probe(ev, devs) { + if (ev && ev.code !== 'Enter') return; + + if (!devs && api.probe.local(gridlocal_probe)) return; + + grid_local = devs; + + let gdev = local['grid-local']; + let gloc = $('grid-local'); + let html = []; + for (let uuid in devs) { + gdev = gdev || uuid; + let dev = devs[uuid]; + let sel = uuid === gdev ? ' selected' : ''; + html.push(``); + } + + if (html.length) { + gloc.innerHTML = html.join('\n'); + gloc.onchange = (ev) => { + local['grid-local'] = gloc.options[gloc.selectedIndex].value + }; + $('send-to-gridhead').style.display = 'flex'; + $('send-to-gridspool').style.display = 'flex'; + } else { + $('send-to-gridhead').style.display = ''; + $('send-to-gridspool').style.display = ''; + } + } + + function sendto_gridlocal() { + let uuid = $('grid-local').value; + let dev = grid_local[uuid]; + if (dev) { + let file = $('print-filename').value; + fetch( + `${api.probe.live}/api/grid_send?uuid=${uuid}&file=${encodeURIComponent(file + "." + fileext)}`, + {method: "POST", body: gcode} + ) + .then(t => t.text()) + .then(t => { + stats.add(`ua_${api.mode.get_lower()}_print_local_ok`); + console.log({grid_spool_said: t}); + }) + .catch(e => { + stats.add(`ua_${api.mode.get_lower()}_print_local_err`); + console.log({grid_local_spool_error: e}); + }) + .finally(() => { + api.modal.hide(); + }); + } + } + + function admin_gridlocal() { + let dev = grid_local[$('grid-local').value]; + if (dev && dev.stat && dev.stat.device) { + let dsd = dev.stat.device; + window.open(`http://${dsd.addr[0]}:${dsd.port || 4080}`, "_grid_admin"); + } + } + + function gridhost_probe(ev, set_host) { + if (ev && ev.code !== 'Enter') return; + if (!(grid_host && grid_apik)) return; + + if (set_host) grid_host.value = set_host; + + let xhtr = new XMLHttpRequest(), + host = grid_host.value, + apik = grid_apik.value, + target = grid_target.value; + + if (!apik) $('gpapik').style.display = 'none'; + + if (!host && api.probe.grid(gridhost_probe)) return; + + if (!host) return; + + xhtr.onreadystatechange = function() { + if (xhtr.readyState === 4) { + if (xhtr.status >= 200 && xhtr.status < 300) { + local['grid-host'] = host; + local['grid-apik'] = apik; + let res = JSON.parse(xhtr.responseText); + let sel = false; + let match = false; + let first = null; + let html = []; + grid_targets = {}; + for (let key in res) { + first = first || key; + if (!local['grid-target']) { + local['grid-target'] = key; + sel = true; + } else { + sel = local['grid-target'] === key; + } + match = match || sel; + grid_targets[html.length] = key; + html.push( + "" + ); + } + if (!match) { + local['grid-target'] = first; + } + grid_target.innerHTML = html.join('\n'); + } else if (xhtr.status === 401) { + $('gpapik').style.display = ''; + } else { + local.removeItem('grid-host'); + local.removeItem('grid-apik'); + console.log("invalid grid:host url"); + } + } + }; + + xhtr.open("GET", host + "/api/active?key=" + apik); + xhtr.send(); + } + + function sendto_gridhost() { + if (!(grid_host && grid_apik)) return; + + let xhtr = new XMLHttpRequest(), + host = grid_host.value, + apik = grid_apik.value, + target = local['grid-target'] || ''; + + if (target === '') { + api.show.alert('invalid or missing target'); + return; + } + if (host.indexOf("http") !== 0) { + api.show.alert("host missing protocol (http:// or https://)"); + return; + } + if (host.indexOf("://") < 0) { + api.show.alert("host:port malformed"); + return; + } + if (api.const.SECURE && !api.util.isSecure(host)) { + api.show.alert("host must begin with 'https' on a secure site"); + return; + } + + local['grid-host'] = host.trim(); + local['grid-apik'] = apik.trim(); + + xhtr.onreadystatechange = function() { + if (xhtr.readyState === 4) { + let status = xhtr.status; + stats.add(`ua_${api.mode.get_lower()}_print_grid_${status}`); + if (status >= 200 && status < 300) { + let json = js2o(xhtr.responseText); + gridhost_tracker(host,json.key); + api.ajax(host+"/api/wait?key="+json.key, function(data) { + data = js2o(data); + console.log(data); + api.show.alert("print to "+target+": "+data.status, 600); + }); + } else { + api.show.alert("grid:host error\nstatus: "+status+"\nmessage: "+xhtr.responseText, 10000); + } + api.show.progress(0); + } + }; + xhtr.upload.addEventListener('progress', function(evt) { + api.show.progress(evt.loaded/evt.total, "sending"); + }); + filename = $('print-filename').value; + xhtr.open("POST", + host + "/api/print?" + + "filename=" + filename + + "&target=" + target + + "&key=" + apik + + "&time=" + Math.round(info.time) + + "&length=" + Math.round(info.distance) + + "&image=" + filename + ); + xhtr.setRequestHeader("Content-Type", "text/plain"); + let snapshot = api.view.snapshot; + xhtr.send(snapshot ? [gcode,snapshot].join("\0") : gcode); + api.modal.hide(); + } + + function download() { + filename = $('print-filename').value; + api.util.download(gcode, filename + "." + fileext); + // saveAs(getBlob(), filename + "." + fileext); + } + + function pad(v) { + v = v.toString(); + return v.length < 2 ? '0' + v : v; + } + + function calcWeight() { + try { + let density = $('print-density'); + $('print-weight').value = ( + (Math.PI * util.sqr( + info.settings.device.extruders[0].extFilament / 2 + )) * + info.distance * + (parseFloat(density.value) || 1.25) / + 1000 + ).toFixed(2); + density.onkeyup = (ev) => { + if (ev.key === 'Enter') calcWeight(); + }; + } catch (e) { } + } + + function calcTime() { + let floor = Math.floor, + time = floor(info.time), + hours = floor(time / 3600), + newtime = time - hours * 3600, + mins = floor(newtime / 60), + secs = newtime - mins * 60; + + $('output-time').value = [pad(hours),pad(mins),pad(secs)].join(':'); + } + + fetch("/kiri/output-gcode.html").then(r => r.text()).then(html => { + ui.print.innerHTML = html; + let set = api.conf.get(); + let fdm = MODE === MODES.FDM; + let octo = set.controller.exportOcto && MODE !== MODES.CAM; + let ghost = set.controller.exportGhost; + let local = set.controller.exportLocal; + let preview = set.controller.exportPreview; + $('code-preview-head').style.display = preview ? '' : 'none'; + $('code-preview').style.display = preview ? '' : 'none'; + $('print-download').onclick = download; + $('print-filament-head').style.display = fdm ? '' : 'none'; + $('print-filament-info').style.display = fdm ? '' : 'none'; + $('print-filename').value = filename; + $('print-filesize').value = util.comma(info.bytes); + $('print-filament').value = Math.round(info.distance); + calcTime(); + if (fdm) { + calcWeight(); + } + + // in cam mode, show zip file option + let downloadZip = $('print-zip'); + downloadZip.style.display = sections ? 'flex' : 'none'; + downloadZip.onclick = function() { + let files = []; + for (let [ name, data ] of Object.entries(sections)) { + if (name.indexOf('op-') === 0) { + let head = sections.header || []; + let foot = sections.footer || []; + files.push({ + name: `${name}.${fileext}`, + data: [ ...head, ...data, ...foot ].join('\r\n') + }) + } + } + kiri.client.zip(files, progress => { + api.show.progress(progress.percent/100, "generating zip files"); + }, output => { + api.show.progress(0); + api.util.download(output, `${$('print-filename').value}.zip`); + }) + }; + + // in palette mode, show download button + let downloadPalette = $('print-palette'); + downloadPalette.style.display = info.segments ? 'flex' : 'none'; + // generate MAFX downloadble file + if (info.segments) { + // todo: reduce segments to eliminate 0 lenghts and transitions before 150mm + let { settings, segments } = info; + let { device, bounds } = settings; + let { min, max } = bounds; + let extras = device.extras || {}; + let pinfo = extras.palette || {}; + // filter pings to those occuring after all tubes combined + let pings = info.purges || []; + let driveInfo = {}; + let volume = {}; + let length = {}; + // clean up and round pings + pings.forEach(p => { + p.length = p.length.round(3); + // p.length = (p.length - pinfo.offset).round(2); + // p.length = (p.length + pinfo.offset).round(2); + p.extrusion = p.extrusion.round(3); + }); + // add length of push filament to the last segment + segments.peek().emitted += pinfo.push; + let lastEmit = 0; + let ratioVolume = (0.4 * 0.4) / (1.75 * 1.75); + for (let seg of segments) { + let seginfo = driveInfo[seg.tool] = driveInfo[seg.tool] || { length: 0, volume: 0 }; + seg.emitted += pinfo.offset; + seginfo.length += seg.emitted - lastEmit; + seginfo.volume = seginfo.length * ratioVolume; + volume[seg.tool+1] = seginfo.volume.round(2); + length[seg.tool+1] = seginfo.length.round(2); + lastEmit = seg.emitted; + } + let totalLength = Object.values(length).reduce((a,v) => a+v).round(2); + let totalVolume = Object.values(volume).reduce((a,v) => a+v).round(2); + // console.log({info, device, pinfo, segments, volume, length, totalLength}); + let meta = { + version: "3.2", + printerProfile: { + id: pinfo.printer, + name: device.deviceName || "My Printer" + }, + preheatTemperature: { nozzle: [0], bed: 0 }, + paletteNozzle: 0, + time: info.time.round(1), + volume, + length, + totalLength, + totalVolume, + inputsUsed: Object.keys(driveInfo).length, + splices: segments.length, + pings: pings.length, + boundingBox: { + min: [ min.x, min.y, min.z ], + max: [ max.x, max.y, max.z ] + }, + filaments: Object.keys(driveInfo).map(v => { return { + name: `Color${v}`, + type: "Filament", + color: `#${v}0${v}0${v}0`, + materialId: parseInt(v) + 1, + filamentId: parseInt(v) + 1 + }}), + }; + let lastDrive; + let algokeys = {}; + let algorithms = []; + let defaultSplice = { + compression: pinfo.press, + cooling: pinfo.cool, + heat: pinfo.heat + }; + for (let key of Object.keys(driveInfo)) { + key = parseInt(key) + 1; + algorithms.push({ + ingoingId: key, + outgoingId: key, + ...defaultSplice + }); + } + let palette = { + version: "3.0", + drives: [0, 0, 0, 0, 0, 0, 0, 0].map((v,i) => { + return driveInfo[i] ? i+1 : 0 + }), + splices: segments.filter(r => { + return r.emitted >= 150; + }).map(r => { + if (lastDrive >= 0 && lastDrive !== r.tool) { + let key = `${lastDrive}+${r.tool}`; + if (!algokeys[key]) { + let rec = algokeys[key] = { + ingoingId: lastDrive + 1, + outgoingId: r.tool + 1, + ...defaultSplice + }; + algorithms.push(rec); + } + } + lastDrive = r.tool; + return { id: r.tool + 1, length: r.emitted.round(2) } + }), + pings, + algorithms + }; + let png; + kiri.client.png({}, data => { + png = data.png; + }); + console.log({meta,palette}); + downloadPalette.onclick = function() { + kiri.client.zip([ + {name:"meta.json", data:JSON.stringify(meta,undefined,4)}, + {name:"palette.json", data:JSON.stringify(palette,undefined,4)}, + {name:"thumbnail.png", data:png.buffer} + ], progress => { + api.show.progress(progress.percent/100, "generating palette files"); + }, output => { + api.show.progress(0); + api.util.download(output, `${$('print-filename').value}.mafx`); + }) + }; + } + + // octoprint setup + $('send-to-octohead').style.display = octo ? '' : 'none'; + $('send-to-octoprint').style.display = octo ? '' : 'none'; + if (octo) try { + $('print-octoprint').onclick = sendto_octoprint; + octo_host = $('octo-host'); + octo_apik = $('octo-apik'); + if (MODE === MODES.CAM) { + $('send-to-octohead').style.display = 'none'; + $('send-to-octoprint').style.display = 'none'; + } else { + $('send-to-octohead').style.display = ''; + $('send-to-octoprint').style.display = ''; + } + // hide octoprint when hard-coded in the url + if (api.const.OCTO) { + $('ophost').style.display = 'none'; + $('opapik').style.display = 'none'; + $('oph1nt').style.display = 'none'; + $('send-to-gridhost').style.display = 'none'; + } + octo_host.value = local['octo-host'] || ''; + octo_apik.value = local['octo-apik'] || ''; + } catch (e) { console.log(e) } + + // grid:host setup + $('send-to-hosthead').style.display = ghost ? '' : 'none'; + $('send-to-gridhost').style.display = ghost ? '' : 'none'; + if (ghost) try { + $('print-gridhost').onclick = sendto_gridhost; + $('grid-host').onkeyup = gridhost_probe; + $('grid-apik').onkeyup = gridhost_probe; + grid_host = $('grid-host'); + grid_apik = $('grid-apik'); + grid_target = $('grid-target'); + grid_target.onchange = function(ev) { + local['grid-target'] = grid_targets[grid_target.selectedIndex]; + }; + grid_host.value = local['grid-host'] || ''; + grid_apik.value = local['grid-apik'] || ''; + gridhost_probe(); + } catch (e) { console.log(e) } + + // grid:local setup + if (local) try { + gridlocal_probe(); + $('print-gridlocal').onclick = sendto_gridlocal; + $('admin-gridlocal').onclick = admin_gridlocal; + } catch (e) { console.log(e) } + + // preview of the generated GCODE (first 64k max) + if (preview && gcode) $('code-preview-textarea').value = gcode.substring(0,65535); + + // show dialog + api.modal.show('print'); + }); +} + +}); diff --git a/src/kiri/files.js b/src/kiri/files.js index 7a16be9f..e5c369a2 100644 --- a/src/kiri/files.js +++ b/src/kiri/files.js @@ -2,221 +2,223 @@ "use strict"; -(function() { +// dep: main.kiri +// use: data.index +gapp.register("kiri.files", [], (root, exports) => { - const { kiri } = self; +const { kiri } = self; - class Files { - constructor(indexdb, options) { - let store = this; - this.db = indexdb; - this.files = {}; - this.listeners = []; - this.options = options || {}; - this.deferredHandler = null; - this.concurrent = 0; - this.refresh(); - } - - setOptions(options) { - this.options = options; - return this; - } - - get index() { - return this.db; - } - - refresh() { - let store = this; - store.db.get('files', function(files) { - if (files) { - store.files = files; - notifyFileListeners(store); - } - }); - }; - - wipe() { - let key, files = this.files; - for (key in files) { - if (files.hasOwnProperty(key)) this.deleteFile(key); - } - }; - - fileList() { - return this.files; - }; - - addFileListener(listener) { - if (!this.listeners.contains(listener)) { - this.listeners.push(listener); - listener(this.files); - } - }; - - removeFileListener(listener) { - this.listeners.remove(listener); - }; - - decimate(vertices, callback) { - let options = this.options; - if (vertices.length < (options.threshold || 500000)) { - return callback(vertices); - } - kiri.client.decimate(vertices, this.options, function(reply) { - callback(reply); - }); - }; - - setDeferredHandler(handler) { - this.deferredHandler = handler; - }; - - putDeferred(name, mark) { - // triggers refresh callback - this.files[name] = { - deferred: mark - }; - saveFileList(this); - }; - - /** - * @param {String} name - * @param {Float32Array} vertices - * @param {Function} [callback] - */ - putFile(name, vertices, callback) { - let mark = Date.now(); - let store = this; - let pdb = store.db.promise(); - store.concurrent++; - - function ondone(error) { - if (callback) callback(error); - if (--store.concurrent === 0) { - notifyFileListeners(store); - } - } - - pdb.put(`file-${name}`, vertices) - .then(() => { - store.files[name] = { - vertices: vertices.length / 3, - updated: new Date().getTime() - }; - return pdb.put('files', store.files); - }) - .then(() => { - return new Promise((resolve,reject) => { - store.decimate(vertices, resolve); - }); - }) - .then(decimated => { - return pdb.put(`fdec-${name}`, decimated) - }) - .then(ondone) - .catch(ondone); - }; - - rename(name, newname, callback) { - if (!this.files[name]) return callback({error: 'no such file'}); - if (!newname || newname == name) return callback({error: 'invalid new name'}); - let done = 0; - let error = []; - let store = this; - function complete(ok, err) { - if (err) error.push(err); - if (++done === 2) { - store.files[newname] = store.files[name]; - delete store.files[name]; - saveFileList(store); - store.db.remove(`fdec-${name}`); - store.db.remove(`file-${name}`); - callback(error.length ? {error} : {}); - } - } - store.db.get(`fdec-${name}`, (vertices) => { - if (!vertices) return complete(false, 'no decimation'); - store.db.put(`fdec-${newname}`, vertices, complete); - }); - store.db.get(`file-${name}`, (vertices) => { - if (!vertices) return complete(false, 'no raw file'); - store.db.put(`file-${newname}`, vertices, complete); - }); - }; - - /** - * @param {String} name - * @param {Function} callback - */ - getFile(name, callback) { - let store = this, - rec = store.files[name]; - if (rec && rec.deferred) { - if (store.deferredHandler) return store.deferredHandler(rec.deferred, name, callback); - return callback(); - } - this.db.get('fdec-'+name, function(vertices) { - if (vertices) { - callback(vertices); - } else { - store.db.get('file-'+name, function(vertices) { - if (vertices) { - store.decimate(vertices, function(decimated) { - store.db.put('fdec-'+name, decimated); - callback(vertices); - }); - } else { - return callback(); - } - }); - } - }); - }; - - /** - * @param {String} name - * @param {Function} callback - */ - deleteFile(name, callback) { - let store = this; - if (store.files[name]) { - delete store.files[name]; - store.db.remove('fdec-'+name); - store.db.remove('file-'+name, function(ok) { - saveFileList(store); - if (callback) callback(ok); - }); - return; - } - if (callback) callback(false); - }; - - deleteFilter(fn, from, to) { - this.db.keys(keys => { - for (let key of keys) { - if (fn(key)) { - this.db.remove(key); - } - } - }, from, to); - } +class Files { + constructor(indexdb, options) { + let store = this; + this.db = indexdb; + this.files = {}; + this.listeners = []; + this.options = options || {}; + this.deferredHandler = null; + this.concurrent = 0; + this.refresh(); } - function saveFileList(store) { - store.db.put('files', store.files); - notifyFileListeners(store); + setOptions(options) { + this.options = options; + return this; } - function notifyFileListeners(store) { - for (let i=0; i { + store.files[name] = { + vertices: vertices.length / 3, + updated: new Date().getTime() + }; + return pdb.put('files', store.files); + }) + .then(() => { + return new Promise((resolve,reject) => { + store.decimate(vertices, resolve); + }); + }) + .then(decimated => { + return pdb.put(`fdec-${name}`, decimated) + }) + .then(ondone) + .catch(ondone); + }; + + rename(name, newname, callback) { + if (!this.files[name]) return callback({error: 'no such file'}); + if (!newname || newname == name) return callback({error: 'invalid new name'}); + let done = 0; + let error = []; + let store = this; + function complete(ok, err) { + if (err) error.push(err); + if (++done === 2) { + store.files[newname] = store.files[name]; + delete store.files[name]; + saveFileList(store); + store.db.remove(`fdec-${name}`); + store.db.remove(`file-${name}`); + callback(error.length ? {error} : {}); + } + } + store.db.get(`fdec-${name}`, (vertices) => { + if (!vertices) return complete(false, 'no decimation'); + store.db.put(`fdec-${newname}`, vertices, complete); + }); + store.db.get(`file-${name}`, (vertices) => { + if (!vertices) return complete(false, 'no raw file'); + store.db.put(`file-${newname}`, vertices, complete); + }); + }; + + /** + * @param {String} name + * @param {Function} callback + */ + getFile(name, callback) { + let store = this, + rec = store.files[name]; + if (rec && rec.deferred) { + if (store.deferredHandler) return store.deferredHandler(rec.deferred, name, callback); + return callback(); + } + this.db.get('fdec-'+name, function(vertices) { + if (vertices) { + callback(vertices); + } else { + store.db.get('file-'+name, function(vertices) { + if (vertices) { + store.decimate(vertices, function(decimated) { + store.db.put('fdec-'+name, decimated); + callback(vertices); + }); + } else { + return callback(); + } + }); + } + }); + }; + + /** + * @param {String} name + * @param {Function} callback + */ + deleteFile(name, callback) { + let store = this; + if (store.files[name]) { + delete store.files[name]; + store.db.remove('fdec-'+name); + store.db.remove('file-'+name, function(ok) { + saveFileList(store); + if (callback) callback(ok); + }); + return; + } + if (callback) callback(false); + }; + + deleteFilter(fn, from, to) { + this.db.keys(keys => { + for (let key of keys) { + if (fn(key)) { + this.db.remove(key); + } + } + }, from, to); + } +} + +function saveFileList(store) { + store.db.put('files', store.files); + notifyFileListeners(store); +} + +function notifyFileListeners(store) { + for (let i=0; i { // add frame message api listener window.addEventListener('message', msg => { @@ -113,4 +120,4 @@ window.addEventListener('message', msg => { } }); -})(); +}); diff --git a/src/kiri/function.js b/src/kiri/function.js index a8f605aa..1c08040c 100644 --- a/src/kiri/function.js +++ b/src/kiri/function.js @@ -2,9 +2,11 @@ "use strict"; -(function () { +// dep: kiri.api +// dep: moto.space +gapp.register("kiri.function", [], (root, exports) => { -const { data, kiri, noop } = self; +const { kiri, noop } = root; const { api, client, consts, utils } = kiri; const { space } = moto; const { COLOR, PMODES } = consts; @@ -59,6 +61,11 @@ function prepareSlices(callback, scale = 1, offset = 0) { const slicing = api.widgets.all() .slice() .filter(w => !w.track.ignore && !w.meta.disabled); + + if (slicing.length === 0) { + return api.show.alert('nothing to slice'); + } + const totalv = slicing.map(w => w.getVertices().count).reduce((a,v) => a + v); const defvert = totalv / slicing.length; const track = {}; @@ -413,4 +420,4 @@ const functions = api.function = { clear_progress() { complete = {} } }; -})(); +}); diff --git a/src/kiri/init.js b/src/kiri/init.js index 814abd82..80180aac 100644 --- a/src/kiri/init.js +++ b/src/kiri/init.js @@ -2,11 +2,15 @@ "use strict"; -(function() { +// dep: geo.base +// dep: kiri.api +// dep: kiri.stats +// dep: kiri.consts +// dep: kiri.platform +// dep: kiri.selection +gapp.register("kiri.init", [], (root, exports) => { - if (self.kiri.init) return; - - const { base, kiri, moto } = self; + const { base, kiri } = root; const { api, catalog, conf, consts, space } = kiri; const { sdb, stats, js2o, o2js, platform, selection, ui, uc } = api; const { VIEWS, MODES, SEED } = consts; @@ -2287,11 +2291,9 @@ uc.hoverPop(ui.ltact, { group: hpops, target: $('pop-slice') }); uc.hoverPop(ui.render, { group: hpops, target: $('pop-render'), sticky: false }); uc.hoverPop(ui.edit, { group: hpops, target: $('pop-tools'), sticky: false }); - // uc.hoverPop(ui.rotate, { group: hpops, target: $('pop-rotate'), sticky: true }); - // uc.hoverPop(ui.scale, { group: hpops, target: $('pop-scale'), sticky: true }); uc.hoverPop(ui.nozzle, { group: hpops, target: $('pop-nozzle'), sticky: true }); uc.hoverPop($('app-acct'), { group: hpops, target: $('acct-pop') } ); - uc.hoverPop($('app-mode'), { group: hpops, target: $('mode-info') } ); + // uc.hoverPop($('app-mode'), { group: hpops, target: $('mode-info') } ); uc.hoverPop($('app-name'), { group: hpops, target: $('app-info') } ); uc.onBlur([ @@ -2925,4 +2927,4 @@ } } -})(); +}); diff --git a/src/kiri/lang.js b/src/kiri/lang.js index a7ed2428..8b72c10e 100644 --- a/src/kiri/lang.js +++ b/src/kiri/lang.js @@ -2,81 +2,80 @@ "use strict"; -(function() { +gapp.register("kiri.lang", [], (root, exports) => { - if (self.kiri.lang) return; +const { kiri } = root; +const LANG = kiri.lang = { current: {} }; +const KDFL = 'en-us'; - const KIRI = self.kiri, LANG = self.kiri.lang = { current: {} }; - const KDFL = 'en-us'; +let lset = navigator.language.toLocaleLowerCase(); - let lset = navigator.language.toLocaleLowerCase(); - - LANG.map = function(key) { - if (!key) { - return KDFL; - } - let tok = key.split('-'); - switch (tok[0]) { - case 'da': return 'da-dk'; - case 'de': return 'de-de'; - case 'en': return 'en-us'; - case 'es': return 'es-es'; - case 'fr': return 'fr-fr'; - case 'pl': return 'pl-pl'; - case 'pt': return 'pt-pt'; - case 'zh': return 'zh'; - } +LANG.map = function(key) { + if (!key) { return KDFL; - }; - - LANG.get = function() { - return lset; - }; - - LANG.set = function() { - let map, key, keys = [...arguments]; - // provide default if none given - if (keys.length === 0) { - keys = [lset, lset.split('-')[0], 'en']; - } - for (let i=0; i { - if (!map[key]) { - map[key] = LANG.en[key]; - missing.push(key); - } - }); - // update current map from chosen map - Object.keys(map).forEach(key => { - if (LANG.en[key]) { - let val = map[key]; - // turn arrays into newline separated strings - if (Array.isArray(val)) { - val = val.join('\n'); - } - LANG.current[key] = val; - } else { - invalid.push(key); - } - }); - if (missing.length) { - console.log(`language map "${key}" missing keys [${missing.length}]: ${missing.join(', ')}`); - } - if (invalid.length) { - console.log(`language map "${key}" invalid keys [${invalid.length}]: ${invalid.join(', ')}`); - } - return key; - } - return undefined; } + let tok = key.split('-'); + switch (tok[0]) { + case 'da': return 'da-dk'; + case 'de': return 'de-de'; + case 'en': return 'en-us'; + case 'es': return 'es-es'; + case 'fr': return 'fr-fr'; + case 'pl': return 'pl-pl'; + case 'pt': return 'pt-pt'; + case 'zh': return 'zh'; + } + return KDFL; +}; -})(); +LANG.get = function() { + return lset; +}; + +LANG.set = function() { + let map, key, keys = [...arguments]; + // provide default if none given + if (keys.length === 0) { + keys = [lset, lset.split('-')[0], 'en']; + } + for (let i=0; i { + if (!map[key]) { + map[key] = LANG.en[key]; + missing.push(key); + } + }); + // update current map from chosen map + Object.keys(map).forEach(key => { + if (LANG.en[key]) { + let val = map[key]; + // turn arrays into newline separated strings + if (Array.isArray(val)) { + val = val.join('\n'); + } + LANG.current[key] = val; + } else { + invalid.push(key); + } + }); + if (missing.length) { + console.log(`language map "${key}" missing keys [${missing.length}]: ${missing.join(', ')}`); + } + if (invalid.length) { + console.log(`language map "${key}" invalid keys [${invalid.length}]: ${invalid.join(', ')}`); + } + return key; + } + return undefined; +} + +}); diff --git a/src/kiri/layers.js b/src/kiri/layers.js index 0d9c713f..c9f11910 100644 --- a/src/kiri/layers.js +++ b/src/kiri/layers.js @@ -2,354 +2,359 @@ "use strict"; -(function() { - let KIRI = self.kiri, - BASE = self.base, - POLY = BASE.polygons, - newPolygon = BASE.newPolygon; +// dep: geo.base +// dep: geo.polygon +// dep: geo.polygons +gapp.register("kiri.layers", [], (root, exports) => { - class Layers { - constructor() { - this.init(); - } +const { base, kiri } = root; +const { polygons, newPolygon } = base; - init() { - this.layers = {}; - this.profiles = {}; - this.stats = { - contour: 0, - flat_line: 0, - flat_poly: 0, - line_poly: 0, - line: 0 +const POLY = base.polygons; + +class Layers { + constructor() { + this.init(); + } + + init() { + this.layers = {}; + this.profiles = {}; + this.stats = { + contour: 0, + flat_line: 0, + flat_poly: 0, + line_poly: 0, + line: 0 + }; + } + + getLayer(layer) { + return this.layers[layer]; + } + + setLayer(layer, colors, off) { + let layers = this.layers; + if (typeof(colors) === 'number') { + colors = { + line: colors, + face: colors, + opacity: 1 }; } + this.current = layers[layer] = layers[layer] || { + off: off === true, + lines: [], + polys: [], // colors are an attribute on polygons + faces: [], + cface: undefined, // face color indices + paths: [], + cpath: undefined, // path colors indices + color: colors || { + check: 0, + line: 0, + face: 0, + opacity: 1 + }, + }; + return this; + } - getLayer(layer) { - return this.layers[layer]; - } + addLine(p1, p2) { + this.current.lines.push(p1, p2); + return this; + } - setLayer(layer, colors, off) { - let layers = this.layers; - if (typeof(colors) === 'number') { - colors = { - line: colors, - face: colors, - opacity: 1 - }; - } - this.current = layers[layer] = layers[layer] || { - off: off === true, - lines: [], - polys: [], // colors are an attribute on polygons - faces: [], - cface: undefined, // face color indices - paths: [], - cpath: undefined, // path colors indices - color: colors || { - check: 0, - line: 0, - face: 0, - opacity: 1 - }, - }; - return this; - } - - addLine(p1, p2) { - this.current.lines.push(p1, p2); - return this; - } - - addLines(lines, options) { - if (options) { - options.open = true; - const polys = []; - for (let i=0; i p.clean(true)); } - - addPolys(polys, options) { - if (polys.length === 0) { - return this; - } - if (options && options.clean) { - polys = polys.map(p => p.clean(true)); - } - if (options && options.flat) { - return this.addFlats(polys, options); - } - if (options && !options.thin) { - return this.addPaths(polys, options); - } - polys = flat(polys); - if (options) { - polys.forEach(p => { - if (options.z !== undefined) { - p.setZ(options.z); - } - switch (typeof(options.color)) { - case 'number': p.color = options.color; break; - case 'object': p.color = options.color.line; break; - } - }); - } - this.current.polys.appendAll(polys); - this.stats.line_poly += polys.length; - return this; + if (options && options.flat) { + return this.addFlats(polys, options); } + if (options && !options.thin) { + return this.addPaths(polys, options); + } + polys = flat(polys); + if (options) { + polys.forEach(p => { + if (options.z !== undefined) { + p.setZ(options.z); + } + switch (typeof(options.color)) { + case 'number': p.color = options.color; break; + case 'object': p.color = options.color.line; break; + } + }); + } + this.current.polys.appendAll(polys); + this.stats.line_poly += polys.length; + return this; + } - // z planar closed polygonal areas - addAreas(polys, options) { - const faces = this.current.faces; - polys = Array.isArray(polys) ? polys : [ polys ]; - polys.forEach(poly => { + // z planar closed polygonal areas + addAreas(polys, options) { + const faces = this.current.faces; + polys = Array.isArray(polys) ? polys : [ polys ]; + polys.forEach(poly => { + poly.earcut().forEach(ep => { + ep.forEachPoint(p => { faces.push(p.x, p.y, p.z) }); + }); + }); + if (options && options.outline) { + this.addPolys(polys.clone(true)); + } + } + + // misleading name. these are flat offset paths (old style rendering) + addFlats(polys, options) { + const opts = options || {}; + const offset = opts.offset || 1; + polys = flat(polys); + if (!polys.length) { + return; + } + const z = polys[0].getZ(), faces = this.current.faces; + const off_opt = { z, flat: true }; + polys.forEach(poly => { + let exp = off_opt.outs = []; + if (poly.isOpen()) { + // shorten line by offset + const pp = poly.points, pl = pp.length; + pp[0] = pp[0].offsetPointTo(pp[1], offset); + pp[pl-1] = pp[pl-1].offsetPointTo(pp[pl-2], offset); + exp.appendAll(POLY.expand_lines(poly, offset * 0.9, z)); + // force re-nest if offsets cause ends to join + exp = POLY.flatten(exp,[],true); + this.stats.flat_line = 0; + } else if (offset) { + POLY.offset([poly], offset * 0.9, off_opt); + POLY.offset([poly], -offset * 0.9, off_opt); + this.stats.flat_poly = 0; + } + if (opts.outline) { + this.addPolys(exp.clone()); + } + const faceidx = faces.length / 3; + POLY.nest(exp).forEach((poly,i) => { poly.earcut().forEach(ep => { ep.forEachPoint(p => { faces.push(p.x, p.y, p.z) }); }); }); - if (options && options.outline) { - this.addPolys(polys.clone(true)); + const cur = this.current; + const color = opts.color ? + (typeof(opts.color) === 'number' ? { line: opts.color, face: opts.color } : opts.color) : + cur.color; + if (!cur.cface) { + cur.cface = [ Object.assign({ start: faceidx, count: Infinity }, color) ]; + } else { + // rewrite last color count if color or opacity have changed + const pc = cur.cface[cur.cface.length - 1]; + if (pc.face !== color.face || pc.opacity !== color.opacity) { + pc.count = faceidx; + cur.cface.push(Object.assign({ start: faceidx, count: Infinity }, color)); + } } + }); + return this; + } + + addPaths(polys, options) { + const opts = options || {}; + const height = opts.height || 1; + const offset = opts.offset || 1; + polys = flat(polys); + if (!polys.length) { + return; } - // misleading name. these are flat offset paths (old style rendering) - addFlats(polys, options) { - const opts = options || {}; - const offset = opts.offset || 1; - polys = flat(polys); - if (!polys.length) { - return; - } - const z = polys[0].getZ(), faces = this.current.faces; - const off_opt = { z, flat: true }; - polys.forEach(poly => { - let exp = off_opt.outs = []; - if (poly.isOpen()) { - // shorten line by offset - const pp = poly.points, pl = pp.length; - pp[0] = pp[0].offsetPointTo(pp[1], offset); - pp[pl-1] = pp[pl-1].offsetPointTo(pp[pl-2], offset); - exp.appendAll(POLY.expand_lines(poly, offset * 0.9, z)); - // force re-nest if offsets cause ends to join - exp = POLY.flatten(exp,[],true); - this.stats.flat_line = 0; - } else if (offset) { - POLY.offset([poly], offset * 0.9, off_opt); - POLY.offset([poly], -offset * 0.9, off_opt); - this.stats.flat_poly = 0; + const profiles = this.profiles; + const prokey = `${offset}x${height}`; + if (!profiles[prokey]) { + const profile = new THREE.Shape(); + profile.moveTo(-offset, -height); + profile.lineTo(-offset, height); + profile.lineTo( offset, height); + profile.lineTo( offset, -height); + profiles[prokey] = profile; + } + const profile = profiles[prokey].clone(); + + polys.forEach(poly => { + const contour = []; + poly = poly.debur(0.05); + if (!poly) return; + poly = poly.miter(); + poly.points.forEach(p => { + contour.push(new THREE.Vector2(p.x, p.y)); + }); + const {index, faces} = ProfiledContourGeometry(profile, contour, poly.isClosed()); + const cur = this.current; + const one = cur.paths[0]; + if (one) { + // merge all contour geometry for massive speed gain + const add = one.faces.length / 3; + for (let i=0; i { - poly.earcut().forEach(ep => { - ep.forEachPoint(p => { faces.push(p.x, p.y, p.z) }); - }); - }); - const cur = this.current; - const color = opts.color ? - (typeof(opts.color) === 'number' ? { line: opts.color, face: opts.color } : opts.color) : - cur.color; - if (!cur.cface) { - cur.cface = [ Object.assign({ start: faceidx, count: Infinity }, color) ]; - } else { + const feces = new Float32Array(one.faces.length + faces.length); + const indln = one.index.length; + feces.set(one.faces); + feces.set(faces, one.faces.length); + one.faces = feces; + one.index.appendAll(index); + // allow changing colors + if (opts.color) { + if (!cur.cpath) { + cur.cpath = [ Object.assign({ start: 0, count: indln - 1 }, cur.color) ]; + } // rewrite last color count if color or opacity have changed - const pc = cur.cface[cur.cface.length - 1]; - if (pc.face !== color.face || pc.opacity !== color.opacity) { - pc.count = faceidx; - cur.cface.push(Object.assign({ start: faceidx, count: Infinity }, color)); + const pc = cur.cpath[cur.cpath.length - 1]; + if (pc.face !== opts.color.face || pc.opacity !== opts.color.opacity) { + pc.count = indln; + cur.cpath.push(Object.assign({ start: indln, count: Infinity }, opts.color)); } } - }); - return this; - } - - addPaths(polys, options) { - const opts = options || {}; - const height = opts.height || 1; - const offset = opts.offset || 1; - polys = flat(polys); - if (!polys.length) { - return; - } - - const profiles = this.profiles; - const prokey = `${offset}x${height}`; - if (!profiles[prokey]) { - const profile = new THREE.Shape(); - profile.moveTo(-offset, -height); - profile.lineTo(-offset, height); - profile.lineTo( offset, height); - profile.lineTo( offset, -height); - profiles[prokey] = profile; - } - const profile = profiles[prokey].clone(); - - polys.forEach(poly => { - const contour = []; - poly = poly.debur(0.05); - if (!poly) return; - poly = poly.miter(); - poly.points.forEach(p => { - contour.push(new THREE.Vector2(p.x, p.y)); - }); - const {index, faces} = ProfiledContourGeometry(profile, contour, poly.isClosed()); - const cur = this.current; - const one = cur.paths[0]; - if (one) { - // merge all contour geometry for massive speed gain - const add = one.faces.length / 3; - for (let i=0; i { - const { base, data, load, kiri, moto, noop } = self; + const { base, data, load, kiri, moto, noop } = root; const { api, consts, lang, Widget, newWidget, utils, stats } = kiri; const { areEqual, parseOpt, encodeOpt, ajax, o2js, js2o, ls2o } = utils; const { feature, platform, selection, settings } = api; @@ -943,4 +943,5 @@ // upon restore, seed presets api.event.emit('preset', api.conf.dbo()); -})(); + +}); diff --git a/src/kiri/pack.js b/src/kiri/pack.js index cb25ac74..4bfde62f 100644 --- a/src/kiri/pack.js +++ b/src/kiri/pack.js @@ -7,24 +7,12 @@ * http://codeincomplete.com/posts/2011/5/7/bin_packing/ */ -(function () { +gapp.register("kiri.pack", [], (root, exports) => { - let kiri = self.kiri = self.kiri || {}; - if (kiri.Pack) return; +const { kiri } = root; - kiri.Pack = Packer; - - kiri.Sort = function (a, b) { - let aa = (a.w * a.h); - let ab = (b.w * b.h); - if (Math.abs(aa-ab) < 1) { - return (b.w / b.h) - (a.w / a.h); - } else { - return ab - aa; - } - }; - - function Packer(w, h, spacing) { +class Packer { + constructor(w, h, spacing) { this.root = { x: 0, y: 0, w: w, h: h }; this.max = { w: 0, h: 0 }; this.packed = false; @@ -32,68 +20,77 @@ this.pad = this.spacing / 2; } - Packer.prototype = { - - simple: function (blocks) { - let n = 0, block, x = 0, y = 0, mh = 0; - while (n < blocks.length) { - block = blocks[n++]; - block.fit = { x, y }; - this.max.w = Math.max(this.max.w, x + block.w); - this.max.h = Math.max(this.max.h, y + block.h); - mh = Math.max(mh, block.h); - x += block.w + this.spacing; - if (x > this.root.w) { - x = 0; - y += mh + this.spacing; - mh = 0; - } - if (y > this.root.h) { - return this; - } + simple(blocks) { + let n = 0, block, x = 0, y = 0, mh = 0; + while (n < blocks.length) { + block = blocks[n++]; + block.fit = { x, y }; + this.max.w = Math.max(this.max.w, x + block.w); + this.max.h = Math.max(this.max.h, y + block.h); + mh = Math.max(mh, block.h); + x += block.w + this.spacing; + if (x > this.root.w) { + x = 0; + y += mh + this.spacing; + mh = 0; } - this.packed = true; - return this; - }, - - fit: function (blocks, simple) { - if (simple) { - return this.simple(blocks); + if (y > this.root.h) { + return this; } - let n = 0, node, block, w, h; - while (n < blocks.length) { - block = blocks[n++]; - w = block.w + this.spacing; - h = block.h + this.spacing; - if (node = this.findNode(this.root, w, h)) { - block.fit = this.splitNode(node, w, h); - } else { - return this; - } - } - this.packed = true; - return this; - }, - - findNode: function (root, w, h) { - if (root.used) { - return this.findNode(root.right, w, h) || this.findNode(root.down, w, h); - } else if (w <= root.w && h <= root.h) { - return root; - } else { - return null; - } - }, - - splitNode: function (node, w, h) { - node.used = true; - node.down = { x: node.x, y: node.y + h, w: node.w, h: node.h - h }; - node.right = { x: node.x + w, y: node.y, w: node.w - w, h: h }; - this.max.w = Math.max(this.max.w, node.x + w); - this.max.h = Math.max(this.max.h, node.y + h); - return node; } + this.packed = true; + return this; + } - }; + fit(blocks, simple) { + if (simple) { + return this.simple(blocks); + } + let n = 0, node, block, w, h; + while (n < blocks.length) { + block = blocks[n++]; + w = block.w + this.spacing; + h = block.h + this.spacing; + if (node = this.findNode(this.root, w, h)) { + block.fit = this.splitNode(node, w, h); + } else { + return this; + } + } + this.packed = true; + return this; + } -})(); + findNode(root, w, h) { + if (root.used) { + return this.findNode(root.right, w, h) || this.findNode(root.down, w, h); + } else if (w <= root.w && h <= root.h) { + return root; + } else { + return null; + } + } + + splitNode(node, w, h) { + node.used = true; + node.down = { x: node.x, y: node.y + h, w: node.w, h: node.h - h }; + node.right = { x: node.x + w, y: node.y, w: node.w - w, h: h }; + this.max.w = Math.max(this.max.w, node.x + w); + this.max.h = Math.max(this.max.h, node.y + h); + return node; + } +} + +// kiri.Sort = function (a, b) { +// let aa = (a.w * a.h); +// let ab = (b.w * b.h); +// if (Math.abs(aa - ab) < 1) { +// return (b.w / b.h) - (a.w / a.h); +// } else { +// return ab - aa; +// } +// }; + +kiri.Pack = Packer; + +}); diff --git a/src/kiri/platform.js b/src/kiri/platform.js index 63fb92b2..ae03e0b6 100644 --- a/src/kiri/platform.js +++ b/src/kiri/platform.js @@ -2,9 +2,14 @@ "use strict"; -(function () { +// dep: moto.space +// dep: kiri.api +// dep: kiri.consts +// dep: kiri.utils +// dep: kiri.widget +gapp.register("kiri.platform", [], (root, exports) => { -const { kiri, moto, load } = self; +const { kiri, moto, load } = root; const { api, consts, driver, utils, newWidget, Widget } = kiri; const { ajax, js2o } = utils; const { space } = moto; @@ -796,4 +801,4 @@ const platform = api.platform = { clear() { api.space.clear(); api.space.save(true) } }; -})(); +}); diff --git a/src/kiri/print.js b/src/kiri/print.js index ce7bb9ef..eefc325a 100644 --- a/src/kiri/print.js +++ b/src/kiri/print.js @@ -2,7 +2,12 @@ "use strict"; -(function() { +// dep: geo.base +// dep: geo.point +// dep: geo.polygon +// dep: geo.paths +// dep: kiri.consts +gapp.register("kiri.print", [], (root, evets) => { const { base, kiri } = self; const { paths, util, newPoint, Polygon } = base; @@ -10,10 +15,6 @@ const { tip2tipEmit } = paths; const { numOrDefault } = util; const { beltfact } = kiri.consts; -kiri.newPrint = function(settings, widgets, id) { - return new Print(settings, widgets, id); -}; - class Print { constructor(settings, widgets, id) { this.id = id || new Date().getTime().toString(36); @@ -523,7 +524,6 @@ class Print { done({ output: scope.output }); } - } class Output { @@ -550,6 +550,13 @@ class Output { } } -kiri.Print = Print; +function newPrint(settings, widgets, id) { + return new Print(settings, widgets, id); +}; -})(); +gapp.overlay(kiri, { + Print, + newPrint +}); + +}); diff --git a/src/kiri/selection.js b/src/kiri/selection.js index 2548cf75..6492f11d 100644 --- a/src/kiri/selection.js +++ b/src/kiri/selection.js @@ -2,9 +2,13 @@ "use strict"; -(function () { +// dep: moto.space +// dep: kiri.api +// dep: kiri.consts +// dep: kiri.utils +gapp.register("kiri.selection", [], (root, exports) => { -const { data, kiri, moto, noop } = self; +const { kiri, moto, noop } = self; const { api, consts, utils } = kiri; const { space } = moto; @@ -293,4 +297,4 @@ const selection = api.selection = { meshes() { return selectedMeshes.slice() }, }; -}()); +}); diff --git a/src/kiri/settings.js b/src/kiri/settings.js index aacd984c..93f317e5 100644 --- a/src/kiri/settings.js +++ b/src/kiri/settings.js @@ -2,7 +2,12 @@ "use strict"; -(function () { +// dep: kiri.api +// dep: kiri.conf +// dep: kiri.utils +// dep: moto.space +// dep: data.local +gapp.register("kiri.settings", [], (root, exports) => { const { data, kiri, moto, noop } = self; const { api, conf, consts, utils } = kiri; @@ -147,6 +152,7 @@ function updateSettings(opt = {}) { $('mode-device').innerText = device.deviceName; $('mode-profile').innerText = `${cproc[mode]}${same ? '' : ' *'}`; + $('mode-info').style.display = 'flex'; } function updateSettingsFromFields(setrec, uirec = api.ui, changes) { @@ -544,7 +550,7 @@ function settingsImport(data, ask) { if (isWork) { api.platform.clear(); kiri.codec.decode(data.work).forEach(widget => { - platform.add(widget, 0, true); + api.platform.add(widget, 0, true); }); if (data.view) { space.view.load(data.view); @@ -696,4 +702,4 @@ api.settings = { mode() { return settings.mode }, }; -})(); +}); diff --git a/src/kiri/slice.js b/src/kiri/slice.js index 4125ac5b..d46ec09a 100644 --- a/src/kiri/slice.js +++ b/src/kiri/slice.js @@ -2,30 +2,25 @@ "use strict"; -(function() { +// dep: geo.base +// dep: geo.polygons +gapp.register("kiri.slice", [], (root, exports) => { - const KIRI = self.kiri, - PRO = Slice.prototype, - BASE = self.base, - UTIL = BASE.util, - POLY = BASE.polygons, - NOKEY = BASE.key.NONE; +const { base, kiri } = root; +const { key, polygons } = base; +const { util } = base; - KIRI.Top = Top; - KIRI.Slice = Slice; - KIRI.newTop = newTop; - KIRI.newSlice = newSlice; +const POLY = base.polygons; +const NOKEY = key.NONE; - /** - * Object encapsulates a z-slice from an object. This code is shared by the - * client and the worker thread. As such, the view layers are ignored in the - * worker code paths. - * - * @param {number} z offset from ground - * @param {THREE.Group} [view] optional view parent object for layers - * @constructor - */ - function Slice(z, view) { +/** + * Object encapsulates a z-slice from an object. This code is shared by the + * client and the worker thread. As such, the view layers are ignored in the + * worker code paths. + */ +class Slice { + + constructor (z, view) { this.z = z; // z-index this.index = 0; // slice index this.lines = null; // slice raw @@ -38,63 +33,18 @@ this.layers = null; // will replace most of the layer output data } - /** - * Represents a top-level (outer) polygon in a slice. Slices may contain - * multiple tops each with nested structures. Top objects contain cached - * and computed objects for quick access for rendering and dependent computations. - * - * @param {Polygon} polygon - * @constructor - */ - function Top(polygon) { - this.poly = polygon; // outline poly - } - - Top.prototype.clone = function(deep) { - let top = new Top(this.poly.clone(deep)); - return top; - }; - - /** - * return innermost traces under a given top. for FDM, this represents - * the outline shell that the fill touches. used by Print, Laser - */ - Top.prototype.innerShells = function() { - let shells = this.shells, - array = []; - if (shells) shells.forEach(function(p) { - if (p.inner) array.appendAll(p.inner); - }); - return array; - }; - - /** - * Returns shell polygons of a given depth - * used by Print (FDM) - * - * @param {Polygon[]} out array to populate - * @returns {Polygon[]} array of top polygons - */ - Top.prototype.shellsAtDepth = function(depth) { - return this.shells ? this.shells.filter(poly => poly.depth === depth) : []; - }; - - /** ****************************************************************** - * Slice Prototype Functions - ******************************************************************* */ - /** * return Layers object for this slice. creates it if necessary. */ - PRO.output = function() { + output() { if (this.layers) return this.layers; - return this.layers = new KIRI.Layers(); + return this.layers = new kiri.Layers(); }; /** * returns a cloned slice the option of a deep clone on the top polys */ - PRO.clone = function(deep) { + clone(deep) { const from = this, slice = newSlice(from.z, from.view); from.tops.forEach(function(top) { slice.addTop(top.poly.clone(deep)); @@ -102,16 +52,16 @@ return slice; }; - PRO.topPolys = function() { + topPolys() { return this.tops.map(top => top.poly); }; - PRO.topSimples = function() { + topSimples() { return this.tops.map(top => top.simple); }; // FDM top intersect optimization - PRO.topPolysFlat = function() { + topPolysFlat() { if (this.topFlatPolys) { return this.topFlatPolys; } @@ -119,7 +69,7 @@ }; // FDM retract path routing using first shell - PRO.topRouteFlat = function() { + topRouteFlat() { if (this.topFlatRoutes) { return this.topFlatRoutes; } @@ -128,27 +78,27 @@ } // CAM only - PRO.topPolyInners = function() { + topPolyInners() { return this.tops.map(top => top.poly.inner).flat().filter(poly => poly); }; // FDM / SLA only - PRO.topInners = function() { + topInners() { return this.tops.map(top => top.last).flat().filter(poly => poly); }; // FDM / SLA only - PRO.topFillOff = function() { + topFillOff() { return this.tops.map(top => top.fill_off).flat().filter(poly => poly); }; // FDM only - PRO.topFill = function() { + topFill() { return this.tops.map(top => top.fill_lines).flat().filter(poly => poly); }; // FDM only - PRO.topShells = function() { + topShells() { return this.tops.map(top => top.shells).flat().filter(poly => poly); }; @@ -158,7 +108,7 @@ * vertical wall regions. this allows us to eliminate expensive diffs * and infill computation when we detect the layers are the same. */ - PRO.fingerprint = function() { + fingerprint() { if (this.finger) { return this.finger; } @@ -168,18 +118,18 @@ /** * returns true if the layers' fingerprints are the same */ - PRO.fingerprintSame = function(slice) { + fingerprintSame(slice) { return slice ? POLY.fingerprintCompare(this.fingerprint(), slice.fingerprint()) : false; }; - PRO.addTops = function(polys) { + addTops(polys) { polys.forEach(p => { this.addTop(p); }); return this; } - PRO.addTop = function(poly) { + addTop(poly) { if (poly.length) { // standard legacy polygon let top = new Top(poly); @@ -189,7 +139,7 @@ } else { // frop top object passed back by slicePost() let top = new Top(poly.poly); - top.thin_fill = poly.thin_fill ? poly.thin_fill.map(p => BASE.newPoint(p.x,p.y,p.z)) : undefined; + top.thin_fill = poly.thin_fill ? poly.thin_fill.map(p => base.newPoint(p.x,p.y,p.z)) : undefined; top.fill_lines = poly.fill_lines; top.fill_off = poly.fill_off; top.last = poly.last; @@ -202,7 +152,7 @@ throw "invalid top"; }; - PRO.findClosestPointTo = function(target) { + findClosestPointTo(target) { let min, find; if (this.tops.length) { @@ -224,7 +174,7 @@ return min; }; - PRO.xray = function(dash = 3) { + xray(dash = 3) { // console.log('xray', this); this.output().setLayer(`xp`, 0x888800).addPolys(this.topPolys()); this.lines.forEach((line, i) => { @@ -232,19 +182,65 @@ const color = [ 0xff0000, 0x00ff00, 0x0000ff, 0xffff00, 0xff00ff ][group]; this.output().setLayer(`xl-${group}`, color).addLine(line.p1, line.p2); }); - }; + } - function newTop(poly) { - return new Top(poly); +} + +/** + * Represents a top-level (outer) polygon in a slice. Slices may contain + * multiple tops each with nested structures. Top objects contain cached + * and computed objects for quick access for rendering and dependent computations. + */ +class Top { + + constructor(polygon) { + this.poly = polygon; // outline poly + } + + clone(deep) { + let top = new Top(this.poly.clone(deep)); + return top; } /** - * @param {number} z - * @param {THREE.Group} view - * @returns {Slice} + * return innermost traces under a given top. for FDM, this represents + * the outline shell that the fill touches. used by Print, Laser */ - function newSlice(z, view) { - return new Slice(z, view); + innerShells() { + let shells = this.shells, + array = []; + if (shells) shells.forEach(function(p) { + if (p.inner) array.appendAll(p.inner); + }); + return array; } -})(); + /** + * Returns shell polygons of a given depth + * used by Print (FDM) + * + * @param {Polygon[]} out array to populate + * @returns {Polygon[]} array of top polygons + */ + shellsAtDepth(depth) { + return this.shells ? this.shells.filter(poly => poly.depth === depth) : []; + } + +} + +function newTop(poly) { + return new Top(poly); +} + +function newSlice(z, view) { + return new Slice(z, view); +} + +gapp.overlay(kiri, { + Top, + Slice, + newTop, + newSlice +}); + +}); diff --git a/src/kiri/stack.js b/src/kiri/stack.js index da77f7f6..ec40eb5d 100644 --- a/src/kiri/stack.js +++ b/src/kiri/stack.js @@ -2,261 +2,263 @@ "use strict"; -(function() { +gapp.register("kiri.stack", [], (root, exports) => { - class Stack { - constructor(view, freeMem) { - this._view = view; - this.view = view.newGroup(); - this.slices = []; - this.meshes = []; - this.freeMem = freeMem; +const { kiri } = root; + +class Stack { + constructor(view, freeMem) { + this._view = view; + this.view = view.newGroup(); + this.slices = []; + this.meshes = []; + this.freeMem = freeMem; + } + + size() { + return this.slices.length; + } + + hide() { + this.view.visible = false; + } + + show() { + this.view.visible = true; + } + + rotate(set) { + this.view.rotation.x = -set.angle * (Math.PI/180); + this.view.position.y += -set.dy; + this.view.position.z += -set.dz; + } + + destroy() { + this._view.remove(this.view); + THREE.dispose(this.view); + this.view = this.slices = this.meshes = null; + } + + setFreeMem(bool) { + this.freeMem = bool; + } + + setVisible(newMin, newMax) { + this.show(); + for (let i=0, s=this.slices, len=s.length; i= newMin && i <= newMax; + if (s[i].visible) this.lastVis = s[i]; } + } - size() { - return this.slices.length; + setLastFraction(frac = 1) { + if (this.lastVis) + for (let mesh of this.lastVis.children) { + let geo = mesh.geometry; + let pos = geo.attributes.position; + let len = frac === 1 ? Infinity : Math.round(pos.count * frac); + geo.setDrawRange(0, len); } + } - hide() { - this.view.visible = false; + addLayers(layers) { + // each slice gets a group so the slice can be toggled efficiently + let group = this.view.newGroup(); + this.slices.push(group); + let map = this.renderLayers(layers, group); + // by default release memory after layers are rendered + if (this.freeMem) { + layers.init(); } + // return map of layers to materials (so they can be toggled on/off) + return map; + } - show() { - this.view.visible = true; + renderLayers(layers, group) { + const map = {} + for (const [label, layer] of Object.entries(layers.layers)) { + map[label] = this.renderLayer(layer, group); } + return map; + } - rotate(set) { - this.view.rotation.x = -set.angle * (Math.PI/180); - this.view.position.y += -set.dy; - this.view.position.z += -set.dz; - } - - destroy() { - this._view.remove(this.view); - THREE.dispose(this.view); - this.view = this.slices = this.meshes = null; - } - - setFreeMem(bool) { - this.freeMem = bool; - } - - setVisible(newMin, newMax) { - this.show(); - for (let i=0, s=this.slices, len=s.length; i= newMin && i <= newMax; - if (s[i].visible) this.lastVis = s[i]; + renderLayer(layer, group) { + function addPoly(vertices, poly) { + const points = poly.points, len = points.length; + for (let i=1; i m.visible = defstate); - for (let i=0; i { mat.push(newMat(c, true)) }); - } else { - mat.push(newMat(color, true)); - } - mat.forEach(m => m.visible = defstate); + // for now, line segments inherit the last color + for (let i=0, il=lines.length; i m.visible = defstate); + for (let i=0; i { mat.push(newMat(c, true)) }); + } else { + mat.push(newMat(color, true)); + } + mat.forEach(m => m.visible = defstate); + const geo = new THREE.BufferGeometry(); + if (faces.toFloat32) { + geo.setAttribute('position', new THREE.BufferAttribute(faces.toFloat32(), 3)); + } else { + geo.setAttribute('position', new THREE.BufferAttribute(faces, 3)); + } + // geo.computeFaceNormals(); + geo.computeVertexNormals(); + if (cface) { + cface.forEach((c, i) => geo.addGroup(c.start, c.count, i)); + } else { + geo.addGroup(0, Infinity, 0); + } + const mesh = new THREE.Mesh(geo, mat); + mesh.castShadow = true; + mesh.receiveShadow = true; + meshes.push(mesh); + group.add(mesh); + mats.appendAll(mat); + } + if (paths.length) { + const mat = []; + if (cpath) { + cpath.forEach(c => { mat.push(newMat(c)) }); + } else { + mat.push(newMat(color)); + } + mat.forEach(m => m.visible = defstate); + // const mat = newMat(data); + paths.forEach((path, i) => { + const { index, faces, z, colors } = path; const geo = new THREE.BufferGeometry(); - if (faces.toFloat32) { - geo.setAttribute('position', new THREE.BufferAttribute(faces.toFloat32(), 3)); - } else { - geo.setAttribute('position', new THREE.BufferAttribute(faces, 3)); - } + geo.setAttribute('position', new THREE.BufferAttribute(faces, 3)); + geo.setIndex(index); // geo.computeFaceNormals(); geo.computeVertexNormals(); - if (cface) { - cface.forEach((c, i) => geo.addGroup(c.start, c.count, i)); + if (cpath) { + cpath.forEach((c, i) => geo.addGroup(c.start, c.count, i)); } else { geo.addGroup(0, Infinity, 0); } const mesh = new THREE.Mesh(geo, mat); + mesh.position.z = z; mesh.castShadow = true; mesh.receiveShadow = true; meshes.push(mesh); group.add(mesh); - mats.appendAll(mat); - } - if (paths.length) { - const mat = []; - if (cpath) { - cpath.forEach(c => { mat.push(newMat(c)) }); - } else { - mat.push(newMat(color)); - } - mat.forEach(m => m.visible = defstate); - // const mat = newMat(data); - paths.forEach((path, i) => { - const { index, faces, z, colors } = path; - const geo = new THREE.BufferGeometry(); - geo.setAttribute('position', new THREE.BufferAttribute(faces, 3)); - geo.setIndex(index); - // geo.computeFaceNormals(); - geo.computeVertexNormals(); - if (cpath) { - cpath.forEach((c, i) => geo.addGroup(c.start, c.count, i)); - } else { - geo.addGroup(0, Infinity, 0); - } - const mesh = new THREE.Mesh(geo, mat); - mesh.position.z = z; - mesh.castShadow = true; - mesh.receiveShadow = true; - meshes.push(mesh); - group.add(mesh); - }); - mats.appendAll(mat); - } - - this.new_meshes = meshes; - this.meshes.appendAll(meshes); - mats.forEach(mat => mat.visible = defstate); - return mats; + }); + mats.appendAll(mat); } + + this.new_meshes = meshes; + this.meshes.appendAll(meshes); + mats.forEach(mat => mat.visible = defstate); + return mats; } +} - self.kiri.Stack = Stack; +kiri.Stack = Stack; - let shininess = 15, - specular = 0x444444, - emissive = 0x101010, - metalness = 0, - roughness = 0.3, - newMat = createPhongMaterial; +let shininess = 15, + specular = 0x444444, + emissive = 0x101010, + metalness = 0, + roughness = 0.3, + newMat = createPhongMaterial; - function createLineMaterial(color, array) { - const opacity = color.lopacity || color.opacity || 1; - const mat = new THREE.LineBasicMaterial({ - transparent: true, - opacity: opacity, - color: color.line - }); - if (array) { - array.push(mat); - } - return mat; +function createLineMaterial(color, array) { + const opacity = color.lopacity || color.opacity || 1; + const mat = new THREE.LineBasicMaterial({ + transparent: true, + opacity: opacity, + color: color.line + }); + if (array) { + array.push(mat); } + return mat; +} - function createStandardMaterial(color, flat) { - return new THREE.MeshStandardMaterial({ - emissive, - roughness, - metalness, - transparent: color.opacity != 1, - opacity: color.opacity || 1, - color: color.face, - side: flat ? THREE.DoubleSide : THREE.FrontSide - }); - } +function createStandardMaterial(color, flat) { + return new THREE.MeshStandardMaterial({ + emissive, + roughness, + metalness, + transparent: color.opacity != 1, + opacity: color.opacity || 1, + color: color.face, + side: flat ? THREE.DoubleSide : THREE.FrontSide + }); +} - function createPhongMaterial(color, flat) { - return new THREE.MeshPhongMaterial({ - shininess, - specular, - transparent: color.opacity != 1, - opacity: color.opacity || 1, - color: color.face, - side: flat ? THREE.DoubleSide : THREE.FrontSide - }); - } +function createPhongMaterial(color, flat) { + return new THREE.MeshPhongMaterial({ + shininess, + specular, + transparent: color.opacity != 1, + opacity: color.opacity || 1, + color: color.face, + side: flat ? THREE.DoubleSide : THREE.FrontSide + }); +} -})(); +}); diff --git a/src/kiri/stacks.js b/src/kiri/stacks.js index 6c618e45..56116ac3 100644 --- a/src/kiri/stacks.js +++ b/src/kiri/stacks.js @@ -2,168 +2,172 @@ "use strict"; -(function() { +// dep: kiri.stack +// use: kiri.api +// use: kiri.ui +gapp.register("kiri.stacks", [], (root, exports) => { - let KIRI = self.kiri, - freeMem = true, - stacks = {}, - tallest = 0, - min = 0, - max = 0, - labels, API, UC, UI, DYN; +const { kiri } = root; - function init() { - labels = $("layers"); - API = KIRI.api, - UC = API.uc, - UI = API.ui; +let freeMem = true, + stacks = {}, + tallest = 0, + min = 0, + max = 0, + labels, API, UC, UI, DYN; + +function init() { + labels = $("layers"); + API = kiri.api, + UC = API.uc, + UI = API.ui; +} + +function setFreeMem(bool) { + Object.values(stacks).forEach(stack => stack.obj.setFreeMem(bool)); + freeMem = bool; + return this; +} + +function clear() { + if (!API) { + init(); } - - function setFreeMem(bool) { - Object.values(stacks).forEach(stack => stack.obj.setFreeMem(bool)); - freeMem = bool; - return this; + // remove stacks from their views + for (const [stack, data] of Object.entries(stacks)) { + data.clear(); } + min = max = tallest = 0; + stacks = {}; + DYN = UI.dyn = {}; - function clear() { - if (!API) { - init(); - } - // remove stacks from their views - for (const [stack, data] of Object.entries(stacks)) { - data.clear(); - } - min = max = tallest = 0; - stacks = {}; - DYN = UI.dyn = {}; + // clear labels + UC.setGroup(labels); + labels.innerHTML = ''; +} - // clear labels - UC.setGroup(labels); - labels.innerHTML = ''; - } +function rotate(set) { + Object.values(stacks).forEach(stack => { + stack.obj.rotate(set); + }); +} - function rotate(set) { - Object.values(stacks).forEach(stack => { - stack.obj.rotate(set); +function getStack(name) { + return stacks[name]; +} + +function getStacks() { + return Object.entries(stacks); +} + +function getLabels() { + return Object.keys(DYN); +} + +function setVisible(label, bool) { + let ctrl = DYN[label]; + if (ctrl) { + ctrl.toggle.checked = bool; + ctrl.group.forEach(mat => { + mat.visible = bool; }); } +} - function getStack(name) { +function create(name, view) { + if (stacks[name]) { return stacks[name]; } - - function getStacks() { - return Object.entries(stacks); - } - - function getLabels() { - return Object.keys(DYN); - } - - function setVisible(label, bool) { - let ctrl = DYN[label]; - if (ctrl) { - ctrl.toggle.checked = bool; - ctrl.group.forEach(mat => { - mat.visible = bool; - }); - } - } - - function create(name, view) { - if (stacks[name]) { - return stacks[name]; - } - const stack = stacks[name] = { - layers: [ ], - obj: new KIRI.Stack(view, freeMem), - add: function(layers) { - let lmap = layers.layers; - let map = stack.obj.addLayers(layers); - for (const [label, mats] of Object.entries(map)) { - if (!DYN[label]) { - let check = DYN[label] = { - group: [], - toggle: UC.newBoolean(label, (abc) => { - ctrl.group.forEach(mat => { - mat.visible = ctrl.toggle.checked; - }); - if (ctrl.toggle.checked) { - KIRI.api.event.emit("stack.show", label); - } else { - KIRI.api.event.emit("stack.hide", label) - } - }) - }; - let color = lmap[label].color.check; - if (color !== undefined) { - let div = document.createElement('div'); - div.setAttribute('class', 'color'); - div.style.backgroundColor = `#${color.toString(16).padStart(6,0)}`; - check.toggle.parentNode.appendChild(div); - check.toggle.classList.add("color"); - } + const stack = stacks[name] = { + layers: [ ], + obj: new kiri.Stack(view, freeMem), + add: function(layers) { + let lmap = layers.layers; + let map = stack.obj.addLayers(layers); + for (const [label, mats] of Object.entries(map)) { + if (!DYN[label]) { + let check = DYN[label] = { + group: [], + toggle: UC.newBoolean(label, (abc) => { + ctrl.group.forEach(mat => { + mat.visible = ctrl.toggle.checked; + }); + if (ctrl.toggle.checked) { + kiri.api.event.emit("stack.show", label); + } else { + kiri.api.event.emit("stack.hide", label) + } + }) + }; + let color = lmap[label].color.check; + if (color !== undefined) { + let div = document.createElement('div'); + div.setAttribute('class', 'color'); + div.style.backgroundColor = `#${color.toString(16).padStart(6,0)}`; + check.toggle.parentNode.appendChild(div); + check.toggle.classList.add("color"); } - const ctrl = DYN[label]; - ctrl.group.appendAll(mats); - ctrl.toggle.checked = mats.state; } - tallest = Math.max(tallest, stack.obj.size()); - }, - remove: function() { - view.remove(stack.obj.view); - THREE.dispose(stack.obj.view); - }, - clear: function() { - view.remove(stack.obj.view); - THREE.dispose(stack.obj.view); - }, - button: function(label, action) { - UC.newRow([ UC.newButton(label, action) ]); + const ctrl = DYN[label]; + ctrl.group.appendAll(mats); + ctrl.toggle.checked = mats.state; } - }; - return stack; - } - - function remove(name) { - const stack = stacks[name]; - if (stack) { - stack.remove(); - delete stacks[name]; + tallest = Math.max(tallest, stack.obj.size()); + }, + remove: function() { + view.remove(stack.obj.view); + THREE.dispose(stack.obj.view); + }, + clear: function() { + view.remove(stack.obj.view); + THREE.dispose(stack.obj.view); + }, + button: function(label, action) { + UC.newRow([ UC.newButton(label, action) ]); } - } - - function getRange() { - return {min, max, tallest}; - } - - function setRange(newMin, newMax) { - Object.values(stacks).forEach(stack => { - stack.obj.setVisible(newMin,newMax); - }); - min = newMin; - max = newMax; - } - - function setFraction(frac) { - Object.values(stacks).forEach(stack => { - stack.obj.setLastFraction(frac); - }); - } - - KIRI.stacks = { - clear, - create, - rotate, - remove, - getStack, - getStacks, - getRange, - setRange, - getLabels, - setVisible, - setFraction, - setFreeMem }; + return stack; +} -})(); +function remove(name) { + const stack = stacks[name]; + if (stack) { + stack.remove(); + delete stacks[name]; + } +} + +function getRange() { + return {min, max, tallest}; +} + +function setRange(newMin, newMax) { + Object.values(stacks).forEach(stack => { + stack.obj.setVisible(newMin,newMax); + }); + min = newMin; + max = newMax; +} + +function setFraction(frac) { + Object.values(stacks).forEach(stack => { + stack.obj.setLastFraction(frac); + }); +} + +kiri.stacks = { + clear, + create, + rotate, + remove, + getStack, + getStacks, + getRange, + setRange, + getLabels, + setVisible, + setFraction, + setFreeMem +}; + +}); diff --git a/src/kiri/stats.js b/src/kiri/stats.js index 9316f3f8..abec28ff 100644 --- a/src/kiri/stats.js +++ b/src/kiri/stats.js @@ -2,9 +2,12 @@ "use strict"; -(function () { +// dep: moto.broker +// dep: kiri.api +// dep: kiri.utils +gapp.register("kiri.stats", [], (root, exports) => { -const { data, kiri, gapp } = self; +const { data, kiri, gapp } = root; const { broker } = gapp; const { api, utils } = kiri; const { js2o, o2js } = utils; @@ -55,4 +58,4 @@ class Stats { kiri.stats = new Stats(); -})(); +}); diff --git a/src/kiri/tools.js b/src/kiri/tools.js index 8951ccda..b9c88dfb 100644 --- a/src/kiri/tools.js +++ b/src/kiri/tools.js @@ -1,100 +1,112 @@ /** Copyright Stewart Allen -- All Rights Reserved */ -kiri.load(function(API) { - // circle - let pgeo = new THREE.CircleGeometry(8, 30); - let pmat = new THREE.MeshBasicMaterial({color: 0xff0000, opacity: 0.5, transparent: true}); - let pmesh = new THREE.Mesh(pgeo, pmat); - // circle outline - let pi = pgeo.index.array; - let pp = pgeo.attributes.position.array; - let np = []; - for (let i=0; i { + +const { kiri } = root; +const { api } = kiri; + +// circle +let pgeo = new THREE.CircleGeometry(8, 30); +let pmat = new THREE.MeshBasicMaterial({color: 0xff0000, opacity: 0.5, transparent: true}); +let pmesh = new THREE.Mesh(pgeo, pmat); +// circle outline +let pi = pgeo.index.array; +let pp = pgeo.attributes.position.array; +let np = []; +for (let i=0; i { - if (enabled) { - endit(); - return; - } - if (API.feature.hover) { - console.log('lay flat cannot pre-empt hover'); - return; - } - $('lay-flat').classList.add('selected'); - API.feature.hover = true; - enabled = true; - alert = API.show.alert('[ESC] to end lay-flat operation', 600000); - }); - API.event.on('mouse.hover', (ev) => { - if (!enabled) { - return; - } +function endit() { + if (enabled) { + api.hide.alert(alert); + $('lay-flat').classList.remove('selected'); + api.feature.hover = false; + enabled = false; + alert = undefined; cleanup(); - let { int, ints, event, point, type } = ev; - if (type === 'widget') { - lastface = int.face; - let obj = lastobj = int.object; - let norm = int.face.normal; - let opos = obj.widget.track.pos; - obj.add(pmesh); - pmesh.position.x = point.x - opos.x + norm.x * 0.1; - pmesh.position.y = -point.z - opos.y + norm.y * 0.1; - pmesh.position.z = point.y + norm.z * 0.1; - let q = new THREE.Quaternion().setFromUnitVectors( - new THREE.Vector3(0,0,1), - new THREE.Vector3(norm.x,norm.y,norm.z) - ); - pmesh.setRotationFromQuaternion(q); - } - }); - API.event.on('mouse.hover.up', (ev) => { - if (!enabled) { - return; - } - let { int, point, object } = ev; - if (!object) { - return; - } - let q = new THREE.Quaternion().setFromUnitVectors(lastface.normal, new THREE.Vector3(0,0,-1)); - API.selection.rotate(q); + } +} + +api.event.on('key.esc', endit); + +api.event.on('tool.mesh.lay-flat', () => { + if (enabled) { endit(); - }); + return; + } + if (api.feature.hover) { + console.log('lay flat cannot pre-empt hover'); + return; + } + $('lay-flat').classList.add('selected'); + api.feature.hover = true; + enabled = true; + alert = api.show.alert('[ESC] to end lay-flat operation', 600000); +}); + +api.event.on('mouse.hover', (ev) => { + if (!enabled) { + return; + } + cleanup(); + let { int, ints, event, point, type } = ev; + if (type === 'widget') { + lastface = int.face; + let obj = lastobj = int.object; + let norm = int.face.normal; + let opos = obj.widget.track.pos; + obj.add(pmesh); + pmesh.position.x = point.x - opos.x + norm.x * 0.1; + pmesh.position.y = -point.z - opos.y + norm.y * 0.1; + pmesh.position.z = point.y + norm.z * 0.1; + let q = new THREE.Quaternion().setFromUnitVectors( + new THREE.Vector3(0,0,1), + new THREE.Vector3(norm.x,norm.y,norm.z) + ); + pmesh.setRotationFromQuaternion(q); + } +}); + +api.event.on('mouse.hover.up', (ev) => { + if (!enabled) { + return; + } + let { int, point, object } = ev; + if (!object) { + return; + } + let q = new THREE.Quaternion().setFromUnitVectors(lastface.normal, new THREE.Vector3(0,0,-1)); + api.selection.rotate(q); + endit(); +}); + }); diff --git a/src/kiri/ui.js b/src/kiri/ui.js index 0b3ff331..06d7ec91 100644 --- a/src/kiri/ui.js +++ b/src/kiri/ui.js @@ -2,7 +2,7 @@ "use strict"; -(function() { +gapp.register("kiri.ui", [], (root, exports) => { if (kiri.ui) return; @@ -939,4 +939,4 @@ }); } -})(); +}); diff --git a/src/kiri/utils.js b/src/kiri/utils.js index 05da95cb..f866d865 100644 --- a/src/kiri/utils.js +++ b/src/kiri/utils.js @@ -5,9 +5,9 @@ // dep: data.local // dep: main.kiri // use: moto.ajax -(function () { +gapp.register("kiri.utils", [], (root, exports) => { -const { kiri, moto, data } = self; +const { kiri, moto, data } = root; function parseOpt(ov) { let opt = {}, kv, kva; @@ -96,7 +96,7 @@ function areEqual(o1, o2) { return false; } -kiri.utils = { +exports({ areEqual, parseOpt, encodeOpt, @@ -107,6 +107,6 @@ kiri.utils = { avgc, rgb, a2c -}; +}); -})(); +}); diff --git a/src/kiri/widget.js b/src/kiri/widget.js index 0f0c83d3..3a345066 100644 --- a/src/kiri/widget.js +++ b/src/kiri/widget.js @@ -1,12 +1,10 @@ -console.log/** Copyright Stewart Allen -- All Rights Reserved */ +/** Copyright Stewart Allen -- All Rights Reserved */ "use strict"; -(function() { +gapp.register("kiri.widget", [], (root, exports) => { -if (self.kiri.Widget) return; - -const { base, kiri } = self; +const { base, kiri } = root; const { api, driver, utils } = kiri; const { config, util, polygons } = base; const { Mesh, newPoint, verticesToPoints } = base; @@ -963,4 +961,4 @@ Widget.deleteFromState = function(id,ondone) { kiri.Widget = Widget; kiri.newWidget = newWidget; -})(); +}); diff --git a/src/kiri/widgets.js b/src/kiri/widgets.js index 893de6ca..4e1ff46f 100644 --- a/src/kiri/widgets.js +++ b/src/kiri/widgets.js @@ -2,9 +2,9 @@ "use strict"; -(function () { +gapp.register("kiri.widgets", [], (root, exports) => { -const { data, kiri, moto, noop } = self; +const { data, kiri, moto, noop } = root; const { api, consts, utils, newWidget, Widget } = kiri; const { space } = moto; @@ -110,4 +110,4 @@ const widgets = api.widgets = { for(fn) { widgets.each(fn) }, }; -})(); +}); diff --git a/web/kiri/index.css b/web/kiri/index.css index e21e230d..654d5abe 100644 --- a/web/kiri/index.css +++ b/web/kiri/index.css @@ -34,12 +34,9 @@ a, a:hover, a:visited { transform: translateX(-50%) translateY(-50%); z-index: 50; } -#app-name, #app-mode-name, #app-acct { +#app-name, #app-acct { cursor: pointer; } -#app-mode-name { - border-bottom: 2px dotted grey; -} #app,div { display: flex; padding: 0; @@ -296,18 +293,16 @@ th, tr, td, label { margin: 0 5px 0 5px; } -#mode-info.hoverpop { - display: flex; -} #mode-info { display: none; - position: absolute; + xposition: absolute; white-space: nowrap; font-size: smaller; font-family: sans-serif; - left: 100%; + top: 50%; height: 100%; - padding: 1px 5px 1px 5px; + transform: translateY(-50%); + padding: 1px 0 1px 0; align-items: center; justify-content: center; } diff --git a/web/kiri/index.html b/web/kiri/index.html index cf95ab65..bb479dd5 100644 --- a/web/kiri/index.html +++ b/web/kiri/index.html @@ -30,8 +30,8 @@
-
+