From 030ffe6dcee84768e39b168a25b365196340c41d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Wed, 14 May 2014 22:48:56 +0200 Subject: [PATCH] GCODE viewer now interprets inverted axes for printer control and mirrors print bed accordingly. Should enable people to set their axes origin so that the viewer matches what they see on their printer. Should close #431 --- .../static/gcodeviewer/js/renderer.js | 103 ++++++++++++------ src/octoprint/static/gcodeviewer/js/ui.js | 8 +- .../static/js/app/viewmodels/gcode.js | 35 ++++-- .../static/js/app/viewmodels/settings.js | 4 +- 4 files changed, 105 insertions(+), 45 deletions(-) diff --git a/src/octoprint/static/gcodeviewer/js/renderer.js b/src/octoprint/static/gcodeviewer/js/renderer.js index af78b030..0a0edfb9 100644 --- a/src/octoprint/static/gcodeviewer/js/renderer.js +++ b/src/octoprint/static/gcodeviewer/js/renderer.js @@ -44,6 +44,7 @@ GCODE.renderer = (function(){ zoomInOnModel: false, zoomInOnBed: false, centerViewport: false, + invertAxes: {x: false, y: false}, bed: {x: 200, y: 200}, container: undefined @@ -54,6 +55,7 @@ GCODE.renderer = (function(){ var scaleX = 1, scaleY = 1; var speeds = []; var speedsByLayer = {}; + var currentInvertX = false, currentInvertY = false; var reRender = function(){ var p1 = ctx.transformedPoint(0,0); @@ -280,8 +282,11 @@ GCODE.renderer = (function(){ var i; - isNotCurrentLayer = typeof isNotCurrentLayer !== 'undefined' ? isNotCurrentLayer : false; + //~~ store current layer values + + isNotCurrentLayer = isNotCurrentLayer !== undefined ? isNotCurrentLayer : false; if (!isNotCurrentLayer) { + // not not current layer == current layer => store layer number and from/to progress layerNumStore = layerNum; progressStore = {from: fromProgress, to: toProgress}; } @@ -291,67 +296,74 @@ GCODE.renderer = (function(){ var cmds = model[layerNum]; var x, y; - if (fromProgress > 0) { - prevX = cmds[fromProgress-1].x * zoomFactor; - prevY = -cmds[fromProgress-1].y * zoomFactor; - } else if (fromProgress === 0 && layerNum == 0) { - if (model[0] && model[0].x !== undefined && model[0].y !== undefined) { - prevX = model[0].x * zoomFactor; - prevY = -model[0].y * zoomFactor; - } else { - prevX = 0; - prevY = 0; - } - } else if(typeof(cmds[0].prevX) !== 'undefined' && typeof(cmds[0].prevY) !== 'undefined') { + //~~ find our initial prevX/prevY tuple + + if (cmds[0].prevX !== undefined && cmds[0].prevY !== undefined) { + // command contains prevX/prevY values, use those prevX = cmds[0].prevX * zoomFactor; - prevY = -cmds[0].prevY * zoomFactor; - } else { - if (model[layerNum-1]) { - prevX = undefined; - prevY = undefined; - for (i = model[layerNum-1].length-1; i >= 0; i--) { - if (prevX === undefined && model[layerNum-1][i].x !== undefined) prevX = model[layerNum-1][i].x * zoomFactor; - if (prevY === undefined && model[layerNum-1][i].y !== undefined) prevY =- model[layerNum-1][i].y * zoomFactor; - } - if (prevX === undefined) prevX=0; - if (prevY === undefined) prevY=0; - } else { - prevX = 0; - prevY = 0; + prevY = -1 * cmds[0].prevY * zoomFactor; + } else if (fromProgress > 0) { + // previous command in same layer exists, use x/y as prevX/prevY + prevX = cmds[fromProgress - 1].x * zoomFactor; + prevY = -cmds[fromProgress - 1].y * zoomFactor; + } else if (model[layerNum - 1]) { + // previous layer exists, use last x/y as prevX/prevY + prevX = undefined; + prevY = undefined; + for (i = model[layerNum-1].length-1; i >= 0; i--) { + if (prevX === undefined && model[layerNum - 1][i].x !== undefined) prevX = model[layerNum - 1][i].x * zoomFactor; + if (prevY === undefined && model[layerNum - 1][i].y !== undefined) prevY =- model[layerNum - 1][i].y * zoomFactor; } } + // if we did not find prevX or prevY, set it to 0 (might be that we are on the first command of the first layer, + // or it's just a very weird model...) + if (prevX === undefined) prevX = 0; + if (prevY === undefined) prevY = 0; + + //~~ render this layer's commands + for (i = fromProgress; i <= toProgress; i++) { ctx.lineWidth = 1; if (typeof(cmds[i]) === 'undefined') continue; if (typeof(cmds[i].prevX) !== 'undefined' && typeof(cmds[i].prevY) !== 'undefined') { + // override new (prevX, prevY) prevX = cmds[i].prevX * zoomFactor; - prevY = -cmds[i].prevY * zoomFactor; + prevY = -1 * cmds[i].prevY * zoomFactor; } + // new x if (typeof(cmds[i].x) === 'undefined' || isNaN(cmds[i].x)) { x = prevX / zoomFactor; } else { x = cmds[i].x; } + + // new y if (typeof(cmds[i].y) === 'undefined' || isNaN(cmds[i].y)) { y = prevY / zoomFactor; } else { y = -cmds[i].y; } + // current tool var tool = cmds[i].tool; if (tool === undefined) tool = 0; + // line color based on tool var lineColor = renderOptions["colorLine"][tool]; if (lineColor === undefined) lineColor = renderOptions["colorLine"][0]; + // alpha value (100% if current layer is being rendered, 30% otherwise) var alpha = (renderOptions['showNextLayer'] || renderOptions['showPreviousLayer']) && isNotCurrentLayer ? 0.3 : 1.0; var shade = tool * 0.15; + if (!cmds[i].extrude && !cmds[i].noMove) { + // neither extrusion nor move if (cmds[i].retract == -1) { + // retract => draw dot if configured to do so if (renderOptions["showRetracts"]) { ctx.strokeStyle = pusher.color(renderOptions["colorRetract"]).shade(shade).alpha(alpha).html(); ctx.fillStyle = pusher.color(renderOptions["colorRetract"]).shade(shade).alpha(alpha).html(); @@ -361,7 +373,9 @@ GCODE.renderer = (function(){ ctx.fill(); } } + if(renderOptions["showMoves"]){ + // move => draw line from (prevX, prevY) to (x, y) in move color ctx.strokeStyle = pusher.color(renderOptions["colorMove"]).shade(shade).alpha(alpha).html(); ctx.beginPath(); ctx.moveTo(prevX, prevY); @@ -370,6 +384,7 @@ GCODE.renderer = (function(){ } } else if(cmds[i].extrude) { if (cmds[i].retract == 0) { + // no retraction => real extrusion move, use tool color to draw line ctx.strokeStyle = pusher.color(renderOptions["colorLine"][tool]).shade(shade).alpha(alpha).html(); ctx.lineWidth = renderOptions['extrusionWidth']; ctx.beginPath(); @@ -377,6 +392,7 @@ GCODE.renderer = (function(){ ctx.lineTo(x*zoomFactor,y*zoomFactor); ctx.stroke(); } else { + // we were previously retracting, now we are restarting => draw dot if configured to do so if (renderOptions["showRetracts"]) { ctx.strokeStyle = pusher.color(renderOptions["colorRestart"]).shade(shade).alpha(alpha).html(); ctx.fillStyle = pusher.color(renderOptions["colorRestart"]).shade(shade).alpha(alpha).html(); @@ -387,6 +403,8 @@ GCODE.renderer = (function(){ } } } + + // set new (prevX, prevY) prevX = x * zoomFactor; prevY = y * zoomFactor; } @@ -443,8 +461,8 @@ GCODE.renderer = (function(){ var scaleF = mdlInfo.modelSize.x > mdlInfo.modelSize.y ? (canvas.width - 10) / mdlInfo.modelSize.x : (canvas.height - 10) / mdlInfo.modelSize.y; scaleF /= zoomFactor; if (transform.a && transform.d) { - scaleX = scaleF / transform.a; - scaleY = scaleF / transform.d; + scaleX = scaleF / transform.a * (renderOptions["invertAxes"]["x"] ? -1 : 1); + scaleY = scaleF / transform.d * (renderOptions["invertAxes"]["y"] ? -1 : 1); ctx.translate(pt.x,pt.y); ctx.scale(scaleX, scaleY); ctx.translate(-pt.x, -pt.y); @@ -455,13 +473,33 @@ GCODE.renderer = (function(){ } }; + var applyInversion = function() { + var width = canvas.width - 10; + var height = canvas.height - 10; + + if (currentInvertX || currentInvertY) { + ctx.scale(currentInvertX ? -1 : 1, currentInvertY ? -1 : 1); + ctx.translate(currentInvertX ? -width : 0, currentInvertY ? height : 0); + } + + var invertX = renderOptions["invertAxes"]["x"]; + var invertY = renderOptions["invertAxes"]["y"]; + if (invertX || invertY) { + ctx.translate(invertX ? width : 0, invertY ? -height : 0); + ctx.scale(invertX ? -1 : 1, invertY ? -1 : 1); + } + + currentInvertX = invertX; + currentInvertY = invertY; + }; + // ***** PUBLIC ******* return { init: function(){ startCanvas(); initialized = true; var bedWidth = renderOptions["bed"]["x"]; - var bedHeight = renderOptions["bed"]["y"];; + var bedHeight = renderOptions["bed"]["y"]; if(renderOptions["bed"]["circular"]) { bedWidth = bedHeight = renderOptions["bed"]["r"] * 2; } @@ -492,7 +530,7 @@ GCODE.renderer = (function(){ dirty = true; renderOptions[opt] = options[opt]; - if ($.inArray(opt, ["moveModel", "centerViewport", "zoomInOnModel", "bed"])) { + if ($.inArray(opt, ["moveModel", "centerViewport", "zoomInOnModel", "bed", "invertAxes"])) { mustRefresh = true; } } @@ -573,6 +611,7 @@ GCODE.renderer = (function(){ } } + applyInversion(); applyOffsets(mdlInfo); applyZoom(mdlInfo); diff --git a/src/octoprint/static/gcodeviewer/js/ui.js b/src/octoprint/static/gcodeviewer/js/ui.js index 8f5a42fb..47d539fd 100644 --- a/src/octoprint/static/gcodeviewer/js/ui.js +++ b/src/octoprint/static/gcodeviewer/js/ui.js @@ -180,8 +180,12 @@ GCODE.ui = (function(){ updateOptions: function(options) { setOptions(options.ui); - GCODE.gCodeReader.setOption(options.reader); - GCODE.renderer.setOption(options.renderer); + if (options.reader) { + GCODE.gCodeReader.setOption(options.reader); + } + if (options.renderer) { + GCODE.renderer.setOption(options.renderer); + } }, changeSelectedLayer: function(newLayerNum) { diff --git a/src/octoprint/static/js/app/viewmodels/gcode.js b/src/octoprint/static/js/app/viewmodels/gcode.js index d29efe66..9219a1ef 100644 --- a/src/octoprint/static/js/app/viewmodels/gcode.js +++ b/src/octoprint/static/js/app/viewmodels/gcode.js @@ -96,21 +96,38 @@ function GcodeViewModel(loginStateViewModel, settingsViewModel) { // subscribe to relevant printer settings... self.settings.printer_extruderOffsets.subscribe(function() { if (!self.enabled) return; + if (!self.settings.printer_extruderOffsets()) return; - var options = { + GCODE.ui.updateOptions({ reader: { toolOffsets: self.settings.printer_extruderOffsets() } - }; + }); + }); + self.settings.printer_bedDimensions.subscribe(function() { + if (!self.enabled) return; var bedDimensions = self.settings.printer_bedDimensions(); - if (bedDimensions && bedDimensions.hasOwnProperty("x") && bedDimensions.hasOwnProperty("y")) { - options["renderer"] = { - bed: self.settings.printer_bedDimensions() - } - } + if (!bedDimensions || (!bedDimensions.hasOwnProperty("x") && !bedDimensions.hasOwnProperty("y") && !bedDimensions.hasOwnProperty("r"))) return; - GCODE.ui.updateOptions(options); + GCODE.ui.updateOptions({ + renderer: { + bed: bedDimensions + } + }); + }); + self.settings.printer_invertAxes.subscribe(function() { + if (!self.enabled) return; + if (!self.settings.printer_invertAxes()) return; + + GCODE.ui.updateOptions({ + renderer: { + invertAxes: { + x: self.settings.printer_invertX(), + y: self.settings.printer_invertY() + } + } + }); }); self.loadedFilename = undefined; @@ -288,7 +305,7 @@ function GcodeViewModel(loginStateViewModel, settingsViewModel) { self.approveLargeFile = function() { self.waitForApproval(false); self.loadFile(self.selectedFile.name(), self.selectedFile.date()); - } + }; self._onProgress = function(type, percentage) { self.ui_progress_type(type); diff --git a/src/octoprint/static/js/app/viewmodels/settings.js b/src/octoprint/static/js/app/viewmodels/settings.js index 22442e93..77828a08 100644 --- a/src/octoprint/static/js/app/viewmodels/settings.js +++ b/src/octoprint/static/js/app/viewmodels/settings.js @@ -183,7 +183,7 @@ function SettingsViewModel(loginStateViewModel, usersViewModel) { if (callback) callback(); } }); - } + }; self.fromResponse = function(response) { self.api_enabled(response.api.enabled); @@ -244,7 +244,7 @@ function SettingsViewModel(loginStateViewModel, usersViewModel) { self.system_actions(response.system.actions); self.terminalFilters(response.terminalFilters); - } + }; self.saveData = function() { var data = {