OctoPrint/octoprint/static/gcodeviewer/js/Worker.js
Gina Häußge 003109d59e Made gcode viewer capable of handling progress information
GCode viewer now updates its visualization (during printing) based on the current progress percentage, not based on the current line (which will not be available any more as part of the progress tuple with gcode streaming)
2013-06-09 14:00:15 +02:00

473 lines
20 KiB
JavaScript

/**
* User: hudbrog (hudbrog@gmail.com)
* Date: 10/24/12
* Time: 12:18 PM
*/
var gcode;
var firstReport;
var z_heights = {};
var model = [];
var gCodeOptions = {
sortLayers: false,
purgeEmptyLayers: true,
analyzeModel: false
};
var max = {x: undefined, y: undefined, z: undefined};
var min = {x: undefined, y: undefined, z: undefined};
var modelSize = {x: undefined, y: undefined, z: undefined};
var filamentByLayer = {};
var totalFilament=0;
var printTime=0;
var printTimeByLayer = {};
var layerHeight=0;
var layerCnt = 0;
var speeds = {extrude: [], retract: [], move: []};
var speedsByLayer = {extrude: {}, retract: {}, move: {}};
var sendLayerToParent = function(layerNum, z, progress){
self.postMessage({
"cmd": "returnLayer",
"msg": {
cmds: model[layerNum],
layerNum: layerNum,
zHeightObject: {zValue: z, layer: z_heights[z]},
isEmpty: false,
progress: progress
}
});
};
var sendMultiLayerToParent = function(layerNum, z, progress){
var tmpModel = [];
var tmpZHeight = {};
for(var i=0;i<layerNum.length;i++){
tmpModel[layerNum[i]] = model[layerNum[i]];
tmpZHeight[layerNum[i]] = z_heights[z[i]];
}
self.postMessage({
"cmd": "returnMultiLayer",
"msg": {
model: tmpModel,
layerNum: layerNum,
zHeightObject: {zValue: z, layer: tmpZHeight},
isEmpty: false,
progress: progress
}
});
};
var sendSizeProgress = function(progress){
self.postMessage({
"cmd": "analyzeProgress",
"msg": {
progress: progress,
printTime: printTime
}
});
};
var sendAnalyzeDone = function(){
self.postMessage({
"cmd": "analyzeDone",
"msg": {
max: max,
min: min,
modelSize: modelSize,
totalFilament:totalFilament,
filamentByLayer: filamentByLayer,
printTime: printTime,
layerHeight: layerHeight,
layerCnt: layerCnt,
layerTotal: model.length,
speeds: speeds,
speedsByLayer: speedsByLayer,
printTimeByLayer: printTimeByLayer
}
});
};
var purgeLayers = function(){
var purge=true;
for(var i=0;i<model.length;i++){
purge=true;
if(!model[i])purge=true;
else {
for(var j=0;j<model[i].length;j++){
if(model[i][j].extrude)purge=false;
}
}
if(!purge){
layerCnt+=1;
}
}
// self.postMessage('LayerCnt: ' + layerCnt);
};
var analyzeModel = function(){
var i,j;
var x_ok=false, y_ok=false;
var cmds;
var tmp1= 0, tmp2=0;
var speedIndex=0;
var type;
var printTimeAdd=0;
// var moveTime=0;
for(i=0;i<model.length;i++){
cmds = model[i];
if(!cmds)continue;
for(j=0;j<cmds.length;j++){
x_ok=false;
y_ok=false;
if(typeof(cmds[j].x) !== 'undefined'&&typeof(cmds[j].prevX) !== 'undefined'&&typeof(cmds[j].extrude) !== 'undefined'&&cmds[j].extrude&&!isNaN(cmds[j].x))
{
max.x = parseFloat(max.x)>parseFloat(cmds[j].x)?parseFloat(max.x):parseFloat(cmds[j].x);
max.x = parseFloat(max.x)>parseFloat(cmds[j].prevX)?parseFloat(max.x):parseFloat(cmds[j].prevX);
min.x = parseFloat(min.x)<parseFloat(cmds[j].x)?parseFloat(min.x):parseFloat(cmds[j].x);
min.x = parseFloat(min.x)<parseFloat(cmds[j].prevX)?parseFloat(min.x):parseFloat(cmds[j].prevX);
x_ok=true;
}
if(typeof(cmds[j].y) !== 'undefined'&&typeof(cmds[j].prevY) !== 'undefined'&&typeof(cmds[j].extrude) !== 'undefined'&&cmds[j].extrude&&!isNaN(cmds[j].y)){
max.y = parseFloat(max.y)>parseFloat(cmds[j].y)?parseFloat(max.y):parseFloat(cmds[j].y);
max.y = parseFloat(max.y)>parseFloat(cmds[j].prevY)?parseFloat(max.y):parseFloat(cmds[j].prevY);
min.y = parseFloat(min.y)<parseFloat(cmds[j].y)?parseFloat(min.y):parseFloat(cmds[j].y);
min.y = parseFloat(min.y)<parseFloat(cmds[j].prevY)?parseFloat(min.y):parseFloat(cmds[j].prevY);
y_ok=true;
}
if(typeof(cmds[j].prevZ) !== 'undefined'&&typeof(cmds[j].extrude) !== 'undefined'&&cmds[j].extrude&&!isNaN(cmds[j].prevZ)){
max.z = parseFloat(max.z)>parseFloat(cmds[j].prevZ)?parseFloat(max.z):parseFloat(cmds[j].prevZ);
min.z = parseFloat(min.z)<parseFloat(cmds[j].prevZ)?parseFloat(min.z):parseFloat(cmds[j].prevZ);
}
if(typeof(cmds[j].extrude) !== 'undefined'||cmds[j].retract!=0){
totalFilament+=cmds[j].extrusion;
if(!filamentByLayer[cmds[j].prevZ])filamentByLayer[cmds[j].prevZ]=0;
filamentByLayer[cmds[j].prevZ]+=cmds[j].extrusion;
}
if(x_ok&&y_ok){
printTimeAdd = Math.sqrt(Math.pow(parseFloat(cmds[j].x)-parseFloat(cmds[j].prevX),2)+Math.pow(parseFloat(cmds[j].y)-parseFloat(cmds[j].prevY),2))/(cmds[j].speed/60);
}else if(cmds[j].retract===0&&cmds[j].extrusion!==0){
tmp1 = Math.sqrt(Math.pow(parseFloat(cmds[j].x)-parseFloat(cmds[j].prevX),2)+Math.pow(parseFloat(cmds[j].y)-parseFloat(cmds[j].prevY),2))/(cmds[j].speed/60);
tmp2 = Math.abs(parseFloat(cmds[j].extrusion)/(cmds[j].speed/60));
printTimeAdd = tmp1>=tmp2?tmp1:tmp2;
}else if(cmds[j].retract!==0){
printTimeAdd = Math.abs(parseFloat(cmds[j].extrusion)/(cmds[j].speed/60));
}
printTime += printTimeAdd;
if(typeof(printTimeByLayer[cmds[j].prevZ])==='undefined'){printTimeByLayer[cmds[j].prevZ]=0;}
printTimeByLayer[cmds[j].prevZ] += printTimeAdd;
if(cmds[j].extrude&&cmds[j].retract===0){
type = 'extrude';
}else if(cmds[j].retract!==0){
type = 'retract';
}else if(!cmds[j].extrude&&cmds[j].retract===0){
type = 'move';
// if(cmds[j].prevZ == '17.1'){
// self.postMessage({cmd: 'Got speed ' + cmds[j].speed + 'with line ' + cmds[j].gcodeLine});
// }
}else {
self.postMessage({cmd: 'unknown type of move'});
type = 'unknown';
}
speedIndex = speeds[type].indexOf(cmds[j].speed);
if (speedIndex === -1) {
speeds[type].push(cmds[j].speed);
speedIndex = speeds[type].indexOf(cmds[j].speed);
}
if(typeof(speedsByLayer[type][cmds[j].prevZ]) === 'undefined'){
speedsByLayer[type][cmds[j].prevZ] = [];
}
if(speedsByLayer[type][cmds[j].prevZ].indexOf(cmds[j].speed) === -1){
speedsByLayer[type][cmds[j].prevZ][speedIndex] = cmds[j].speed;
}
}
sendSizeProgress(i/model.length*100);
}
purgeLayers();
modelSize.x = Math.abs(max.x - min.x);
modelSize.y = Math.abs(max.y - min.y);
modelSize.z = Math.abs(max.z - min.z);
layerHeight = (max.z-min.z)/(layerCnt-1);
sendAnalyzeDone();
};
var doParse = function(){
var argChar, numSlice;
model=[];
var sendLayer = undefined;
var sendLayerZ = 0;
var sendMultiLayer = [];
var sendMultiLayerZ = [];
var lastSend = 0;
// console.time("parseGCode timer");
var reg = new RegExp(/^(?:G0|G1)\s/i);
var comment = new RegExp()
var j, layer= 0, extrude=false, prevRetract= 0, retract=0, x, y, z=0, f, prevZ=0, prevX, prevY,lastF=4000, prev_extrude = {a: undefined, b: undefined, c: undefined, e: undefined, abs: undefined}, extrudeRelative=false;
var dcExtrude=false;
var assumeNonDC = false;
for(var i=0;i<gcode.length;i++){
x=undefined;
y=undefined;
z=undefined;
retract = 0;
var line = gcode[i].line;
var percentage = gcode[i].percentage;
extrude=false;
line = line.split(/[\(;]/)[0];
if(reg.test(line)){
var args = line.split(/\s/);
for(j=0;j<args.length;j++){
switch(argChar = args[j].charAt(0).toLowerCase()){
case 'x':
x=args[j].slice(1);
break;
case 'y':
y=args[j].slice(1);
break;
case 'z':
z=args[j].slice(1);
z = Number(z);
if(z == prevZ) continue;
if(z_heights.hasOwnProperty(z)){
layer = z_heights[z];
}else{
layer = model.length;
z_heights[z] = layer;
}
sendLayer = layer;
sendLayerZ = z;
prevZ = z;
break;
case 'e':
case 'a':
case 'b':
case 'c':
assumeNonDC = true;
numSlice = parseFloat(args[j].slice(1)).toFixed(3);
if(!extrudeRelative){
// absolute extrusion positioning
prev_extrude["abs"] = parseFloat(numSlice)-parseFloat(prev_extrude[argChar]);
}else{
prev_extrude["abs"] = parseFloat(numSlice);
}
extrude = prev_extrude["abs"]>0;
if(prev_extrude["abs"]<0){
prevRetract = -1;
retract = -1;
}
else if(prev_extrude["abs"]==0){
retract = 0;
}else if(prev_extrude["abs"]>0&&prevRetract < 0){
prevRetract = 0;
retract = 1;
} else {
retract = 0;
}
prev_extrude[argChar] = numSlice;
break;
case 'f':
numSlice = args[j].slice(1);
lastF = numSlice;
break;
default:
break;
}
}
if(dcExtrude&&!assumeNonDC){
extrude = true;
prev_extrude["abs"] = Math.sqrt((prevX-x)*(prevX-x)+(prevY-y)*(prevY-y));
}
if(!model[layer])model[layer]=[];
if(typeof(x) !== 'undefined' || typeof(y) !== 'undefined' ||typeof(z) !== 'undefined'||retract!=0) model[layer][model[layer].length] = {x: Number(x), y: Number(y), z: Number(z), extrude: extrude, retract: Number(retract), noMove: false, extrusion: (extrude||retract)?Number(prev_extrude["abs"]):0, prevX: Number(prevX), prevY: Number(prevY), prevZ: Number(prevZ), speed: Number(lastF), gcodeLine: Number(i), percentage: percentage};
//{x: x, y: y, z: z, extrude: extrude, retract: retract, noMove: false, extrusion: (extrude||retract)?prev_extrude["abs"]:0, prevX: prevX, prevY: prevY, prevZ: prevZ, speed: lastF, gcodeLine: i};
if(typeof(x) !== 'undefined') prevX = x;
if(typeof(y) !== 'undefined') prevY = y;
} else if(line.match(/^(?:M82)/i)){
extrudeRelative = false;
}else if(line.match(/^(?:G91)/i)){
extrudeRelative=true;
}else if(line.match(/^(?:G90)/i)){
extrudeRelative=false;
}else if(line.match(/^(?:M83)/i)){
extrudeRelative=true;
}else if(line.match(/^(?:M101)/i)){
dcExtrude=true;
}else if(line.match(/^(?:M103)/i)){
dcExtrude=false;
}else if(line.match(/^(?:G92)/i)){
var args = line.split(/\s/);
for(j=0;j<args.length;j++){
switch(argChar = args[j].charAt(0).toLowerCase()){
case 'x':
x=args[j].slice(1);
break;
case 'y':
y=args[j].slice(1);
break;
case 'z':
z=args[j].slice(1);
prevZ = z;
break;
case 'e'||'a'||'b'||'c':
numSlice = args[j].slice(1);
if(!extrudeRelative)
prev_extrude[argChar] = 0;
else {
prev_extrude[argChar] = numSlice;
}
break;
default:
break;
}
}
if(!model[layer])model[layer]=[];
if(typeof(x) !== 'undefined' || typeof(y) !== 'undefined' ||typeof(z) !== 'undefined') model[layer][model[layer].length] = {x: parseFloat(x), y: parseFloat(y), z: parseFloat(z), extrude: extrude, retract: parseFloat(retract), noMove: true, extrusion: (extrude||retract)?parseFloat(prev_extrude["abs"]):0, prevX: parseFloat(prevX), prevY: parseFloat(prevY), prevZ: parseFloat(prevZ), speed: parseFloat(lastF),gcodeLine: parseFloat(i), percentage: percentage};
}else if(line.match(/^(?:G28)/i)){
var args = line.split(/\s/);
for(j=0;j<args.length;j++){
switch(argChar = args[j].charAt(0).toLowerCase()){
case 'x':
x=args[j].slice(1);
break;
case 'y':
y=args[j].slice(1);
break;
case 'z':
z=args[j].slice(1);
z = Number(z);
if(z === prevZ)continue;
sendLayer = layer;
sendLayerZ = z;//}
if(z_heights.hasOwnProperty(z)){
layer = z_heights[z];
}else{
layer = model.length;
z_heights[z] = layer;
}
prevZ = z;
break;
default:
break;
}
}
// G28 with no arguments
if(args.length == 1){
//need to init values to default here
}
// if it's the first layer and G28 was without
if(layer==0&&typeof(z) === 'undefined'){
z=0;
if(z_heights.hasOwnProperty(z)){
layer = z_heights[z];
}else{
layer = model.length;
z_heights[z] = layer;
}
prevZ = z;
}
if(!model[layer])model[layer]=[];
if(typeof(x) !== 'undefined' || typeof(y) !== 'undefined' ||typeof(z) !== 'undefined'||retract!=0) model[layer][model[layer].length] = {x: Number(x), y: Number(y), z: Number(z), extrude: extrude, retract: Number(retract), noMove: false, extrusion: (extrude||retract)?Number(prev_extrude["abs"]):0, prevX: Number(prevX), prevY: Number(prevY), prevZ: Number(prevZ), speed: Number(lastF), gcodeLine: Number(i), percentage: percentage};
}
if(typeof(sendLayer) !== "undefined"){
if(i-lastSend > gcode.length*0.02 && sendMultiLayer.length != 0){
lastSend = i;
sendMultiLayerToParent(sendMultiLayer, sendMultiLayerZ, i/gcode.length*100);
sendMultiLayer = [];
sendMultiLayerZ = [];
}
sendMultiLayer[sendMultiLayer.length] = sendLayer;
sendMultiLayerZ[sendMultiLayerZ.length] = sendLayerZ;
sendLayer = undefined;
sendLayerZ = undefined;
}
}
sendMultiLayerToParent(sendMultiLayer, sendMultiLayerZ, i/gcode.length*100);
};
var parseGCode = function(message){
gcode = message.gcode;
firstReport = message.options.firstReport;
doParse();
gcode = [];
self.postMessage({
"cmd": "returnModel",
"msg": {
// model: model
}
});
};
var runAnalyze = function(message){
analyzeModel();
model = [];
z_heights = [];
gcode = undefined;
firstReport = undefined;
z_heights = {};
model = [];
max = {x: undefined, y: undefined, z: undefined};
min = {x: undefined, y: undefined, z: undefined};
modelSize = {x: undefined, y: undefined, z: undefined};
filamentByLayer = {};
totalFilament=0;
printTime=0;
printTimeByLayer = {};
layerHeight=0;
layerCnt = 0;
speeds = {extrude: [], retract: [], move: []};
speedsByLayer = {extrude: {}, retract: {}, move: {}};
};
var setOption = function(options){
for(var opt in options){
gCodeOptions[opt] = options[opt];
}
};
onmessage = function (e){
var data = e.data;
// for some reason firefox doesn't garbage collect when something inside closures is deleted, so we delete and recreate whole object eaech time
switch (data.cmd) {
case 'parseGCode':
parseGCode(data.msg);
break;
case 'setOption':
setOption(data.msg);
break;
case 'analyzeModel':
runAnalyze(data.msg);
break;
default:
self.postMessage('Unknown command: ' + data.msg);
}
};