From cd244f341d5ceb45e7c3099439ef8328aac38b5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Thu, 16 Apr 2015 15:20:18 +0200 Subject: [PATCH] Small fixes prior to merge of #852 * Proper origin visualization in gcode viewer for centered rectangular * Validation of origin profile entry * Auto-migration of existing profiles upon load * Added changelog entry --- CHANGELOG.md | 3 + src/octoprint/printer/profile.py | 32 +++++++++-- .../static/gcodeviewer/js/renderer.js | 55 +++++++++++++------ .../js/app/viewmodels/printerprofiles.js | 31 +++++++++-- 4 files changed, 97 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c6016f9..153032cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -118,6 +118,9 @@ * Renamed "Temperature Timeout" and "SD Status Timeout" in Settings to "Temperature Interval" and "SD Status Interval" to better reflect what those values are actually used for. * Better behaviour of the settings dialog on mobile devices. +* Added support for rectangular printer beds with the origin in the center ([#682](https://github.com/foosel/OctoPrint/issues/682) + and [#852](https://github.com/foosel/OctoPrint/pull/852)). Printer profiles now contain a new settings ``volume.origin`` + which can either be ``lowerleft`` or ``center``. For circular beds only ``center`` is supported. ### Bug Fixes diff --git a/src/octoprint/printer/profile.py b/src/octoprint/printer/profile.py index 0b3f6834..5487a661 100644 --- a/src/octoprint/printer/profile.py +++ b/src/octoprint/printer/profile.py @@ -80,10 +80,9 @@ class PrinterProfileManager(object): * - ``volume.formFactor`` - ``string`` - Form factor of the print bed, either ``rectangular`` or ``circular`` - - ``volume.origin`` - - ``string`` - - Location of gcode origin in the print volume, either ``lowerleft`` - - or ``center`` + * - ``volume.origin`` + - ``string`` + - Location of gcode origin in the print volume, either ``lowerleft`` or ``center`` * - ``heatedBed`` - ``bool`` - Whether the printer has a heated bed (``True``) or not (``False``) @@ -304,7 +303,15 @@ class PrinterProfileManager(object): import yaml with open(path) as f: profile = yaml.safe_load(f) + + if self._migrate_profile(profile): + try: + self._save_to_path(path, profile, allow_overwrite=True) + except: + self._logger.exception("Tried to save profile to {path} after migrating it while loading, ran into exception".format(path=path)) + profile = self._ensure_valid_profile(profile) + if not profile: self._logger.warn("Invalid profile: %s" % path) raise InvalidProfileError() @@ -356,6 +363,14 @@ class PrinterProfileManager(object): sanitized_name = sanitized_name.replace(" ", "_") return sanitized_name + def _migrate_profile(self, profile): + # make sure profile format is up to date + if "volume" in profile and "formFactor" in profile["volume"] and not "origin" in profile["volume"]: + profile["volume"]["origin"] = BedOrigin.CENTER if profile["volume"]["formFactor"] == BedTypes.CIRCULAR else BedOrigin.LOWERLEFT + return True + + return False + def _ensure_valid_profile(self, profile): # ensure all keys are present if not dict_contains_keys(self.default, profile): @@ -399,6 +414,15 @@ class PrinterProfileManager(object): if not profile["volume"]["formFactor"] in BedTypes.values(): return False + # validate origin type + if not profile["volume"]["origin"] in BedOrigin.values(): + return False + + # ensure origin and form factor combination is legal + if profile["volume"]["formFactor"] == BedTypes.CIRCULAR and not profile["volume"]["origin"] == BedOrigin.CENTER: + # we do not support circular beds with anything other than a centered origin + return False + # validate offsets offsets = [] for offset in profile["extruder"]["offsets"]: diff --git a/src/octoprint/static/gcodeviewer/js/renderer.js b/src/octoprint/static/gcodeviewer/js/renderer.js index 15b44eca..9afc32ea 100644 --- a/src/octoprint/static/gcodeviewer/js/renderer.js +++ b/src/octoprint/static/gcodeviewer/js/renderer.js @@ -258,43 +258,66 @@ GCODE.renderer = (function(){ }; var drawRectangularGrid = function() { - var i; - var width = renderOptions["bed"]["x"] * zoomFactor; - var height = renderOptions["bed"]["y"] * zoomFactor; - var origin = { - x: 0, - y: -1 * renderOptions["bed"]["y"] * zoomFactor - }; + var x, y; + var width = renderOptions["bed"]["x"]; + var height = renderOptions["bed"]["y"]; + var minX, maxX, minY, maxY; if (renderOptions["bed"]["centeredOrigin"]) { - origin.x -= width / 2; - origin.y += height / 2; + var halfWidth = width / 2; + var halfHeight = height / 2; + + minX = -halfWidth; + maxX = halfWidth; + minY = -halfHeight; + maxY = halfHeight; + } else { + minX = 0; + maxX = width; + minY = 0; + maxY = height; } + //~ bed outline and origin ctx.beginPath(); ctx.strokeStyle = renderOptions["colorGrid"]; ctx.fillStyle = "#ffffff"; ctx.lineWidth = 2; - ctx.rect(origin.x, origin.y, width, height); + // outline + ctx.rect(minX * zoomFactor, -1 * minY * zoomFactor, width * zoomFactor, -1 * height * zoomFactor); + // origin + ctx.moveTo(minX * zoomFactor, 0); + ctx.lineTo(maxX * zoomFactor, 0); + ctx.moveTo(0, -1 * minY * zoomFactor); + ctx.lineTo(0, -1 * maxY * zoomFactor); + + // draw ctx.fill(); ctx.stroke(); ctx.strokeStyle = renderOptions["colorGrid"]; ctx.lineWidth = 1; + //~~ grid starting from origin ctx.beginPath(); - for (i = 0; i <= renderOptions["bed"]["x"]; i += gridStep) { - ctx.moveTo(origin.x + i * zoomFactor, origin.y); - ctx.lineTo(origin.x + i * zoomFactor, origin.y + height); + for (x = 0; x <= maxX; x += gridStep) { + ctx.moveTo(x * zoomFactor, -1 * minY * zoomFactor); + ctx.lineTo(x * zoomFactor, -1 * maxY * zoomFactor); + + ctx.moveTo(-1 * x * zoomFactor, -1 * minY * zoomFactor); + ctx.lineTo(-1 * x * zoomFactor, -1 * maxY * zoomFactor); } ctx.stroke(); ctx.beginPath(); - for (i = 0; i <= renderOptions["bed"]["y"]; i += gridStep) { - ctx.moveTo(origin.x, origin.y + i * zoomFactor); - ctx.lineTo(origin.x + width, origin.y + i * zoomFactor); + for (y = 0; y <= maxY; y += gridStep) { + ctx.moveTo(minX * zoomFactor, -1 * y * zoomFactor); + ctx.lineTo(maxX * zoomFactor, -1 * y * zoomFactor); + + ctx.moveTo(minX * zoomFactor, y * zoomFactor); + ctx.lineTo(maxX * zoomFactor, y * zoomFactor); } ctx.stroke(); }; diff --git a/src/octoprint/static/js/app/viewmodels/printerprofiles.js b/src/octoprint/static/js/app/viewmodels/printerprofiles.js index 109c0791..beb214de 100644 --- a/src/octoprint/static/js/app/viewmodels/printerprofiles.js +++ b/src/octoprint/static/js/app/viewmodels/printerprofiles.js @@ -69,6 +69,12 @@ $(function() { self.editorVolumeFormFactor = ko.observable(); self.editorVolumeOrigin = ko.observable(); + self.editorVolumeFormFactor.subscribe(function(oldVal, newVal) { + if (oldVal != newVal && newVal == "circular") { + self.editorVolumeOrigin("center"); + } + }); + self.editorHeatedBed = ko.observable(); self.editorNozzleDiameter = ko.observable(); @@ -95,10 +101,27 @@ $(function() { {key: "black", name: gettext("black")} ]); - self.availableOrigins = ko.observable([ - {key: "lowerleft", name: gettext("Lower Left")}, - {key: "center", name: gettext("Centered")} - ]); + self.availableOrigins = ko.computed(function() { + var formFactor = self.editorVolumeFormFactor(); + + var possibleOrigins = { + "lowerleft": gettext("Lower Left"), + "center": gettext("Center") + }; + + var keys = []; + if (formFactor == "rectangular") { + keys = ["lowerleft", "center"]; + } else if (formFactor == "circular") { + keys = ["center"]; + } + + var result = []; + _.each(keys, function(key) { + result.push({key: key, name: possibleOrigins[key]}); + }); + return result; + }); self.koEditorExtruderOffsets = ko.computed(function() { var extruderOffsets = self.editorExtruderOffsets();