From cbd94a902085af2f943194b40325c8acdbb7e762 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Tue, 13 Jun 2017 15:59:30 +0200 Subject: [PATCH] Ignore coordinates outside bed for zoom/centering in gcode viewer They might be nozzle priming routines which are not actually part of the model proper and hence it doesn't make sense trying to keep them visible when zooming in on the model, or allowing them to offset the model center. --- src/octoprint/static/gcodeviewer/js/Worker.js | 127 +++++++++++++----- .../static/gcodeviewer/js/gCodeReader.js | 21 +++ .../static/gcodeviewer/js/renderer.js | 87 +++++++++--- src/octoprint/static/gcodeviewer/js/ui.js | 3 +- .../static/js/app/viewmodels/gcode.js | 15 ++- 5 files changed, 196 insertions(+), 57 deletions(-) diff --git a/src/octoprint/static/gcodeviewer/js/Worker.js b/src/octoprint/static/gcodeviewer/js/Worker.js index ea14a5c3..f864e889 100644 --- a/src/octoprint/static/gcodeviewer/js/Worker.js +++ b/src/octoprint/static/gcodeviewer/js/Worker.js @@ -14,7 +14,10 @@ var z_heights = {}; var model = []; var max = {x: undefined, y: undefined, z: undefined}; var min = {x: undefined, y: undefined, z: undefined}; +var boundingBox = {minX: undefined, maxX: undefined, minY: undefined, maxY: undefined, minZ: undefined, maxZ: undefined}; var modelSize = {x: undefined, y: undefined, z: undefined}; +var bed = {x: undefined, y: undefined, r: undefined, circular: false, centeredOrigin: false}; +var ignoreOutsideBed = false; var filamentByLayer = {}; var totalFilament = [0]; var printTime = 0; @@ -76,6 +79,7 @@ var sendAnalyzeDone = function () { max: max, min: min, modelSize: modelSize, + boundingBox: boundingBox, totalFilament: totalFilament, filamentByLayer: filamentByLayer, printTime: printTime, @@ -106,6 +110,36 @@ var purgeLayers = function () { } }; +var bedBounds = function() { + if (!bed) { + return {xMin: 0, xMax: 0, yMin: 0, yMax: 0}; + } + + var result; + if (bed.circular) { + result = {xMin: -bed.r, xMax: bed.r, yMin: -bed.r, yMax: bed.r}; + } else if (bed.centeredOrigin) { + result = {xMin: -bed.x/2.0, xMax: bed.x/2.0, yMin: -bed.y/2.0, yMax: bed.y/2.0}; + } else { + result = {xMin: 0, xMax: bed.x, yMin: 0, yMax: bed.y}; + } + return result; +}; + +var withinBedBounds = function(x, y, bedBounds) { + if (!ignoreOutsideBed) return true; + + var result = true; + + if (x !== undefined && !isNaN(x)) { + result = result && (x >= bedBounds.xMin && x <= bedBounds.xMax); + } + if (y !== undefined && !isNaN(y)) { + result = result && (y >= bedBounds.yMin && y <= bedBounds.yMax); + } + + return result; +}; var analyzeModel = function () { var tmp1 = 0, tmp2 = 0; @@ -113,6 +147,8 @@ var analyzeModel = function () { var type; var printTimeAdd = 0; + var bounds = bedBounds(); + for (var i = 0; i < model.length; i++) { var cmds = model[i]; if (!cmds) continue; @@ -120,41 +156,56 @@ var analyzeModel = function () { for (var j = 0; j < cmds.length; j++) { var tool = cmds[j].tool; - var x_ok = false; - var y_ok = false; + var retract = cmds[j].retract && cmds[j].retract > 0; + var extrude = cmds[j].extrude && cmds[j].extrude > 0 && !retract; + var move = cmds[j].x !== undefined || cmds[j].y !== undefined || cmds[j].z !== undefined; + + var prevInBounds = withinBedBounds(cmds[j].prevX, cmds[j].prevY, bounds); + var inBounds = withinBedBounds(cmds[j].x === undefined ? cmds[j].prevX : cmds[j].x, + cmds[j].y === undefined ? cmds[j].prevY : cmds[j].y, + bounds); + + var recordX = function(x, inBounds) { + if (x === undefined || isNaN(x)) return; - if (typeof(cmds[j].x) !== 'undefined' - && typeof(cmds[j].prevX) !== 'undefined' - && typeof(cmds[j].extrude) !== 'undefined' - && cmds[j].extrude - && !isNaN(cmds[j].x)) { - var x = cmds[j].x; max.x = max.x !== undefined ? Math.max(max.x, x) : x; min.x = min.x !== undefined ? Math.min(min.x, x) : x; - x_ok = true; - } + if (inBounds) { + boundingBox.minX = boundingBox.minX !== undefined ? Math.min(boundingBox.minX, x): x; + boundingBox.maxX = boundingBox.maxX !== undefined ? Math.max(boundingBox.maxX, x): x; + } + }; - if (typeof(cmds[j].y) !== 'undefined' - && typeof(cmds[j].prevY) !== 'undefined' - && typeof(cmds[j].extrude) !== 'undefined' - && cmds[j].extrude - && !isNaN(cmds[j].y)) { - var y = cmds[j].y; + var recordY = function(y, inBounds) { + if (y === undefined || isNaN(y)) return; max.y = max.y !== undefined ? Math.max(max.y, y) : y; min.y = min.y !== undefined ? Math.min(min.y, y) : y; - y_ok = true; - } + if (inBounds) { + boundingBox.minY = boundingBox.minY !== undefined ? Math.min(boundingBox.minY, y): y; + boundingBox.maxY = boundingBox.maxY !== undefined ? Math.max(boundingBox.maxY, y): y; + } + }; + + var recordZ = function(z) { + if (z === undefined || isNaN(z)) return; - if (typeof(cmds[j].prevZ) !== 'undefined' - && typeof(cmds[j].extrude) !== 'undefined' - && cmds[j].extrude - && !isNaN(cmds[j].prevZ)) { - var z = cmds[j].prevZ; max.z = max.z !== undefined ? Math.max(max.z, z) : z; min.z = min.z !== undefined ? Math.min(min.z, z) : z; + + boundingBox.minZ = min.z; + boundingBox.maxZ = max.z; + }; + + if (move && extrude) { + recordX(cmds[j].prevX, prevInBounds); + recordX(cmds[j].x, inBounds); + recordY(cmds[j].prevY, prevInBounds); + recordY(cmds[j].y, inBounds); + recordZ(cmds[j].prevZ); + recordZ(cmds[j].z); } if (!totalFilament[tool]) totalFilament[tool] = 0; @@ -165,20 +216,25 @@ var analyzeModel = function () { filamentByLayer[cmds[j].prevZ][tool] += cmds[j].extrusion; } - var diffX = cmds[j].x - cmds[j].prevX; - var diffY = cmds[j].y - cmds[j].prevY; - if (x_ok && y_ok) { - printTimeAdd = Math.sqrt(diffX * diffX + diffY * diffY) / (cmds[j].speed / 60); - } else if (cmds[j].retract === 0 && cmds[j].extrusion !== 0) { - tmp1 = Math.sqrt(diffX * diffX + diffY * diffY) / (cmds[j].speed / 60); - tmp2 = Math.abs(cmds[j].extrusion / (cmds[j].speed / 60)); - printTimeAdd = Math.max(tmp1, tmp2); - } else if (cmds[j].retract !== 0) { - printTimeAdd = Math.abs(cmds[j].extrusion / (cmds[j].speed / 60)); + if (cmds[j].x !== undefined && !isNaN(cmds[j].x) + && cmds[j].y !== undefined && !isNaN(cmds[j].y)) { + var diffX = cmds[j].x - cmds[j].prevX; + var diffY = cmds[j].y - cmds[j].prevY; + if (move) { + printTimeAdd = Math.sqrt(diffX * diffX + diffY * diffY) / (cmds[j].speed / 60); + } else if (extrude) { + tmp1 = Math.sqrt(diffX * diffX + diffY * diffY) / (cmds[j].speed / 60); + tmp2 = Math.abs(cmds[j].extrusion / (cmds[j].speed / 60)); + printTimeAdd = Math.max(tmp1, tmp2); + } else if (retract) { + printTimeAdd = Math.abs(cmds[j].extrusion / (cmds[j].speed / 60)); + } + } else { + printTimeAdd = 0; } printTime += printTimeAdd; - if (typeof(printTimeByLayer[cmds[j].prevZ]) === 'undefined') { + if (printTimeByLayer[cmds[j].prevZ] === undefined) { printTimeByLayer[cmds[j].prevZ] = 0; } printTimeByLayer[cmds[j].prevZ] += printTimeAdd; @@ -574,6 +630,8 @@ var parseGCode = function (message) { firstReport = message.options.firstReport; toolOffsets = message.options.toolOffsets; if (!toolOffsets || toolOffsets.length == 0) toolOffsets = [{x: 0, y: 0}]; + bed = message.options.bed; + ignoreOutsideBed = message.options.ignoreOutsideBed; g90InfluencesExtruder = message.options.g90InfluencesExtruder; doParse(); @@ -595,6 +653,7 @@ var runAnalyze = function (message) { max = {x: undefined, y: undefined, z: undefined}; min = {x: undefined, y: undefined, z: undefined}; modelSize = {x: undefined, y: undefined, z: undefined}; + boundingBox = {minX: undefined, maxX: undefined, minY: undefined, maxY: undefined, minZ: undefined, maxZ: undefined}; filamentByLayer = {}; totalFilament = 0; printTime = 0; diff --git a/src/octoprint/static/gcodeviewer/js/gCodeReader.js b/src/octoprint/static/gcodeviewer/js/gCodeReader.js index bf0124ad..5dc18981 100644 --- a/src/octoprint/static/gcodeviewer/js/gCodeReader.js +++ b/src/octoprint/static/gcodeviewer/js/gCodeReader.js @@ -12,6 +12,9 @@ GCODE.gCodeReader = (function(){ 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 boundingBox = {minX: undefined, maxX: undefined, + minY: undefined, maxY: undefined, + minZ: undefined, maxZ: undefined}; var filamentByLayer = {}; var printTimeByLayer; var totalFilament=0; @@ -25,6 +28,14 @@ GCODE.gCodeReader = (function(){ toolOffsets: [ {x: 0, y: 0} ], + bed: { + x: undefined, + y: undefined, + r: undefined, + circular: undefined, + centeredOrigin: undefined + }, + ignoreOutsideBed: false, g90InfluencesExtruder: false }; @@ -123,6 +134,12 @@ GCODE.gCodeReader = (function(){ clear: function() { model = []; z_heights = []; + max = {x: undefined, y: undefined, z: undefined}; + min = {x: undefined, y: undefined, z: undefined}; + modelSize = {x: undefined, y: undefined, z: undefined}; + boundingBox = {minX: undefined, maxX: undefined, + minY: undefined, maxY: undefined, + minZ: undefined, maxZ: undefined}; }, loadFile: function(reader){ @@ -140,6 +157,8 @@ GCODE.gCodeReader = (function(){ options: { firstReport: 5, toolOffsets: gCodeOptions["toolOffsets"], + bed: gCodeOptions["bed"], + ignoreOutsideBed: gCodeOptions["ignoreOutsideBed"], g90InfluencesExtruder: gCodeOptions["g90InfluencesExtruder"] } } @@ -182,6 +201,7 @@ GCODE.gCodeReader = (function(){ min = msg.min; max = msg.max; modelSize = msg.modelSize; + boundingBox = msg.boundingBox; totalFilament = msg.totalFilament; filamentByLayer = msg.filamentByLayer; speeds = msg.speeds; @@ -203,6 +223,7 @@ GCODE.gCodeReader = (function(){ min: min, max: max, modelSize: modelSize, + boundingBox: boundingBox, totalFilament: totalFilament, speeds: speeds, speedsByLayer: speedsByLayer, diff --git a/src/octoprint/static/gcodeviewer/js/renderer.js b/src/octoprint/static/gcodeviewer/js/renderer.js index 0fd6882d..9186d279 100644 --- a/src/octoprint/static/gcodeviewer/js/renderer.js +++ b/src/octoprint/static/gcodeviewer/js/renderer.js @@ -20,7 +20,8 @@ GCODE.renderer = (function(){ var lastX, lastY; var dragStart, dragged; var scaleFactor = 1.1; - var model; + var model = undefined; + var modelInfo = undefined; var initialized = false; var renderOptions = { colorGrid: "#bbbbbb", @@ -40,6 +41,8 @@ GCODE.renderer = (function(){ differentiateColors: true, showNextLayer: false, showPreviousLayer: false, + showBoundingBox: false, + showFullSize: false, moveModel: true, zoomInOnModel: false, @@ -65,6 +68,7 @@ GCODE.renderer = (function(){ var p2 = ctx.transformedPoint(canvas.width,canvas.height); ctx.clearRect(p1.x,p1.y,p2.x-p1.x,p2.y-p1.y); drawGrid(); + drawBoundingBox(); if(renderOptions['showNextLayer'] && layerNumStore < model.length - 1) { drawLayer(layerNumStore + 1, 0, GCODE.renderer.getLayerNumSegments(layerNumStore + 1), true); } @@ -378,6 +382,44 @@ GCODE.renderer = (function(){ ctx.stroke(); }; + var drawBoundingBox = function() { + if (!modelInfo) return; + + var minX, minY, width, height; + + if (renderOptions["showFullSize"]) { + minX = modelInfo.min.x * zoomFactor; + minY = modelInfo.min.y * zoomFactor; + width = modelInfo.modelSize.x * zoomFactor; + height = modelInfo.modelSize.y * zoomFactor; + + ctx.beginPath(); + ctx.strokeStyle = "#0000ff"; + ctx.setLineDash([2, 5]); + + ctx.rect(minX, minY * -1, width, height * -1); + + ctx.stroke(); + } + + if (renderOptions["showBoundingBox"]) { + minX = modelInfo.boundingBox.minX * zoomFactor; + minY = modelInfo.boundingBox.minY * zoomFactor; + width = modelInfo.boundingBox.maxX * zoomFactor - minX; + height = modelInfo.boundingBox.maxY * zoomFactor - minY; + + ctx.beginPath(); + ctx.strokeStyle = "#ff0000"; + ctx.setLineDash([2, 5]); + + ctx.rect(minX, minY * -1, width, height * -1); + + ctx.stroke(); + } + + ctx.setLineDash([1, 0]); + }; + var drawLayer = function(layerNum, fromProgress, toProgress, isNotCurrentLayer){ log.trace("Drawing layer " + layerNum + " from " + fromProgress + " to " + toProgress + " (current: " + !isNotCurrentLayer + ")"); @@ -524,27 +566,27 @@ GCODE.renderer = (function(){ ctx.stroke(); }; - var applyOffsets = function(mdlInfo) { + var applyOffsets = function() { var canvasCenter; // determine bed and model offsets if (ctx) ctx.translate(-offsetModelX, -offsetModelY); if (renderOptions["centerViewport"] || renderOptions["zoomInOnModel"]) { canvasCenter = ctx.transformedPoint(canvas.width / 2, canvas.height / 2); - if (mdlInfo) { - offsetModelX = canvasCenter.x - (mdlInfo.min.x + mdlInfo.modelSize.x / 2) * zoomFactor; - offsetModelY = canvasCenter.y + (mdlInfo.min.y + mdlInfo.modelSize.y / 2) * zoomFactor; + if (modelInfo) { + offsetModelX = canvasCenter.x - (modelInfo.boundingBox.minX + modelInfo.boundingBox.maxX) * zoomFactor / 2; + offsetModelY = canvasCenter.y + (modelInfo.boundingBox.minY + modelInfo.boundingBox.maxY) * zoomFactor / 2; } else { offsetModelX = 0; offsetModelY = 0; } offsetBedX = 0; offsetBedY = 0; - } else if (mdlInfo && renderOptions["moveModel"]) { - offsetModelX = (renderOptions["bed"]["x"] / 2 - (mdlInfo.min.x + mdlInfo.modelSize.x / 2)) * zoomFactor; - offsetModelY = -1 * (renderOptions["bed"]["y"] / 2 - (mdlInfo.min.y + mdlInfo.modelSize.y / 2)) * zoomFactor; - offsetBedX = -1 * (renderOptions["bed"]["x"] / 2 - (mdlInfo.min.x + mdlInfo.modelSize.x / 2)) * zoomFactor; - offsetBedY = (renderOptions["bed"]["y"] / 2 - (mdlInfo.min.y + mdlInfo.modelSize.y / 2)) * zoomFactor; + } else if (modelInfo && renderOptions["moveModel"]) { + offsetModelX = (renderOptions["bed"]["x"] / 2 - (modelInfo.boundingBox.minX + modelInfo.boundingBox.maxX) / 2) * zoomFactor; + offsetModelY = -1 * (renderOptions["bed"]["y"] / 2 - (modelInfo.boundingBox.minY + modelInfo.boundingBox.maxY) / 2) * zoomFactor; + offsetBedX = -1 * (renderOptions["bed"]["x"] / 2 - (modelInfo.boundingBox.minX + modelInfo.boundingBox.maxX) / 2) * zoomFactor; + offsetBedY = (renderOptions["bed"]["y"] / 2 - (modelInfo.boundingBox.minY + modelInfo.boundingBox.maxY) / 2) * zoomFactor; } else if (renderOptions["bed"]["circular"] || renderOptions["bed"]["centeredOrigin"]) { canvasCenter = ctx.transformedPoint(canvas.width / 2, canvas.height / 2); offsetModelX = canvasCenter.x; @@ -560,7 +602,7 @@ GCODE.renderer = (function(){ if (ctx) ctx.translate(offsetModelX, offsetModelY); }; - var applyZoom = function(mdlInfo) { + var applyZoom = function() { // get middle of canvas var pt = ctx.transformedPoint(canvas.width/2,canvas.height/2); @@ -575,9 +617,12 @@ GCODE.renderer = (function(){ transform = ctx.getTransform(); } - if (mdlInfo && renderOptions["zoomInOnModel"]) { + if (modelInfo && renderOptions["zoomInOnModel"]) { // if we need to zoom in on model, scale factor is calculated by longer side of object in relation to that axis of canvas - var scaleF = mdlInfo.modelSize.x > mdlInfo.modelSize.y ? (canvas.width - 10) / mdlInfo.modelSize.x : (canvas.height - 10) / mdlInfo.modelSize.y; + var width = modelInfo.boundingBox.maxX - modelInfo.boundingBox.minX; + var length = modelInfo.boundingBox.maxY - modelInfo.boundingBox.minY; + + var scaleF = width > length ? (canvas.width - 10) / width : (canvas.height - 10) / length; scaleF /= zoomFactor; if (transform.a && transform.d) { scaleX = scaleF / transform.a * (renderOptions["invertAxes"]["x"] ? -1 : 1); @@ -682,6 +727,7 @@ GCODE.renderer = (function(){ var p2 = ctx.transformedPoint(canvas.width, canvas.height); ctx.clearRect(p1.x, p1.y, p2.x - p1.x, p2.y - p1.y); drawGrid(); + drawBoundingBox(); if (model && model.length) { if (layerNum < model.length) { if (renderOptions['showNextLayer'] && layerNum < model.length - 1) { @@ -715,30 +761,31 @@ GCODE.renderer = (function(){ scaleY = 1; speeds = []; speedsByLayer = {}; + modelInfo = undefined; this.doRender([], 0); }, doRender: function(mdl, layerNum){ model = mdl; + modelInfo = undefined; - var mdlInfo = undefined; prevX = 0; prevY = 0; if (!initialized) this.init(); var toProgress = 1; - if (model) { - mdlInfo = GCODE.gCodeReader.getModelInfo(); - speeds = mdlInfo.speeds; - speedsByLayer = mdlInfo.speedsByLayer; + if (model && model.length) { + modelInfo = GCODE.gCodeReader.getModelInfo(); + speeds = modelInfo.speeds; + speedsByLayer = modelInfo.speedsByLayer; if (model[layerNum]) { toProgress = model[layerNum].length; } } applyInversion(); - applyOffsets(mdlInfo); - applyZoom(mdlInfo); + applyOffsets(); + applyZoom(); this.render(layerNum, 0, toProgress); }, diff --git a/src/octoprint/static/gcodeviewer/js/ui.js b/src/octoprint/static/gcodeviewer/js/ui.js index 31a4aeef..59ef8b03 100644 --- a/src/octoprint/static/gcodeviewer/js/ui.js +++ b/src/octoprint/static/gcodeviewer/js/ui.js @@ -161,7 +161,8 @@ GCODE.ui = (function(){ bed: options.bed }); GCODE.gCodeReader.setOption({ - toolOffsets: options.toolOffsets + toolOffsets: options.toolOffsets, + bed: options.bed }); GCODE.renderer.render(0, 0); diff --git a/src/octoprint/static/js/app/viewmodels/gcode.js b/src/octoprint/static/js/app/viewmodels/gcode.js index e00e73a5..f96ac908 100644 --- a/src/octoprint/static/js/app/viewmodels/gcode.js +++ b/src/octoprint/static/js/app/viewmodels/gcode.js @@ -46,6 +46,8 @@ $(function() { self.renderer_zoomOnModel = ko.observable(false); self.renderer_showMoves = ko.observable(true); self.renderer_showRetracts = ko.observable(true); + self.renderer_showBoundingBox = ko.observable(false); + self.renderer_showFullSize = ko.observable(false); self.renderer_extrusionWidthEnabled = ko.observable(false); self.renderer_extrusionWidth = ko.observable(2); self.renderer_showNext = ko.observable(false); @@ -65,6 +67,8 @@ $(function() { centerViewport: self.renderer_centerViewport(), showMoves: self.renderer_showMoves(), showRetracts: self.renderer_showRetracts(), + showBoundingBox: self.renderer_showBoundingBox(), + showFullSize: self.renderer_showFullSize(), extrusionWidth: self.renderer_extrusionWidthEnabled() ? self.renderer_extrusionWidth() : 1, showNextLayer: self.renderer_showNext(), showPreviousLayer: self.renderer_showPrevious(), @@ -77,7 +81,8 @@ $(function() { var reader = { sortLayers: self.reader_sortLayers(), - purgeEmptyLayers: self.reader_hideEmptyLayers() + purgeEmptyLayers: self.reader_hideEmptyLayers(), + ignoreOutsideBed: true }; if (additionalReaderOptions) { _.extend(reader, additionalReaderOptions); @@ -95,6 +100,8 @@ $(function() { self.renderer_zoomOnModel.subscribe(self.synchronizeOptions); self.renderer_showMoves.subscribe(self.synchronizeOptions); self.renderer_showRetracts.subscribe(self.synchronizeOptions); + self.renderer_showBoundingBox.subscribe(self.synchronizeOptions); + self.renderer_showFullSize.subscribe(self.synchronizeOptions); self.renderer_extrusionWidthEnabled.subscribe(self.synchronizeOptions); self.renderer_extrusionWidth.subscribe(self.synchronizeOptions); self.renderer_showNext.subscribe(self.synchronizeOptions); @@ -120,10 +127,13 @@ $(function() { } var bedDimensions = self._retrieveBedDimensions(currentProfileData); - if (toolOffsets) { + if (bedDimensions) { GCODE.ui.updateOptions({ renderer: { bed: bedDimensions + }, + reader: { + bed: bedDimensions } }); } @@ -336,6 +346,7 @@ $(function() { result: response } }; + GCODE.renderer.clear(); GCODE.gCodeReader.loadFile(par); if (self.layerSlider != undefined) {