checkpoint kiri module migration work
This commit is contained in:
parent
e341b98849
commit
7d22cc1029
201 changed files with 41262 additions and 85 deletions
518
src2/kiri-mode/cam/export.js
Normal file
518
src2/kiri-mode/cam/export.js
Normal file
|
|
@ -0,0 +1,518 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
import { base } from '../../geo/base.js';
|
||||
|
||||
const { util } = base;
|
||||
|
||||
/**
|
||||
* @returns {Array} gcode lines
|
||||
*/
|
||||
export function cam_export(print, online) {
|
||||
const widget = print.widgets[0];
|
||||
if (!widget) return;
|
||||
|
||||
console.log({ cam_export: print });
|
||||
|
||||
const { settings } = print;
|
||||
const { device, tools } = settings;
|
||||
|
||||
let i,
|
||||
time = 0,
|
||||
lines = 0,
|
||||
bytes = 0,
|
||||
opnum = 0,
|
||||
factor = 1,
|
||||
output = [],
|
||||
spindle = 0,
|
||||
newSpindle = 0,
|
||||
toolChanges = 0,
|
||||
spindleMax = device.spindleMax,
|
||||
origin = settings.origin || { x: 0, y: 0, z: 0 },
|
||||
space = device.gcodeSpace ? ' ' : '',
|
||||
isRML = device.gcodeFExt?.toLowerCase() === 'rml',
|
||||
stripComments = device.gcodeStrip || false,
|
||||
cmdToolChange = device.gcodeChange || [ "M6 T{tool}" ],
|
||||
cmdSpindle = device.gcodeSpindle || [ "M3 S{speed}" ],
|
||||
cmdDwell = device.gcodeDwell || [ "G4 P{time}" ],
|
||||
axis = { X: 'X', Y: 'Y', Z: 'Z', A: 'A', F: 'F', R: 'R', I: 'I', J: 'J' },
|
||||
dev = settings.device,
|
||||
spro = settings.process,
|
||||
maxZd = spro.camFastFeedZ,
|
||||
maxXYd = spro.camFastFeed,
|
||||
decimals = base.config.gcode_decimals || 3,
|
||||
pos = { x:null, y:null, z:null, a:undefined, f:null, t:null, emit:null },
|
||||
line,
|
||||
cidx,
|
||||
mode,
|
||||
point,
|
||||
points = 0,
|
||||
lastGn = '',
|
||||
rewrite_comments = false,
|
||||
compact_output = false,
|
||||
lasering = false,
|
||||
laserOp,
|
||||
stock = settings.stock || { },
|
||||
ztOff = stock.z - widget.track.top,
|
||||
bounds = widget.getBoundingBox(),
|
||||
zmax = stock.z,
|
||||
runbox = {
|
||||
max: { x:-Infinity, y:-Infinity, z:-Infinity},
|
||||
min: { x:Infinity, y:Infinity, z:Infinity}
|
||||
},
|
||||
offset = {
|
||||
x: -origin.x,
|
||||
y: origin.y,
|
||||
z: spro.camOriginTop ? origin.z - zmax : origin.z
|
||||
},
|
||||
scale = {
|
||||
x: 1,
|
||||
y: 1,
|
||||
z: 1
|
||||
},
|
||||
consts = {
|
||||
box: runbox,
|
||||
tool: 0,
|
||||
tool_name: "unknown",
|
||||
top: (offset ? dev.bedDepth : dev.bedDepth/2),
|
||||
left: (offset ? 0 : -dev.bedWidth/2),
|
||||
right: (offset ? dev.bedWidth : dev.bedWidth/2),
|
||||
bottom: (offset ? 0 : -dev.bedDepth/2),
|
||||
time_sec: 0,
|
||||
time_ms: 0,
|
||||
time: 0
|
||||
};
|
||||
|
||||
function section(section) {
|
||||
append();
|
||||
online({ section });
|
||||
}
|
||||
|
||||
function append(line) {
|
||||
if (line) {
|
||||
lines++;
|
||||
bytes += line.length;
|
||||
if (rewrite_comments) {
|
||||
const spos = line.indexOf(';');
|
||||
if (spos >= 0) {
|
||||
const left = line.substring(0, spos);
|
||||
const right = '(' + line.substring(spos)
|
||||
.replace(/; /g,'')
|
||||
.replace(/;/g,'') + ')';
|
||||
line = [ left, right ].join('');
|
||||
}
|
||||
}
|
||||
output.append(line);
|
||||
}
|
||||
if (!line || output.length > 1000) {
|
||||
online(output.join("\r\n"));
|
||||
output = [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Take an array of lines and emit them after:
|
||||
* - splitting a string into an array
|
||||
* - stripping comments if !isRML and stripComments
|
||||
* - applying constant replacements
|
||||
* - applying Inch/Millimeter conversions if G20 or G21 are found
|
||||
*
|
||||
* @param {string|Array} array - an array of lines or a single string
|
||||
* @param {Object} consts - a dictionary of constants to replace
|
||||
*/
|
||||
function filterEmit(array, consts) {
|
||||
if (!array) {
|
||||
return;
|
||||
}
|
||||
if (typeof(array) === 'string') {
|
||||
array = array.split('\n');
|
||||
}
|
||||
for (i=0; i<array.length; i++) {
|
||||
line = print.constReplace(array[i], consts);
|
||||
if (!isRML && stripComments && (cidx = line.indexOf(";")) >= 0) {
|
||||
line = line.substring(0, cidx).trim();
|
||||
if (line.length === 0) continue;
|
||||
}
|
||||
if (line.indexOf('G20') === 0) {
|
||||
factor = 1/25.4;
|
||||
consts.top = (offset ? dev.bedDepth : dev.bedDepth/2) * factor;
|
||||
consts.left = (offset ? 0 : -dev.bedWidth/2) * factor;
|
||||
consts.right = (offset ? dev.bedWidth : dev.bedWidth/2) * factor;
|
||||
consts.bottom = (offset ? 0 : -dev.bedDepth/2) * factor;
|
||||
} else if (line.indexOf('G21') === 0) {
|
||||
factor = 1;
|
||||
}
|
||||
append(line);
|
||||
}
|
||||
}
|
||||
|
||||
function add0(val, opt) {
|
||||
let s = val.toString(),
|
||||
d = s.indexOf(".");
|
||||
if (d < 0 && decimals > 0) {
|
||||
return opt ? s : s + '.0';
|
||||
} else {
|
||||
return val.toFixed(decimals);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Move to a point, applies post-processing including for laser/RML, and appends to output
|
||||
* @param {Point} out.point - the point to move to
|
||||
* @param {Tool} [out.tool] - the tool to move with
|
||||
* @param {number} [out.speed] - the speed to move at (in mm/min)
|
||||
* @param {number} [out.emit] - the emission state (0=off, 1=on)
|
||||
* @param {boolean} [opt.newOp] - whether to force a new operation
|
||||
* @param {number} [opt.dx] - the delta X change (if not given, uses out.point.x - pos.x)
|
||||
* @param {number} [opt.dy] - the delta Y change (if not given, uses out.point.y - pos.y)
|
||||
* @param {number} [opt.dz] - the delta Z change (if not given, uses out.point.z - pos.z)
|
||||
* @param {number} [opt.time] - the time to move (in ms) (if not given, calculate from speed)
|
||||
*/
|
||||
function moveTo(out, opt = {}) {
|
||||
let laser = out.type === 'laser';
|
||||
let newpos = out.point;
|
||||
|
||||
// no point == dwell
|
||||
// out.speed = time to dwell in ms
|
||||
if (!newpos) {
|
||||
time += out.speed / 60;
|
||||
consts.time_sec = out.speed / 1000;
|
||||
consts.time_ms = out.speed;
|
||||
consts.time = consts.time_sec;
|
||||
filterEmit(cmdDwell, consts);
|
||||
return;
|
||||
}
|
||||
|
||||
newpos.x = util.round(newpos.x, decimals);
|
||||
newpos.y = util.round(newpos.y, decimals);
|
||||
newpos.z = util.round(newpos.z, decimals);
|
||||
|
||||
// on tool change
|
||||
let changeTool = !(out.tool && pos.t) || out.tool.getID() !== pos.t.getID();
|
||||
if (changeTool) {
|
||||
pos.t = out.tool;
|
||||
consts.tool = pos.t.getNumber();
|
||||
consts.tool_name = pos.t.getName();
|
||||
if (!laserOp && (spro.camToolInit || toolChanges > 0)) {
|
||||
filterEmit(cmdToolChange, { ...consts, spindle: newSpindle } );
|
||||
}
|
||||
toolChanges++;
|
||||
}
|
||||
|
||||
// on spindle change (deferred from layer transition so it's post-toolchange)
|
||||
if (spindleMax && newSpindle && (changeTool || newSpindle !== spindle || opt.newOp)) {
|
||||
spindle = newSpindle;
|
||||
if (!laserOp) {
|
||||
if (spindle > 0) {
|
||||
let speed = Math.abs(spindle);
|
||||
filterEmit(cmdSpindle, { speed, spindle: speed, rpm: speed });
|
||||
} else {
|
||||
append("M4");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// enforce XY origin at start of print
|
||||
if (points === 0 || changeTool) {
|
||||
pos.x = pos.y = pos.z = 0;
|
||||
}
|
||||
|
||||
// split first move to X,Y then Z for that new location
|
||||
// safety to prevent tool crashing
|
||||
if (points === 0 || changeTool) {
|
||||
points++;
|
||||
if (spro.camFirstZMax) {
|
||||
moveTo({
|
||||
// speed: Infinity,
|
||||
tool: out.tool,
|
||||
point: { x: pos.x, y: pos.y, z: newpos.z, a: newpos.a },
|
||||
emit: 0,
|
||||
}, {
|
||||
dx: 0, dy: 0, dz: 1, time: 0
|
||||
});
|
||||
moveTo({
|
||||
// speed: Infinity,
|
||||
tool: out.tool,
|
||||
point: { x: newpos.x, y: newpos.y, z: newpos.z, a: pos.a },
|
||||
emit: 1,
|
||||
}, {
|
||||
dx: 1, dy: 1, dz: 0, time: 0
|
||||
});
|
||||
} else {
|
||||
moveTo({
|
||||
// speed: Infinity,
|
||||
tool: out.tool,
|
||||
point: { x: newpos.x, y: newpos.y, z: pos.z, a: pos.a },
|
||||
emit: 0
|
||||
}, {
|
||||
dx: 1, dy: 1, dz: 0, time: 0
|
||||
});
|
||||
moveTo({
|
||||
// speed: Infinity,
|
||||
tool: out.tool,
|
||||
point: { x: newpos.x, y: newpos.y, z: newpos.z, a: newpos.a },
|
||||
emit: 1
|
||||
}, {
|
||||
dx: 0, dy: 0, dz: 1, time: 0
|
||||
});
|
||||
}
|
||||
points--;
|
||||
return;
|
||||
}
|
||||
let gn;
|
||||
if ( out.emit >=0 && out.emit <= 3) gn = `G${out.emit}`;
|
||||
let speed = out.speed,
|
||||
arc = out.emit == 2 || out.emit == 3,
|
||||
center = out.center,
|
||||
nl = (compact_output && lastGn === gn) ? [] : [gn],
|
||||
dx = opt.dx || newpos.x - pos.x,
|
||||
dy = opt.dy || newpos.y - pos.y,
|
||||
dz = opt.dz || newpos.z - pos.z,
|
||||
da = newpos.a != pos.a,
|
||||
maxf = dz ? maxZd : maxXYd,
|
||||
feed = Math.min(speed || maxf, maxf),
|
||||
dist = Math.sqrt(dx * dx + dy * dy + dz * dz),
|
||||
newFeed = feed && feed !== pos.f;
|
||||
|
||||
|
||||
// drop dup points (all deltas are 0)
|
||||
if (!arc && !(dx || dy || dz || da)) {
|
||||
return;
|
||||
}
|
||||
|
||||
lastGn = gn;
|
||||
|
||||
if (dx || newpos.x !== pos.x) {
|
||||
pos.x = newpos.x;
|
||||
runbox.min.x = Math.min(runbox.min.x, pos.x);
|
||||
runbox.max.x = Math.max(runbox.max.x, pos.x);
|
||||
nl.append(space).append(axis.X).append(add0(consts.pos_x = pos.x * factor));
|
||||
}
|
||||
if (dy || newpos.y !== pos.y) {
|
||||
pos.y = newpos.y;
|
||||
runbox.min.y = Math.min(runbox.min.y, pos.y);
|
||||
runbox.max.y = Math.max(runbox.max.y, pos.y);
|
||||
nl.append(space).append(axis.Y).append(add0(consts.pos_y = pos.y * factor));
|
||||
}
|
||||
if (dz || newpos.z !== pos.z) {
|
||||
pos.z = newpos.z;
|
||||
runbox.min.z = Math.min(runbox.min.z, pos.z);
|
||||
runbox.max.z = Math.max(runbox.max.z, pos.z);
|
||||
nl.append(space).append(axis.Z).append(add0(consts.pos_z = pos.z * factor));
|
||||
}
|
||||
if (da) {
|
||||
pos.a = newpos.a;
|
||||
nl.append(space).append(axis.A).append(add0(consts.pos_a = pos.a * -factor));
|
||||
}
|
||||
if (newFeed) {
|
||||
pos.f = feed;
|
||||
nl.append(space).append(axis.F).append(add0(consts.feed = feed * factor, true));
|
||||
}
|
||||
if(arc){
|
||||
nl.append(space).append(axis.I).append(add0(center.x * factor, true))
|
||||
.append(space).append(axis.J).append(add0(center.y * factor, true));
|
||||
}
|
||||
|
||||
// temp hack to support RML1 dialect from a file extensions trigger
|
||||
if (isRML) {
|
||||
if (speed) {
|
||||
if (newFeed) {
|
||||
append(`VS${feed};`);
|
||||
}
|
||||
nl = [ axis.Z, add0(pos.x * factor), ",", add0(pos.y * factor), ",", add0(pos.z * factor), ";" ];
|
||||
} else {
|
||||
nl = [ "PU", add0(pos.x * factor), ",", add0(pos.y * factor), ";" ];
|
||||
}
|
||||
}
|
||||
|
||||
if (!lasering && laser) {
|
||||
// enable laser
|
||||
filterEmit(laserOp.on, consts);
|
||||
}
|
||||
|
||||
// if (laser && pos.emit !== out.emit) {
|
||||
if (laser) {
|
||||
nl.append(space).append(`S${add0(out.emit)}`);
|
||||
}
|
||||
|
||||
if (laser) {
|
||||
pos.emit = out.emit;
|
||||
}
|
||||
|
||||
if (lasering && !laser) {
|
||||
// disable laser
|
||||
filterEmit(laserOp.off, consts);
|
||||
}
|
||||
|
||||
// update lasering state
|
||||
lasering = laser;
|
||||
|
||||
// update time calculation
|
||||
time += opt.time >= 0 ? opt.time : (dist / (pos.f || 1000)) * 60;
|
||||
consts.time = Math.round(time);
|
||||
// if (comment && !stripComments) {
|
||||
// nl.append(" ; ").append(comment);
|
||||
// nl.append(" ; ").append(points);
|
||||
// }
|
||||
append(nl.join(''));
|
||||
points++;
|
||||
}
|
||||
|
||||
// look for SCALE header directive
|
||||
let gcodePre = [];
|
||||
for (let line of device.gcodePre) {
|
||||
if (line.indexOf(';; SCALE ') === 0) {
|
||||
try {
|
||||
let map = JSON.parse(line.substring(9));
|
||||
if (map.X) scale.x = map.X;
|
||||
if (map.Y) scale.y = map.Y;
|
||||
if (map.Z) scale.z = map.Z;
|
||||
if (map.A) scale.a = map.A;
|
||||
if (map.DEC !== undefined) decimals = parseInt(map.DEC);
|
||||
console.log('export scaling applied', map);
|
||||
} catch (e) {
|
||||
console.log('malformed scale directive', line);
|
||||
}
|
||||
} else if (line.indexOf(';; REWRITE-COMMENTS-PARENS') === 0) {
|
||||
rewrite_comments = true;
|
||||
} else if (line.indexOf(';; DECIMALS') === 0) {
|
||||
decimals = parseInt(line.split('=')[1].trim() || 3) || 3;
|
||||
} else if (line.indexOf(';; COMPACT-OUTPUT') === 0) {
|
||||
compact_output = true;
|
||||
stripComments = true;
|
||||
space = '';
|
||||
} else if (line.indexOf(";; AXISMAP ") === 0) {
|
||||
let axmap = JSON.parse(line.substring(11).trim());
|
||||
for (let key in axmap) {
|
||||
axis[key] = axmap[key];
|
||||
}
|
||||
} else {
|
||||
gcodePre.push(line);
|
||||
}
|
||||
}
|
||||
|
||||
section('header');
|
||||
if (!stripComments) {
|
||||
append(`; Generated by Kiri:Moto ${kiri.version}`);
|
||||
append(`; ${new Date().toString()}`);
|
||||
filterEmit(["; Bed left:{left} right:{right} top:{top} bottom:{bottom}"], consts);
|
||||
append(`; Stock X:${stock.x?.round(2)} Y:${stock.y?.round(2)} Z:${stock.z?.round(2)}`);
|
||||
append(`; Target: ${settings.filter[settings.mode]}`);
|
||||
append("; --- process ---");
|
||||
for (let pk in spro) {
|
||||
if (pk !== "ops" && pk !== "op2") {
|
||||
append("; " + pk + " = " + spro[pk]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// collect tool info to add to header
|
||||
let toolz = {}, ctool;
|
||||
const isOffset = offset && (offset.x || offset.y);
|
||||
// remap points as necessary for origins, offsets, inversions
|
||||
print.output.forEach(layer => {
|
||||
if (!Array.isArray(layer)) {
|
||||
return;
|
||||
}
|
||||
layer.forEach(out => {
|
||||
if (out.gcode || out.type === 'lerp') {
|
||||
return;
|
||||
}
|
||||
if (out.tool && out.tool !== ctool) {
|
||||
ctool = out.tool;
|
||||
toolz[out.tool] = ctool;
|
||||
}
|
||||
point = out.point;
|
||||
if (!point || point.mod) return;
|
||||
// ensure not point is modified twice
|
||||
point.mod = 1;
|
||||
if (isOffset) {
|
||||
point.x += offset.x;
|
||||
point.y += offset.y;
|
||||
}
|
||||
if (scale) {
|
||||
point.x *= scale.x;
|
||||
point.y *= scale.y;
|
||||
point.z *= scale.z;
|
||||
}
|
||||
if (point.a && scale.a) {
|
||||
point.a *= scale.a;
|
||||
}
|
||||
if (spro.outputInvertX) point.x = -point.x;
|
||||
if (spro.outputInvertY) point.y = -point.y;
|
||||
if (spro.camOriginTop) point.z = point.z - zmax;
|
||||
});
|
||||
});
|
||||
|
||||
if (!stripComments) {
|
||||
// emit tools used in comments
|
||||
append("; --- tools ---");
|
||||
Object.keys(toolz).sort().forEach(tn => {
|
||||
let { number, flute_diam, flute_len, metric } = toolz[tn].tool;
|
||||
append(`; tool#=${number} flute=${flute_diam} len=${flute_len} unit=${metric ? 'metric' : 'imperial'}`);
|
||||
});
|
||||
}
|
||||
// emit gcode preamble
|
||||
filterEmit(gcodePre, consts);
|
||||
|
||||
// emit all points in layer/point order
|
||||
for (let layerout of print.output) {
|
||||
const newmode = layerout.mode;
|
||||
if (newmode) {
|
||||
if (newmode.type === 'laser on') {
|
||||
laserOp = newmode;
|
||||
} else if (newmode.type === 'laser off') {
|
||||
laserOp = undefined;
|
||||
// force tool change in case laser tool matches next tool
|
||||
pos.t = undefined;
|
||||
}
|
||||
}
|
||||
const firstOut = layerout[0];
|
||||
const newOp = newmode && !newmode.silent && mode !== newmode;
|
||||
if (newOp) {
|
||||
if (mode && !stripComments) {
|
||||
append("; ending " + mode.type + " op after " + Math.round(time) + " seconds");
|
||||
}
|
||||
mode = newmode;
|
||||
if (mode) {
|
||||
section(`op-${opnum++}-${mode.type}`);
|
||||
}
|
||||
if (!stripComments && mode) {
|
||||
append("; starting " + mode.type + " op");
|
||||
}
|
||||
}
|
||||
newSpindle = layerout.spindle;
|
||||
// iterate over layer output records
|
||||
layerout.forEach((out, ind) => {
|
||||
if (out.type === 'lerp') {
|
||||
// suppress display only lerp points
|
||||
return;
|
||||
}
|
||||
if (out.gcode && Array.isArray(out.gcode)) {
|
||||
filterEmit(out.gcode, consts);
|
||||
} else {
|
||||
moveTo(out, { newOp: ind === 0 ? newOp : false });
|
||||
}
|
||||
});
|
||||
if (lasering && laserOp) {
|
||||
// disable laser
|
||||
filterEmit(laserOp.off, consts);
|
||||
lasering = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (mode && !stripComments) {
|
||||
append("; ending " + mode.type + " op after " + Math.round(time) + " seconds");
|
||||
}
|
||||
|
||||
// emit gcode post
|
||||
section('footer');
|
||||
filterEmit(device.gcodePost, consts);
|
||||
|
||||
// flush buffered gcode
|
||||
append();
|
||||
|
||||
print.time = time;
|
||||
print.lines = lines;
|
||||
print.bytes = bytes + lines - 1;
|
||||
print.bounds = runbox;
|
||||
};
|
||||
Loading…
Reference in a new issue