From 7028bbfbfd2b3502b87448d1bbb6af6517dd1eb6 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 (cherry picked from commit 030ffe6) --- .../static/gcodeviewer/js/renderer.js | 109 ++++++++++++------ 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, 110 insertions(+), 46 deletions(-) diff --git a/src/octoprint/static/gcodeviewer/js/renderer.js b/src/octoprint/static/gcodeviewer/js/renderer.js index 3c7d2ee7..a1202006 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, @@ -56,6 +57,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); @@ -290,8 +292,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}; } @@ -301,67 +306,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(); @@ -371,7 +383,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); @@ -380,6 +394,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(); @@ -387,6 +402,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(); @@ -397,6 +413,8 @@ GCODE.renderer = (function(){ } } } + + // set new (prevX, prevY) prevX = x * zoomFactor; prevY = y * zoomFactor; } @@ -451,8 +469,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); @@ -464,13 +482,37 @@ 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; - zoomFactor = Math.min((canvas.width - 10) / renderOptions["bed"]["x"], (canvas.height - 10) / renderOptions["bed"]["y"]); - ctx.translate((canvas.width - renderOptions["bed"]["x"] * zoomFactor) / 2, renderOptions["bed"]["y"] * zoomFactor + (canvas.height - renderOptions["bed"]["y"] * zoomFactor) / 2); + var bedWidth = renderOptions["bed"]["x"]; + var bedHeight = renderOptions["bed"]["y"]; + if(renderOptions["bed"]["circular"]) { + bedWidth = bedHeight = renderOptions["bed"]["r"] * 2; + } + zoomFactor = Math.min((canvas.width - 10) / bedWidth, (canvas.height - 10) / bedHeight); offsetModelX = 0; offsetModelY = 0; @@ -487,7 +529,7 @@ GCODE.renderer = (function(){ dirty = true; renderOptions[opt] = options[opt]; - if ($.inArray(opt, ["moveModel", "centerViewport", "zoomInOnModel", "bed"]) > -1) { + if ($.inArray(opt, ["moveModel", "centerViewport", "zoomInOnModel", "bed", "invertAxes"]) > -1) { mustRefresh = true; } } @@ -568,6 +610,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 5b5f9b33..2d3dc276 100644 --- a/src/octoprint/static/js/app/viewmodels/gcode.js +++ b/src/octoprint/static/js/app/viewmodels/gcode.js @@ -97,21 +97,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; @@ -289,7 +306,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 1647d2b8..177d7b53 100644 --- a/src/octoprint/static/js/app/viewmodels/settings.js +++ b/src/octoprint/static/js/app/viewmodels/settings.js @@ -177,7 +177,7 @@ function SettingsViewModel(loginStateViewModel, usersViewModel) { if (callback) callback(); } }); - } + }; self.fromResponse = function(response) { self.api_enabled(response.api.enabled); @@ -238,7 +238,7 @@ function SettingsViewModel(loginStateViewModel, usersViewModel) { self.system_actions(response.system.actions); self.terminalFilters(response.terminalFilters); - } + }; self.saveData = function() { var data = {