move cli, engine, frame access points to kiri-run

This commit is contained in:
Stewart Allen 2022-02-19 13:13:53 -05:00
commit 827847a321
5 changed files with 3 additions and 3 deletions

219
src/kiri-run/cli.js Normal file
View file

@ -0,0 +1,219 @@
/** 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",
process: "src/cli/kiri-fdm-process.json",
device: "src/cli/kiri-fdm-device.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)",
" --source=[file] | source file list (defaults to kiri engine)",
" --device=[file] | device definition file (json)",
" --process=[file] | process 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 = {},
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 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 = 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.setProcess(process))
.then(() => engine.setDevice(device))
.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();

167
src/kiri-run/engine.js Normal file
View file

@ -0,0 +1,167 @@
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
"use strict";
(function() {
let KIRI = self.kiri = self.kiri || {};
KIRI.newEngine = ()=> { return new Engine ()};
if (!KIRI.api) {
KIRI.api = {
event: {
emit: () => {}
}
};
}
class Engine {
constructor() {
this.widget = KIRI.newWidget();
this.settings = {
mode: "FDM",
controller: {},
render: false,
filter: { FDM: "internal" },
device: KIRI.conf.defaults.fdm.d, // device profile
process: KIRI.conf.defaults.fdm.p, // slicing settings
widget: { [ this.widget.id ]: {} }
};
this.listener = () => {};
}
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() {
KIRI.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);
}
});
}
setListener(listener) {
this.listener = listener;
return this;
}
setRender(bool) {
this.settings.render = bool;
return 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;
}
setTools(tools) {
this.settings.tools = tools;
return this;
}
setStock(stock) {
this.settings.stock = stock;
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) => {
KIRI.client.clear();
KIRI.client.sync([ this.widget ]);
KIRI.client.rotate(this.settings);
KIRI.client.slice(this.settings, this.widget, msg => {
this.listener({slice:msg});
if (msg.error) {
reject(msg.error);
}
if (msg.done) {
accept(this);
}
});
KIRI.client.unrotate(this.settings);
});
}
prepare() {
return new Promise((accept, reject) => {
KIRI.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 = [];
KIRI.client.export(this.settings, segment => {
this.listener({export:{segment}});
output.push(segment);
}, done => {
this.listener({export:{done}});
accept(output.join('\r\n'));
});
});
}
}
})();

87
src/kiri-run/frame.js Normal file
View file

@ -0,0 +1,87 @@
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
"use strict";
(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 })},
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;
})();