move src/kiri/* to src/kiri/core and src/kiri-*/ to src/kiri/* and updated all imports
This commit is contained in:
parent
d47dc4e4e2
commit
43104fa5ac
214 changed files with 339 additions and 341 deletions
227
src/kiri/run/cli.js
Normal file
227
src/kiri/run/cli.js
Normal file
|
|
@ -0,0 +1,227 @@
|
|||
/** example of how to use Kiri:Moto's slicer engine from the command-line */
|
||||
let fs = require('fs');
|
||||
let args = process.argv.slice(2);
|
||||
let opts = {
|
||||
dir: process.cwd(),
|
||||
output: "-",
|
||||
model: "web/obj/cube.stl",
|
||||
source: "src/cli/kiri-source.json",
|
||||
controller: "src/cli/kiri-controller.json",
|
||||
process: "src/cli/kiri-fdm-process.json",
|
||||
device: "src/cli/kiri-fdm-device.json",
|
||||
tools: "src/cli/kiri-cam-tools.json"
|
||||
};
|
||||
for (let i=0; i<args.length; i++) {
|
||||
let arg = args[i].split('=');
|
||||
let [ key, val ] = arg;
|
||||
if (arg.length === 2) {
|
||||
key = arg[0];
|
||||
val = arg[1];
|
||||
} else if (key.indexOf('--') === 0) {
|
||||
val = true;
|
||||
} else if (key.indexOf('-') === 0) {
|
||||
val = args[++i];
|
||||
} else {
|
||||
val = undefined;
|
||||
}
|
||||
if (key && val) {
|
||||
key = key.replace(/-/g,'');
|
||||
opts[key.trim()] = val;
|
||||
} else {
|
||||
key = "model";
|
||||
val = arg;
|
||||
}
|
||||
}
|
||||
if (opts.help) {
|
||||
console.log([
|
||||
"cli <options> <file>",
|
||||
" --verbose | enable verbose logging",
|
||||
" --dir=[dir] | root directory for file paths (default: '.')",
|
||||
" --model=[file] | model file to load (or last parameter)",
|
||||
" --tools=[file] | tools array for CAM mode",
|
||||
" --source=[file] | source file list (defaults to kiri engine)",
|
||||
" --device=[file] | device definition file (json)",
|
||||
" --process=[file] | process definition file (json)",
|
||||
" --controller=[file] | controller definition file (json)",
|
||||
" --output=[file] | gcode output to file or '-' for stdout",
|
||||
" --position=x,y,z | move loaded model to position x,y,z",
|
||||
" --rotate=x,y,z | rotate loaded model x,y,z radians",
|
||||
" --scale=x,y,z | scale loaded model in x,y,z",
|
||||
" --move=x,y,z | move loaded model x,y,z millimeters"
|
||||
].join("\r\n"));
|
||||
return;
|
||||
}
|
||||
|
||||
let { dir, verbose, model, output, position, move, scale, rotate } = opts;
|
||||
let exports_save = exports,
|
||||
navigator = { userAgent: "" },
|
||||
module_save = module,
|
||||
THREE = {},
|
||||
gapp = {},
|
||||
geo = {},
|
||||
noop = () => { },
|
||||
self = this.self = {
|
||||
gapp,
|
||||
THREE,
|
||||
location: { hostname: 'local', port: 0, protocol: 'fake' },
|
||||
postMessage: (msg) => {
|
||||
self.kiri.client.onmessage({data:msg});
|
||||
}
|
||||
};
|
||||
|
||||
// fake fetch for worker to get wasm, if needed
|
||||
let fetch = function(url, opts = {}) {
|
||||
if (verbose) console.log({fetch: url});
|
||||
if (!url.startsWith('/')) {
|
||||
url = `${dir}/${url}`;
|
||||
}
|
||||
let buf = fs.readFileSync(url);
|
||||
return new Promise((resolve, reject) => {
|
||||
resolve(new Promise((resolve, reject) => {
|
||||
if (opts.format === 'string') {
|
||||
return resolve(buf.toString());
|
||||
}
|
||||
if (opts.format === 'buffer') {
|
||||
return resolve(buf);
|
||||
}
|
||||
if (opts.format === 'eval') {
|
||||
return resolve(eval('(' + buf + ')'));
|
||||
}
|
||||
resolve({
|
||||
arrayBuffer: function() {
|
||||
return buf;
|
||||
}
|
||||
});
|
||||
}));
|
||||
});
|
||||
};
|
||||
|
||||
// imitate worker process
|
||||
class Worker {
|
||||
constructor(url) {
|
||||
if (verbose) console.log({worker: url});
|
||||
}
|
||||
|
||||
postMessage(msg) {
|
||||
setImmediate(() => {
|
||||
self.kiri.worker.onmessage({data:msg});
|
||||
});
|
||||
}
|
||||
|
||||
onmessage(msg) {
|
||||
// if we end up here, something went wrong
|
||||
console.trace('worker-recv', msg);
|
||||
}
|
||||
|
||||
terminate() {
|
||||
// if we end up here, something went wrong
|
||||
console.trace('worker terminate');
|
||||
}
|
||||
}
|
||||
|
||||
// node is missing these functions so put them in scope during eval
|
||||
function atob(a) {
|
||||
return Buffer.from(a).toString('base64');
|
||||
}
|
||||
|
||||
function btoa(b) {
|
||||
return Buffer.from(b, 'base64').toString();
|
||||
}
|
||||
|
||||
async function run() {
|
||||
let files = await fetch(opts.source, { format: "eval" } );
|
||||
let tools = await fetch(opts.tools, { format: "eval" } );
|
||||
let device = await fetch(opts.device, { format: "eval" } );
|
||||
let process = await fetch(opts.process, { format: "eval" } );
|
||||
|
||||
for (let file of files.map(p => `${dir}/src/${p}.js`)) {
|
||||
let isPNG = file.indexOf("/pngjs") > 0;
|
||||
let isClip = file.indexOf("/clip") > 0;
|
||||
let isEarcut = file.indexOf("/earcut") > 0;
|
||||
let isTHREE = file.indexOf("/three") > 0;
|
||||
if (isTHREE) {
|
||||
// THREE.js kung-fu fake-out
|
||||
exports = {};
|
||||
}
|
||||
let swapMod = isEarcut;
|
||||
if (swapMod) {
|
||||
module = { exports: {} };
|
||||
}
|
||||
let clearMod = isPNG || isClip;
|
||||
if (clearMod) {
|
||||
module = undefined;
|
||||
}
|
||||
try {
|
||||
if (verbose) console.log(`loading ... ${file}`);
|
||||
eval(fs.readFileSync(`${file}`).toString());
|
||||
} catch (e) {
|
||||
throw e;
|
||||
}
|
||||
if (isClip) {
|
||||
ClipperLib = self.ClipperLib;
|
||||
}
|
||||
if (isTHREE) {
|
||||
Object.assign(THREE, exports);
|
||||
// restore exports after faking out THREE.js
|
||||
exports = exports_save;
|
||||
}
|
||||
if (isEarcut) {
|
||||
self.earcut = module.exports;
|
||||
}
|
||||
if (clearMod || swapMod) {
|
||||
module = module_save;
|
||||
}
|
||||
}
|
||||
|
||||
let { kiri, moto, load } = self;
|
||||
|
||||
console.log({version: kiri.version});
|
||||
|
||||
let engine = kiri.newEngine();
|
||||
let data = await fetch(model)
|
||||
let buf = new Uint8Array(data.arrayBuffer()).buffer;
|
||||
|
||||
return engine.parse(buf)
|
||||
.then(data => { if (verbose) console.log({loaded: data}) })
|
||||
.then(() => {
|
||||
if (position) {
|
||||
let [ x, y, z ] = position.split(',').map(v => parseFloat(v || 0));
|
||||
if (verbose) console.log('moveTo', {x, y, z});
|
||||
engine.moveTo(x,y,z);
|
||||
}
|
||||
if (move) {
|
||||
let [ x, y, z ] = move.split(',').map(v => parseFloat(v || 0));
|
||||
if (verbose) console.log('move', {x, y, z});
|
||||
engine.move(x,y,z);
|
||||
}
|
||||
if (scale) {
|
||||
let [ x, y, z ] = scale.split(',').map(v => parseFloat(v || 0));
|
||||
if (verbose) console.log('scale', {x, y, z});
|
||||
engine.scale(x,y,z);
|
||||
}
|
||||
if (rotate) {
|
||||
let [ x, y, z ] = rotate.split(',').map(v => parseFloat(v || 0));
|
||||
if (verbose) console.log('rotate', {x, y, z});
|
||||
engine.rotate(x,y,z);
|
||||
}
|
||||
})
|
||||
.then(() => engine.setDevice(device))
|
||||
.then(() => engine.setProcess(process))
|
||||
.then(() => { if (device.mode === 'CAM') engine.setTools(tools) })
|
||||
.then(() => engine.setMode(device.mode))
|
||||
.then(eng => eng.slice())
|
||||
.then(eng => eng.prepare())
|
||||
.then(eng => engine.export())
|
||||
.then(gcode => {
|
||||
if (output === '-') {
|
||||
console.log({gcode});
|
||||
} else {
|
||||
fs.writeFileSync(output, gcode);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.log({error});
|
||||
});
|
||||
}
|
||||
|
||||
run();
|
||||
196
src/kiri/run/engine.js
Normal file
196
src/kiri/run/engine.js
Normal file
|
|
@ -0,0 +1,196 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
import '../../add/array.js';
|
||||
import '../../add/class.js';
|
||||
import '../../add/three.js';
|
||||
import { api } from '../core/api.js';
|
||||
import { conf } from '../core/conf.js';
|
||||
import { client } from '../core/client.js';
|
||||
import { load } from '../../load/file.js';
|
||||
import { newWidget } from '../core/widget.js';
|
||||
|
||||
class Engine {
|
||||
constructor() {
|
||||
this.widget = newWidget();
|
||||
this.settings = {
|
||||
mode: "FDM",
|
||||
controller: {},
|
||||
render: false,
|
||||
filter: { FDM: "internal" },
|
||||
device: conf.defaults.fdm.d, // device profile
|
||||
process: conf.defaults.fdm.p, // slicing settings
|
||||
widget: { [this.widget.id]: {} }
|
||||
};
|
||||
this.listener = () => { };
|
||||
client.start();
|
||||
}
|
||||
|
||||
load(url) {
|
||||
return new Promise((accept, reject) => {
|
||||
try {
|
||||
new load.STL().load(url, vertices => {
|
||||
this.listener({ loaded: url, vertices });
|
||||
this.widget.loadVertices(vertices).center();
|
||||
accept(this);
|
||||
});
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
clear() {
|
||||
api.platform.clear();
|
||||
}
|
||||
|
||||
parse(data) {
|
||||
return new Promise((accept, reject) => {
|
||||
try {
|
||||
let vertices = new load.STL().parse(data);
|
||||
this.listener({ parsed: data, vertices });
|
||||
this.widget.loadVertices(vertices).center();
|
||||
accept(this);
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
setThreading(bool) {
|
||||
if (bool) {
|
||||
client.pool.start();
|
||||
} else {
|
||||
client.pool.stop();
|
||||
}
|
||||
}
|
||||
|
||||
setListener(listener) {
|
||||
this.listener = listener;
|
||||
return this;
|
||||
}
|
||||
|
||||
setRender(bool) {
|
||||
this.settings.render = bool;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the mode of the engine. Valid modes are:
|
||||
* @param {"FDM"|"CAM"|"LASER"|"SLA"} mode - the mode to set
|
||||
* @returns {Engine} this
|
||||
*/
|
||||
setMode(mode) {
|
||||
this.settings.mode = mode;
|
||||
return this;
|
||||
}
|
||||
|
||||
setDevice(device) {
|
||||
Object.assign(this.settings.device, device);
|
||||
return this;
|
||||
}
|
||||
|
||||
setProcess(process) {
|
||||
Object.assign(this.settings.process, process);
|
||||
return this;
|
||||
}
|
||||
|
||||
setController(controller) {
|
||||
let ctrl = this.settings.controller;
|
||||
Object.assign(ctrl, controller);
|
||||
if (ctrl.threaded) {
|
||||
client.pool.start();
|
||||
} else {
|
||||
client.pool.stop();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
setTools(tools) {
|
||||
this.settings.tools = tools;
|
||||
return this;
|
||||
}
|
||||
|
||||
setStock(stock) {
|
||||
let { settings } = this;
|
||||
let { process } = settings;
|
||||
settings.stock = stock;
|
||||
process.camStockX = stock.x;
|
||||
process.camStockY = stock.y;
|
||||
process.camStockZ = stock.z;
|
||||
return this;
|
||||
}
|
||||
|
||||
setOrigin(x, y, z) {
|
||||
this.settings.origin = { x, y, z };
|
||||
return this;
|
||||
}
|
||||
|
||||
moveTo(x, y, z) {
|
||||
this.widget.move(x, y, z, true);
|
||||
return this;
|
||||
}
|
||||
|
||||
move(x, y, z) {
|
||||
this.widget.move(x, y, z);
|
||||
return this;
|
||||
}
|
||||
|
||||
scale(x, y, z) {
|
||||
this.widget.scale(x, y, z);
|
||||
return this;
|
||||
}
|
||||
|
||||
rotate(x, y, z) {
|
||||
this.widget.rotate(x, y, z);
|
||||
return this;
|
||||
}
|
||||
|
||||
slice() {
|
||||
return new Promise((accept, reject) => {
|
||||
client.clear();
|
||||
client.sync([this.widget]);
|
||||
client.rotate(this.settings);
|
||||
client.slice(this.settings, this.widget, msg => {
|
||||
this.listener({ slice: msg });
|
||||
if (msg.error) {
|
||||
reject(msg.error);
|
||||
}
|
||||
if (msg.done) {
|
||||
accept(this);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
prepare() {
|
||||
return new Promise((accept, reject) => {
|
||||
client.prepare(this.settings, update => {
|
||||
this.listener({ prepare: { update } });
|
||||
}, done => {
|
||||
this.listener({ prepare: { done: true } });
|
||||
accept(this);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export() {
|
||||
return new Promise((accept, reject) => {
|
||||
let output = [];
|
||||
client.export(this.settings, segment => {
|
||||
if (typeof segment === 'string') {
|
||||
this.listener({ export: { segment } });
|
||||
output.push(segment);
|
||||
}
|
||||
}, done => {
|
||||
this.listener({ export: { done } });
|
||||
accept(output.join('\r\n'));
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function newEngine() {
|
||||
return new Engine();
|
||||
}
|
||||
|
||||
export { Engine };
|
||||
87
src/kiri/run/frame.js
Normal file
87
src/kiri/run/frame.js
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
(function() {
|
||||
let KIRI = self.kiri = self.kiri || {};
|
||||
let frame, cwin, onevent = {}, targetOrigin;
|
||||
|
||||
function $(id) { return document.getElementById(id) }
|
||||
|
||||
function setFrame(fo) {
|
||||
frame = fo;
|
||||
cwin = fo.contentWindow;
|
||||
}
|
||||
|
||||
function send(msg) {
|
||||
cwin.postMessage(msg, targetOrigin);
|
||||
}
|
||||
|
||||
function recv(msg) {
|
||||
let { origin, source, target, data } = msg;
|
||||
let efn = onevent[data.event];
|
||||
if (efn) {
|
||||
efn(data.data, data.event);
|
||||
}
|
||||
if (API.onmessage) {
|
||||
API.onmessage(data, msg);
|
||||
}
|
||||
}
|
||||
|
||||
let API = KIRI.frame = {
|
||||
setFrame: (io, target) => {
|
||||
let type = typeof(io);
|
||||
targetOrigin = target;
|
||||
switch (type) {
|
||||
case 'string': setFrame($(io)); break;
|
||||
case 'object': setFrame(io); break;
|
||||
default: throw `invalid frame type ${type}`;
|
||||
}
|
||||
window.addEventListener('message', msg => {
|
||||
let { origin, source, target, data } = msg;
|
||||
if (source.window === cwin) {
|
||||
recv(msg);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
send: send,
|
||||
|
||||
load: (load) => { send({ load }) },
|
||||
|
||||
clear: () => { send({ clear: true }) },
|
||||
|
||||
parse: (data, type) => { send({ parse: data, type })},
|
||||
|
||||
get: (scope) => { send({ get: scope })},
|
||||
|
||||
setMode: (mode) => { send({ mode }) },
|
||||
|
||||
setDevice: (device) => { send({ device })},
|
||||
|
||||
setProcess: (process) => { send({ process })},
|
||||
|
||||
setFeatures: (features) => { send({ features })},
|
||||
|
||||
setController: (controller) => { send({ controller })},
|
||||
|
||||
slice: () => { send({ function: "slice", callback: true }) },
|
||||
|
||||
prepare: () => { send({ function: "prepare", callback: true }) },
|
||||
|
||||
export: (cb) => { send({ function: "export", callback: cb ? true : false }) },
|
||||
|
||||
onmessage: () => { },
|
||||
|
||||
progress: (progress, message) => { send({ progress, message }) },
|
||||
|
||||
alert: (alert, time) => { send({ alert, time }) },
|
||||
|
||||
emit: (emit, message) => { send({ emit, message }) },
|
||||
|
||||
onevent: (event, fn) => {
|
||||
onevent[event] = fn;
|
||||
send({ event });
|
||||
}
|
||||
};
|
||||
|
||||
API.on = API.onevent;
|
||||
})();
|
||||
316
src/kiri/run/minion.js
Normal file
316
src/kiri/run/minion.js
Normal file
|
|
@ -0,0 +1,316 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
import "../../ext/clip2.js";
|
||||
import "../../ext/earcut.js";
|
||||
import '../../add/array.js';
|
||||
import '../../add/class.js';
|
||||
import '../../add/three.js';
|
||||
|
||||
import { base } from '../../geo/base.js';
|
||||
import { codec, encode, encodePointArray } from '../core/codec.js';
|
||||
import { doTopShells } from '../mode/fdm/post.js';
|
||||
import { newPoint } from '../../geo/point.js';
|
||||
import { polygons as POLY } from '../../geo/polygons.js';
|
||||
import { sliceZ } from '../../geo/slicer.js';
|
||||
import { Slicer as cam_slicer } from '../mode/cam/slicer.js';
|
||||
import { Slicer as topo_slicer } from '../mode/cam/slicer_topo.js';
|
||||
import { Probe, Trace, raster_slice } from '../mode/cam/topo3.js';
|
||||
import { wasm_ctrl } from '../../geo/wasm.js';
|
||||
|
||||
const clib = self.ClipperLib;
|
||||
const ctyp = clib.ClipType;
|
||||
const ptyp = clib.PolyType;
|
||||
const cfil = clib.PolyFillType;
|
||||
|
||||
let cache = self.cache = {};
|
||||
let name = "unknown";
|
||||
|
||||
// catch clipper alerts and convert to console messages
|
||||
self.alert = function(o) {
|
||||
console.log(o);
|
||||
};
|
||||
|
||||
self.onmessage = function(msg) {
|
||||
let { data } = msg;
|
||||
let { cmd } = data;
|
||||
debug('MINION.onmessage', { cmd, data });
|
||||
try {
|
||||
(funcs[cmd] || funcs.invalid)(data, data.seq, cmd);
|
||||
} catch (error) {
|
||||
log('MINION.dispatch.error', error);
|
||||
}
|
||||
};
|
||||
|
||||
function reply(msg, direct) {
|
||||
self.postMessage(msg, direct);
|
||||
}
|
||||
|
||||
function log() {
|
||||
console.log(`[${name}]`, ...arguments);
|
||||
}
|
||||
|
||||
function debug() {
|
||||
// console.log(`[${name}]`, ...arguments);
|
||||
}
|
||||
|
||||
const funcs = self.minion = {
|
||||
invalid(data, seq, cmd) {
|
||||
console.error({ invalid_minion_command: cmd, data });
|
||||
reply({ seq, error: `invalid command (${cmd})` });
|
||||
},
|
||||
|
||||
label(data, seq) {
|
||||
name = data.name;
|
||||
self.kiri_minion = { name, cache, log };
|
||||
},
|
||||
|
||||
config(data) {
|
||||
if (data.base) {
|
||||
Object.assign(base.config, data.base);
|
||||
} else {
|
||||
log({invalid: data});
|
||||
}
|
||||
},
|
||||
|
||||
union(data, seq) {
|
||||
if (!(data.polys && data.polys.length)) {
|
||||
reply({ seq, union: codec.encode([]) });
|
||||
return;
|
||||
}
|
||||
let state = { zeros: [] };
|
||||
let polys = codec.decode(data.polys);
|
||||
let union = POLY.union(polys, data.minarea || 0, true);
|
||||
reply({ seq, union: codec.encode(union) }, state.zeros);
|
||||
},
|
||||
|
||||
topShells(data, seq) {
|
||||
let top = codec.decode(data.top, {full: true});
|
||||
let {z, count, offset1, offsetN, fillOffset, opt} = data;
|
||||
doTopShells(z, top, count, offset1, offsetN, fillOffset, opt);
|
||||
let state = { zeros: [] };
|
||||
reply({ seq, top: codec.encode(top, {full: true}) }, state.zeros);
|
||||
},
|
||||
|
||||
fill(data, seq) {
|
||||
let polys = codec.decode(data.polys);
|
||||
let { angle, spacing, minLen, maxLen } = data;
|
||||
let fill = POLY.fillArea(polys, angle, spacing, [], minLen, maxLen);
|
||||
let arr = new Float32Array(fill.length * 4);
|
||||
for (let i=0, p=0; p<fill.length; ) {
|
||||
let pt = fill[p++];
|
||||
arr[i++] = pt.x;
|
||||
arr[i++] = pt.y;
|
||||
arr[i++] = pt.z;
|
||||
arr[i++] = pt.index;
|
||||
}
|
||||
reply({ seq, fill: arr }, [ arr.buffer ]);
|
||||
},
|
||||
|
||||
clip(data, seq) {
|
||||
const clip = new clib.Clipper();
|
||||
const ctre = new clib.PolyTree();
|
||||
const clips = [];
|
||||
const M = base.config.clipper;
|
||||
|
||||
let lines = data.lines.map(array => {
|
||||
return codec.decodePointArray2D(array, data.z, (X, Y) => { return {X: X*M, Y: Y*M} })
|
||||
});
|
||||
let polys = data.polys.map(array => {
|
||||
return codec.decodePointArray2D(array, data.z, (X, Y) => { return {X: X*M, Y: Y*M} })
|
||||
});
|
||||
|
||||
clip.AddPaths(lines, ptyp.ptSubject, false);
|
||||
clip.AddPaths(polys, ptyp.ptClip, true);
|
||||
|
||||
const state = { zeros: [] };
|
||||
if (clip.Execute(ctyp.ctIntersection, ctre, cfil.pftNonZero, cfil.pftEvenOdd)) {
|
||||
for (let node of ctre.m_AllPolys) {
|
||||
clips.push(codec.encode(POLY.fromClipperNode(node, data.z), state.zeros));
|
||||
}
|
||||
}
|
||||
|
||||
reply({ seq, clips }, state.zeros);
|
||||
},
|
||||
|
||||
sliceZ(data, seq) {
|
||||
debug('minion.sliceZ', { data, seq });
|
||||
let { z, points, options } = data;
|
||||
let i = 0, p = 0, realp = new Array(points.length / 3);
|
||||
while (i < points.length) {
|
||||
realp[p++] = newPoint(points[i++], points[i++], points[i++]).round(3);
|
||||
}
|
||||
let state = { zero: [] };
|
||||
let output = [];
|
||||
sliceZ(z, realp, {
|
||||
...options,
|
||||
each(out) { output.push(out) }
|
||||
}).then(() => {
|
||||
for (let rec of output) {
|
||||
// lines do not pass codec properly (for now)
|
||||
delete rec.lines;
|
||||
}
|
||||
reply({ seq, output: codec.encode(output) }, state.zeros);
|
||||
});
|
||||
},
|
||||
|
||||
putCache(msg) {
|
||||
const { key, data } = msg;
|
||||
// log({ minion_putCache: key, data });
|
||||
if (data) {
|
||||
cache[key] = data;
|
||||
} else {
|
||||
delete cache[key];
|
||||
}
|
||||
},
|
||||
|
||||
clearCache(msg) {
|
||||
for (let key in cache) {
|
||||
delete cache[key];
|
||||
}
|
||||
},
|
||||
|
||||
wasm(data) {
|
||||
if (data.enable) {
|
||||
wasm_ctrl.enable();
|
||||
} else {
|
||||
wasm_ctrl.disable();
|
||||
}
|
||||
},
|
||||
|
||||
// CAM slice support
|
||||
|
||||
cam_slice_init() {
|
||||
cache.slicer = new cam_slicer();
|
||||
},
|
||||
|
||||
cam_slice_cleanup() {
|
||||
delete cache.slicer;
|
||||
},
|
||||
|
||||
cam_slice(data, seq) {
|
||||
const { bucket, opt } = data;
|
||||
cache.slicer.sliceBucket(bucket, opt, slice => {
|
||||
// log({ slice });
|
||||
}).then(data => {
|
||||
data.forEach(rec => {
|
||||
rec.polys = encode(rec.polys);
|
||||
if (rec.lines) {
|
||||
const points = rec.lines.map(l => [l.p1, l.p2]).flat();
|
||||
rec.lines = encodePointArray(points);
|
||||
}
|
||||
});
|
||||
reply({ seq, slices: data });
|
||||
});
|
||||
},
|
||||
|
||||
// CAM Topo3 support
|
||||
|
||||
topo_raster(data, seq) {
|
||||
const { id, slice, params } = data;
|
||||
const { resolution } = params;
|
||||
const vertices = cache[id];
|
||||
const box = new THREE.Box2();
|
||||
new topo_slicer(slice.index)
|
||||
.setFromArray(vertices, slice)
|
||||
.slice(resolution)
|
||||
.forEach(rec => {
|
||||
const { z, index, lines } = rec;
|
||||
|
||||
for (let line of lines) {
|
||||
const { p1, p2 } = line;
|
||||
if (!p1.swapped) { p1.swapXZ(); p1.swapped = true }
|
||||
if (!p2.swapped) { p2.swapXZ(); p2.swapped = true }
|
||||
}
|
||||
|
||||
raster_slice({
|
||||
...params,
|
||||
box,
|
||||
lines,
|
||||
gridx: index
|
||||
});
|
||||
});
|
||||
// only pass back bounds of rasters to be merged
|
||||
reply({ seq, box });
|
||||
},
|
||||
|
||||
trace_init(data) {
|
||||
data.cross.clipTo = codec.decode(data.cross.clipTo);
|
||||
data.cross.clipTab = codec.decode(data.cross.clipTab);
|
||||
const probe = new Probe(data.probe);
|
||||
const trace = new Trace(probe, data.trace);
|
||||
cache.trace = {
|
||||
probe,
|
||||
trace,
|
||||
cross: data.cross
|
||||
};
|
||||
trace.init(data.cross);
|
||||
},
|
||||
|
||||
trace_y(data, seq) {
|
||||
const { trace } = cache.trace;
|
||||
trace.crossY_sync(data.params, slice => {
|
||||
slice = codec.encode(slice);
|
||||
reply({ seq, slice });
|
||||
});
|
||||
},
|
||||
|
||||
trace_x(data, seq) {
|
||||
const { trace } = cache.trace;
|
||||
trace.crossX_sync(data.params, slice => {
|
||||
slice = codec.encode(slice);
|
||||
reply({ seq, slice });
|
||||
});
|
||||
},
|
||||
|
||||
trace_cleanup() {
|
||||
delete cache.trace;
|
||||
},
|
||||
|
||||
// CAM Topo4 support
|
||||
|
||||
topo4_slice(data, seq) {
|
||||
const { slice, resolution } = data;
|
||||
const vertices = cache.vertices;
|
||||
const recs = new kiri.topo_slicer(slice.index)
|
||||
.setFromArray(vertices, slice)
|
||||
.slice(resolution)
|
||||
.map(rec => {
|
||||
const { z, index, lines } = rec;
|
||||
|
||||
for (let line of lines) {
|
||||
const { p1, p2 } = line;
|
||||
if (!p1.swapped) { p1.swapXZ(); p1.swapped = true }
|
||||
if (!p2.swapped) { p2.swapXZ(); p2.swapped = true }
|
||||
}
|
||||
|
||||
const points = codec.encodePointArray(lines.map(l => [ l.p1, l.p2 ]).flat());
|
||||
const shared = new Float32Array(new SharedArrayBuffer(points.length * 4));
|
||||
shared.set(points);
|
||||
|
||||
return {
|
||||
z, index, shared,
|
||||
polys: codec.encode(sliceConnect(lines)),
|
||||
};
|
||||
});
|
||||
// only pass back bounds of rasters to be merged
|
||||
reply({ seq, recs });
|
||||
},
|
||||
|
||||
topo4_lathe(data, seq) {
|
||||
const { angle } = data;
|
||||
const { slices, tool } = cache.lathe;
|
||||
|
||||
const axis = new THREE.Vector3(1, 0, 0);
|
||||
const mrot = new THREE.Matrix4().makeRotationAxis(axis, -angle);
|
||||
const stmp = slices.map(s => {
|
||||
const lines = s.lines.slice();
|
||||
rotatePoints(lines, mrot);
|
||||
return { z: s.z, lines }
|
||||
});
|
||||
|
||||
const topo4 = Object.assign(new Topo4(), cache.lathe);
|
||||
const heights = topo4.lathePath(stmp, tool);
|
||||
|
||||
reply({ seq, heights });
|
||||
}
|
||||
};
|
||||
772
src/kiri/run/worker.js
Normal file
772
src/kiri/run/worker.js
Normal file
|
|
@ -0,0 +1,772 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
import '../../add/array.js';
|
||||
import '../../add/class.js';
|
||||
import '../../add/three.js';
|
||||
import '../../ext/jszip.js';
|
||||
|
||||
import { base } from '../../geo/base.js';
|
||||
import { codec } from '../core/codec.js';
|
||||
import { consts } from '../core/consts.js';
|
||||
import { util } from '../../geo/base.js';
|
||||
import { newPoint } from '../../geo/point.js';
|
||||
import { polygons as POLY } from '../../geo/polygons.js';
|
||||
import { newPrint } from '../core/print.js';
|
||||
import { render } from '../core/render.js';
|
||||
import { wasm_ctrl } from '../../geo/wasm.js';
|
||||
import { version } from '../../moto/license.js';
|
||||
import { Widget, newWidget } from '../core/widget.js';
|
||||
import { load } from "../../load/png.js";
|
||||
|
||||
import { CAM } from '../mode/cam/driver-be.js';
|
||||
import { DRAG } from '../mode/drag/driver.js';
|
||||
import { FDM } from '../mode/fdm/driver-be.js';
|
||||
import { LASER } from '../mode/laser/driver.js';
|
||||
import { SLA } from '../mode/sla/driver.js';
|
||||
import { WEDM } from '../mode/wedm/driver.js';
|
||||
import { WJET } from '../mode/wjet/driver.js';
|
||||
|
||||
const { time } = util;
|
||||
|
||||
let drivers = {
|
||||
DRAG,
|
||||
CAM,
|
||||
FDM,
|
||||
LASER,
|
||||
SLA,
|
||||
WEDM,
|
||||
WJET
|
||||
},
|
||||
ccvalue = self.navigator ? self.navigator.hardwareConcurrency || 0 : 0,
|
||||
concurrent = Math.min(4, self.Worker && ccvalue > 3 ? ccvalue - 1 : 0),
|
||||
current = {
|
||||
print: null,
|
||||
snap: null,
|
||||
mode: null,
|
||||
},
|
||||
wgroup = {},
|
||||
wcache = {},
|
||||
pcache = {},
|
||||
minions = [],
|
||||
minionq = [],
|
||||
minifns = {},
|
||||
miniseq = 0;
|
||||
|
||||
function debug() {
|
||||
if (self.debug === true)
|
||||
console.log(...arguments);
|
||||
}
|
||||
|
||||
// catch clipper alerts and convert to console messages
|
||||
self.alert = function(o) {
|
||||
console.log(o);
|
||||
};
|
||||
|
||||
self.uuid = ((Math.random() * Date.now()) | 0).toString(36);
|
||||
|
||||
function minhandler(msg) {
|
||||
let data = msg.data;
|
||||
let seq = data.seq;
|
||||
let fn = minifns[seq];
|
||||
if (!fn) {
|
||||
throw `missing dispatch ${seq}`;
|
||||
}
|
||||
delete minifns[seq];
|
||||
fn(data);
|
||||
}
|
||||
|
||||
// for concurrent operations
|
||||
const minwork = {
|
||||
get concurrent() {
|
||||
return concurrent
|
||||
},
|
||||
|
||||
get running() {
|
||||
return minions.length;
|
||||
},
|
||||
|
||||
start(poolpath) {
|
||||
if (minions.length || !concurrent) {
|
||||
return;
|
||||
}
|
||||
for (let i=0; i < concurrent; i++) {
|
||||
let minion = new Worker(poolpath || consts.PATHS.pool, { type: 'module' });
|
||||
minion.onerror = (error) => {
|
||||
debug({ MINION_ERROR: error });
|
||||
error.preventDefault();
|
||||
};
|
||||
minion.onmessageerror = (error) => {
|
||||
debug({ MINION_MESSAGE_ERROR: error });
|
||||
error.preventDefault();
|
||||
};
|
||||
minion.onmessage = minhandler;
|
||||
minion.postMessage({ cmd: "label", name: `#${i}` });
|
||||
minions.push(minion);
|
||||
}
|
||||
console.log(`kiri | init pool | ${version || "rogue"} | ${concurrent}`);
|
||||
},
|
||||
|
||||
stop() {
|
||||
for (let minion of minions) {
|
||||
minion.terminate();
|
||||
}
|
||||
minions.length = 0;
|
||||
},
|
||||
|
||||
union(polys, minarea) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (concurrent < 2 || polys.length < concurrent * 2 || POLY.points(polys) < concurrent * 50) {
|
||||
resolve(POLY.union(polys, minarea, true));
|
||||
return;
|
||||
}
|
||||
let polyper = Math.ceil(polys.length / concurrent);
|
||||
let running = 0;
|
||||
let union = [];
|
||||
let receiver = function(data) {
|
||||
let polys = codec.decode(data.union);
|
||||
union.appendAll(polys);
|
||||
if (--running === 0) {
|
||||
resolve(POLY.union(union, minarea, true));
|
||||
}
|
||||
};
|
||||
for (let i=0; i<polys.length; i += polyper) {
|
||||
let state = { zeros: [] };
|
||||
running++;
|
||||
minwork.queue({
|
||||
cmd: "union",
|
||||
minarea,
|
||||
polys: codec.encode(polys.slice(i, i + polyper), state)
|
||||
}, receiver, state.zeros);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
fill(polys, angle, spacing, output, minLen, maxLen) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (concurrent < 2) {
|
||||
resolve(POLY.fillArea(polys, angle, spacing, [], minLen, maxLen));
|
||||
return;
|
||||
}
|
||||
const state = { zeros: [] };
|
||||
minwork.queue({
|
||||
cmd: "fill",
|
||||
polys: codec.encode(polys, state),
|
||||
angle, spacing, minLen, maxLen
|
||||
}, data => {
|
||||
let arr = data.fill;
|
||||
let fill = [];
|
||||
for (let i=0; i<arr.length; ) {
|
||||
let pt = newPoint(arr[i++], arr[i++], arr[i++]);
|
||||
pt.index = arr[i++];
|
||||
fill.push(pt);
|
||||
}
|
||||
output.appendAll(fill);
|
||||
resolve(fill);
|
||||
}, state.zeros);
|
||||
});
|
||||
},
|
||||
|
||||
clip(slice, polys, lines) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (concurrent < 2) {
|
||||
reject("concurrent clip unavailable");
|
||||
}
|
||||
const state = { zeros: [] };
|
||||
|
||||
minwork.queue({
|
||||
cmd: "clip",
|
||||
polys: codec.encode(POLY.flatten(polys).map(poly => codec.encodePointArray2D(poly.points, state)), state),
|
||||
lines: codec.encode(lines.map(array => codec.encodePointArray2D(array, state)), state),
|
||||
z: slice.z
|
||||
}, data => {
|
||||
let polys = codec.decode(data.clips);
|
||||
for (let top of slice.tops) {
|
||||
for (let poly of polys) {
|
||||
if (poly.isInside(top.poly)) {
|
||||
top.fill_sparse.push(poly);
|
||||
}
|
||||
}
|
||||
}
|
||||
resolve(polys);
|
||||
}, state.zeros);
|
||||
});
|
||||
},
|
||||
|
||||
sliceZ(z, points, options) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (concurrent < 2) {
|
||||
reject("concurrent slice unavaiable");
|
||||
}
|
||||
let { each } = options;
|
||||
// todo use shared array buffer?
|
||||
let i = 0, floatP = new Float32Array(points.length * 3);
|
||||
for (let p of points) {
|
||||
floatP[i++] = p.x;
|
||||
floatP[i++] = p.y;
|
||||
floatP[i++] = p.z;
|
||||
}
|
||||
minwork.queue({
|
||||
cmd: "sliceZ",
|
||||
z,
|
||||
points: floatP,
|
||||
options: codec.toCodable(options)
|
||||
}, data => {
|
||||
let recs = codec.decode(data.output);
|
||||
if (each) {
|
||||
for (let rec of recs) {
|
||||
each(rec);
|
||||
}
|
||||
}
|
||||
resolve(recs);
|
||||
}, [ floatP.buffer ]);
|
||||
});
|
||||
},
|
||||
|
||||
queue(work, ondone, direct) {
|
||||
minionq.push({work, ondone, direct});
|
||||
minwork.kick();
|
||||
},
|
||||
|
||||
queueAsync(work, direct) {
|
||||
return new Promise(resolve => {
|
||||
minwork.queue(work, resolve, direct);
|
||||
});
|
||||
},
|
||||
|
||||
kick() {
|
||||
if (minions.length && minionq.length) {
|
||||
let qrec = minionq.shift();
|
||||
let minion = minions.shift();
|
||||
let seq = miniseq++;
|
||||
qrec.work.seq = seq;
|
||||
minifns[seq] = (data) => {
|
||||
qrec.ondone(data);
|
||||
minions.push(minion);
|
||||
minwork.kick();
|
||||
};
|
||||
minion.postMessage(qrec.work, qrec.direct);
|
||||
}
|
||||
},
|
||||
|
||||
broadcast(cmd, data, direct) {
|
||||
for (let minion of minions) {
|
||||
minion.postMessage({
|
||||
cmd, ...data
|
||||
}, direct);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
console.log(`kiri | init work | ${version || "rogue"}`);
|
||||
|
||||
// code is running in the worker / server context
|
||||
const dispatch = {
|
||||
pool_start(data, send) {
|
||||
minwork.start(data.url);
|
||||
send.done({});
|
||||
},
|
||||
|
||||
pool_stop(data, send) {
|
||||
minwork.stop();
|
||||
send.done({});
|
||||
},
|
||||
|
||||
group: wgroup,
|
||||
|
||||
cache: wcache,
|
||||
|
||||
snap(data, send) {
|
||||
current.snap = data;
|
||||
send.done();
|
||||
},
|
||||
|
||||
png(data, send) {
|
||||
if (current.snap) {
|
||||
let sws = current.snap.url;
|
||||
let b64 = atob(sws.substring(sws.indexOf(',') + 1));
|
||||
let bin = Uint8Array.from(b64, c => c.charCodeAt(0));
|
||||
let img = new png.PNG();
|
||||
img.parse(bin, (err, data) => {
|
||||
send.done({png: bin}, [ bin ]);
|
||||
});
|
||||
} else {
|
||||
send.done({error: "missing snapshot"});
|
||||
}
|
||||
},
|
||||
|
||||
// purge all sync data
|
||||
clear(data, send) {
|
||||
// current.snap = null;
|
||||
current.print = null;
|
||||
dispatch.group = wgroup = {};
|
||||
dispatch.cache = worker.cache = wcache = {};
|
||||
Widget.Groups.clear();
|
||||
send.done({ clear: true });
|
||||
},
|
||||
|
||||
// widget sync
|
||||
sync(data, send) {
|
||||
// ensure each widget belongs to at least one group
|
||||
let group = wgroup[data.group];
|
||||
if (!group) {
|
||||
group = [];
|
||||
group.id = data.group;
|
||||
wgroup[data.group] = group;
|
||||
}
|
||||
|
||||
let vertices = data.vertices,
|
||||
widget = newWidget(data.id, group)
|
||||
.setInWorker()
|
||||
.loadVertices(vertices);
|
||||
|
||||
// do it here so cancel can work
|
||||
wcache[data.id] = widget;
|
||||
// stored for possible future rotations
|
||||
widget.vertices = vertices;
|
||||
// restore meta
|
||||
widget.meta = data.meta;
|
||||
// restore tracking object
|
||||
widget.track = data.track;
|
||||
send.done(data.id);
|
||||
},
|
||||
|
||||
// belt mode rotate widgets 45 degrees on X axis before slicing
|
||||
rotate(data, send) {
|
||||
let { settings } = data;
|
||||
if (!settings.device.bedBelt) {
|
||||
return send.done({});
|
||||
}
|
||||
|
||||
function mins(vert, last = {}) {
|
||||
let miny = last.miny || Infinity,
|
||||
maxy = last.maxy || -Infinity;
|
||||
for (let i=0, l=vert.length; i<l; ) {
|
||||
let x = vert[i++];
|
||||
let y = vert[i++];
|
||||
let z = vert[i++];
|
||||
miny = Math.min(miny, y);
|
||||
maxy = Math.max(maxy, y);
|
||||
}
|
||||
return { miny, maxy };
|
||||
}
|
||||
|
||||
function gmin(group) {
|
||||
let minv = {};
|
||||
for (let w of group) {
|
||||
minv = mins(w.vertices, minv);
|
||||
}
|
||||
return minv;
|
||||
}
|
||||
|
||||
for (let group of Object.values(wgroup)) {
|
||||
let { miny, maxy } = gmin(group);
|
||||
let widget = group[0];
|
||||
let proc = settings.process;
|
||||
let track = widget.track;
|
||||
let angle = proc.sliceAngle;
|
||||
let xpos = track.pos.x;
|
||||
let yoff = proc.beltAnchor || proc.firstLayerBeltLead || 0;
|
||||
let ypos = settings.device.bedDepth / 2 + track.pos.y + miny + yoff;
|
||||
let radians = Math.PI / 180;
|
||||
let rotation = radians * angle;
|
||||
for (let w of group) {
|
||||
w.moveMesh(0, miny, 0);
|
||||
}
|
||||
// rotating the root of the group rotates all widgets in the group
|
||||
widget.rotate(rotation, 0, 0, true, false);
|
||||
widget.belt = {
|
||||
angle,
|
||||
// used during prepare
|
||||
xpos,
|
||||
ypos,
|
||||
// used during slice
|
||||
dy: - miny - yoff,
|
||||
dz: 0,
|
||||
// ratio for anchor lengths, path offsets
|
||||
cosf: Math.cos(radians * angle), // Y comp
|
||||
sinf: Math.sin(radians * angle), // Z comp
|
||||
// slope for calculating z = 0 from angle bias
|
||||
slope: Math.tan(radians * (90 - angle))
|
||||
};
|
||||
for (let others of group.slice(1)) {
|
||||
others.belt = widget.belt;
|
||||
}
|
||||
send.data({group: group.id, belt: widget.belt});
|
||||
}
|
||||
send.done({});
|
||||
},
|
||||
|
||||
slice(data, send) {
|
||||
send.data({ update:0.001, updateStatus:"slicing" });
|
||||
|
||||
const { settings } = data;
|
||||
const { mode } = settings;
|
||||
const driver = drivers[mode];
|
||||
const widget = wcache[data.id];
|
||||
|
||||
if (!(driver && driver.prepare)) {
|
||||
return console.log({ invalid_print_driver: mode, driver });
|
||||
}
|
||||
|
||||
let last = time(), now;
|
||||
|
||||
current.print = null;
|
||||
current.mode = settings.mode.toUpperCase();
|
||||
|
||||
widget.anno = data.anno || widget.anno;
|
||||
widget.settings = settings;
|
||||
widget.clearSlices();
|
||||
|
||||
driver.slice(settings, widget, (update, msg, alert) => {
|
||||
now = time();
|
||||
// on alert
|
||||
if (alert) send.data({ alert });
|
||||
// on update
|
||||
if (now - last < 10 && update < 0.99) return;
|
||||
if (update || msg) send.data({update: (0.05 + update * 0.95), updateStatus: msg});
|
||||
last = now;
|
||||
}, (error) => {
|
||||
if (error) {
|
||||
send.data({error: error});
|
||||
} else {
|
||||
const slices = widget.slices || [];
|
||||
send.data({send_start: time()});
|
||||
send.data({
|
||||
stats: widget.stats,
|
||||
slices: slices.length,
|
||||
});
|
||||
slices.forEach((slice,index) => {
|
||||
const state = { zeros: [] };
|
||||
send.data({index: index, slice: slice.encode(state)}, state.zeros);
|
||||
})
|
||||
send.data({send_end: time()});
|
||||
}
|
||||
send.done({done: true});
|
||||
widget.points = undefined;
|
||||
});
|
||||
},
|
||||
|
||||
sliceAll(data, send) {
|
||||
const { settings } = data;
|
||||
const { mode } = settings;
|
||||
const driver = drivers[mode];
|
||||
|
||||
if (driver.sliceAll) {
|
||||
driver.sliceAll(settings, send.data);
|
||||
}
|
||||
|
||||
send.done({done: true});
|
||||
},
|
||||
|
||||
prepare(data, send) {
|
||||
// create widget array from id:widget cache
|
||||
const widgets = Object.values(wcache);
|
||||
|
||||
// let client know we've started
|
||||
send.data({update:0.05, updateStatus:"preview"});
|
||||
|
||||
const { settings } = data;
|
||||
const { mode } = settings;
|
||||
const driver = drivers[mode];
|
||||
|
||||
if (!(driver && driver.prepare)) {
|
||||
return console.log({ invalid_print_driver: mode, driver });
|
||||
}
|
||||
|
||||
driver.prepare(widgets, settings, (progress, message, layer) => {
|
||||
const state = { zeros: [] };
|
||||
const emit = { progress, message, layer: (layer ? layer.encode(state) : undefined) };
|
||||
send.data(emit, state.zeros);
|
||||
}).then(() => {
|
||||
const unitScale = settings.controller.units === 'in' ? (1 / 25.4) : 1;
|
||||
const print = current.print || {};
|
||||
const minSpeed = (print.minSpeed || 0) * unitScale;
|
||||
const maxSpeed = (print.maxSpeed || 0) * unitScale;
|
||||
|
||||
send.data({ progress: 1, message: "transfer" });
|
||||
send.done({ done: true, minSpeed, maxSpeed });
|
||||
});
|
||||
},
|
||||
|
||||
export(data, send) {
|
||||
const mode = data.settings.mode;
|
||||
const driver = drivers[mode];
|
||||
|
||||
if (!(driver && driver.export)) {
|
||||
console.log({missing_export_driver: mode});
|
||||
return send.done()
|
||||
}
|
||||
|
||||
let output;
|
||||
driver.export(current.print, (line, direct) => {
|
||||
send.data({line}, direct);
|
||||
}, (done) => {
|
||||
// SLA workaround
|
||||
output = done;
|
||||
}, (debug) => {
|
||||
send.data({debug});
|
||||
});
|
||||
|
||||
// export in different modes, based on device type
|
||||
if (mode.toUpperCase() === "LASER") {
|
||||
let FExt = data.settings.device.gcodeFExt.toUpperCase();
|
||||
if (FExt === "DXF"){
|
||||
let dxf = driver.exportDXF(data.settings, current.print.output);
|
||||
send.data({line:dxf});
|
||||
} else if(FExt === "SVG") {
|
||||
let svg = driver.exportSVG(data.settings, current.print.output);
|
||||
send.data({line:svg});
|
||||
} else {
|
||||
// if not svg or dxf, default to gcode
|
||||
let gcode = driver.exportGCode(data.settings, current.print.output);
|
||||
send.data({line:gcode});
|
||||
}
|
||||
}
|
||||
|
||||
const {
|
||||
bounds,
|
||||
time,
|
||||
lines,
|
||||
bytes,
|
||||
distance,
|
||||
settings,
|
||||
segments,
|
||||
purges,
|
||||
labels
|
||||
} = current.print;
|
||||
|
||||
send.done({
|
||||
done: true,
|
||||
output: output ? output : {
|
||||
bounds,
|
||||
time,
|
||||
lines,
|
||||
bytes,
|
||||
distance,
|
||||
settings,
|
||||
segments,
|
||||
purges,
|
||||
labels
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
colors(data, send) {
|
||||
const { colors, max } = data;
|
||||
const colorMap = {};
|
||||
colors.forEach(color => {
|
||||
colorMap[color] = render.rate_to_color(color, max);
|
||||
});
|
||||
send.done(colorMap);
|
||||
},
|
||||
|
||||
parse(args, send) {
|
||||
const { settings, code, type } = args;
|
||||
const origin = settings.origin;
|
||||
const offset = {
|
||||
x: origin.x,
|
||||
y: -origin.y,
|
||||
z: origin.z
|
||||
};
|
||||
const device = settings.device;
|
||||
const print = current.print = newPrint(settings, Object.values(wcache));
|
||||
const tools = device.extruders;
|
||||
const mode = settings.mode;
|
||||
const thin = settings.controller.lineType === 'line' || mode !== 'FDM';
|
||||
const flat = settings.controller.lineType === 'flat' && mode === 'FDM';
|
||||
const parsed = print.parseGCode(code, offset, progress => {
|
||||
send.data({ progress: progress * 0.25 });
|
||||
}, done => {
|
||||
const minSpeed = print.minSpeed;
|
||||
const maxSpeed = print.maxSpeed;
|
||||
render.path(done.output, progress => {
|
||||
send.data({ progress: 0.25 + progress * 0.75 });
|
||||
}, { thin: thin || print.belt, flat, tools })
|
||||
.then(layers => {
|
||||
send.done({parsed: codec.encode(layers), maxSpeed, minSpeed});
|
||||
});
|
||||
}, {
|
||||
fdm: mode === 'FDM',
|
||||
cam: mode === 'CAM',
|
||||
belt: device.bedBelt
|
||||
});
|
||||
},
|
||||
|
||||
parse_svg(parsed, send) {
|
||||
parsed.forEach(layer => {
|
||||
layer.forEach(out => {
|
||||
const { x, y, z } = out.point;
|
||||
out.point = newPoint(x,y,z || 0);
|
||||
});
|
||||
});
|
||||
const print = current.print = newPrint(null, Object.values(wcache));
|
||||
render.path(parsed, progress => {
|
||||
send.data({ progress });
|
||||
}, { thin: true })
|
||||
.then(layers => {
|
||||
send.done({parsed: codec.encode(layers)});
|
||||
});
|
||||
},
|
||||
|
||||
config(data, send) {
|
||||
const update = {};
|
||||
if (data.base) {
|
||||
update.base = data.base;
|
||||
Object.assign(base.config, data.base);
|
||||
} else {
|
||||
console.log({invalid:data});
|
||||
}
|
||||
for (let minion of minions) {
|
||||
minion.postMessage({
|
||||
cmd: "config",
|
||||
base: data.base
|
||||
});
|
||||
}
|
||||
send.done({config: update});
|
||||
},
|
||||
|
||||
image2mesh(info, send) {
|
||||
let { device } = info.settings;
|
||||
load.PNG.parse(info.png, {
|
||||
outWidth: device.bedDepth,
|
||||
outHeight: device.bedWidth,
|
||||
inv_image: info.inv_image,
|
||||
inv_alpha: info.inv_alpha,
|
||||
border: info.border,
|
||||
blur: info.blur,
|
||||
base: info.base,
|
||||
progress(progress) { send.data({ progress }) },
|
||||
done(vertices) { send.done({ vertices }, [ vertices.buffer ])}
|
||||
});
|
||||
},
|
||||
|
||||
gerber2mesh(data, send) {
|
||||
const vertices = load.GBR.toMesh(data, { progress(pct) {
|
||||
send.data({ progress: pct/100 });
|
||||
} } );
|
||||
send.data({ vertices }, [ vertices.buffer ]);
|
||||
},
|
||||
|
||||
zip(data, send) {
|
||||
let { files } = data;
|
||||
let zip = new JSZip();
|
||||
for (let file of files) {
|
||||
zip.file(file.name, file.data);
|
||||
}
|
||||
zip.generateAsync({
|
||||
type: "uint8array",
|
||||
compression: "DEFLATE",
|
||||
compressionOptions: { level: 6 },
|
||||
streamFiles: true
|
||||
}, progress => {
|
||||
send.data(progress);
|
||||
}).then(output => {
|
||||
send.done(output);
|
||||
});
|
||||
},
|
||||
|
||||
wasm(data, send) {
|
||||
if (data.enable) {
|
||||
wasm_ctrl.enable();
|
||||
} else {
|
||||
wasm_ctrl.disable();
|
||||
}
|
||||
minwork.broadcast("wasm", { enable: data.enable ? true : false });
|
||||
send.done({ wasm: data });
|
||||
},
|
||||
|
||||
putCache(msg, send) {
|
||||
const { key, data } = msg;
|
||||
// console.log({ worker_putCache: key, data });
|
||||
if (data) {
|
||||
pcache[key] = data;
|
||||
} else {
|
||||
delete pcache[key];
|
||||
}
|
||||
minwork.broadcast("putCache", msg);
|
||||
send.done({ ok: true });
|
||||
},
|
||||
|
||||
clearCache(msg, send) {
|
||||
pcache = {};
|
||||
minwork.broadcast("clearCache", msg);
|
||||
send.done({ ok: true });
|
||||
}
|
||||
};
|
||||
|
||||
function is_async(fn) {
|
||||
return fn.constructor.name === "AsyncFunction";
|
||||
}
|
||||
|
||||
dispatch.send = (msg, direct) => {
|
||||
self.postMessage(msg, direct);
|
||||
};
|
||||
|
||||
dispatch.onmessage = self.onmessage = async function(e) {
|
||||
let time_recv = time(),
|
||||
msg = e.data || {},
|
||||
run = dispatch[msg.task],
|
||||
send = {
|
||||
data : function(data, direct) {
|
||||
// if (direct && direct.length) {
|
||||
// console.log( direct.map(z => z.byteLength).reduce((a,v) => a+v) );
|
||||
// }
|
||||
dispatch.send({
|
||||
seq: msg.seq,
|
||||
task: msg.task,
|
||||
done: false,
|
||||
data: data
|
||||
}, direct);
|
||||
// if (direct && direct.length) {
|
||||
// console.log( direct.map(z => z.byteLength).reduce((a,v) => a+v) );
|
||||
// }
|
||||
},
|
||||
done : function(data,direct) {
|
||||
dispatch.send({
|
||||
seq: msg.seq,
|
||||
task: msg.task,
|
||||
done: true,
|
||||
data: data
|
||||
}, direct);
|
||||
}
|
||||
};
|
||||
|
||||
if (run) {
|
||||
try {
|
||||
let time_xfer = (time_recv - msg.time),
|
||||
output = is_async(run) ? await run(msg.data, send) : run(msg.data, send),
|
||||
time_send = time(),
|
||||
time_proc = time_send - time_recv;
|
||||
|
||||
if (output) dispatch.send({
|
||||
seq: msg.seq,
|
||||
task: msg.task,
|
||||
time_send: time_xfer,
|
||||
time_proc: time_proc,
|
||||
// replaced on reply side
|
||||
time_recv: time(),
|
||||
data: output
|
||||
});
|
||||
} catch (wrkerr) {
|
||||
console.trace(wrkerr.stack);
|
||||
send.done({error: wrkerr.toString()});
|
||||
}
|
||||
} else {
|
||||
console.log({worker_unhandled: e, msg, fn: dispatch[msg.task]});
|
||||
}
|
||||
};
|
||||
|
||||
const worker = self.kiri_worker = {
|
||||
cache: wcache,
|
||||
current,
|
||||
dispatch,
|
||||
drivers,
|
||||
version,
|
||||
minions: minwork
|
||||
};
|
||||
|
||||
// initilize driver mode handlers
|
||||
CAM.init(worker);
|
||||
FDM.init(worker);
|
||||
LASER.init(worker);
|
||||
SLA.init(worker);
|
||||
Loading…
Reference in a new issue