From f07305ae4fe68293dfc8a8684f4911de6e87df22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Mon, 9 Sep 2013 16:51:46 +0200 Subject: [PATCH] Fixed a couple of merge issues, changed way Cura is called (just using "python" won't work in environments where the default python executable doesn't bring the necessary dependencies to run Cura, now using Cura startup script instead), introduced some messaging to notify UI what's going on. --- octoprint/gcodefiles.py | 34 ++++++------- octoprint/printer.py | 5 +- octoprint/server.py | 18 +++++-- octoprint/slicers/cura/__init__.py | 16 ++++-- octoprint/static/js/app/dataupdater.js | 49 +++++++++++-------- .../static/js/app/viewmodels/gcodefiles.js | 5 +- .../static/js/app/viewmodels/settings.js | 13 +++++ octoprint/templates/settings.jinja2 | 48 +++++++++--------- octoprint/util/__init__.py | 26 ++++++---- 9 files changed, 128 insertions(+), 86 deletions(-) diff --git a/octoprint/gcodefiles.py b/octoprint/gcodefiles.py index 7a9fe8eb..c8223e82 100644 --- a/octoprint/gcodefiles.py +++ b/octoprint/gcodefiles.py @@ -11,7 +11,9 @@ import time import logging import octoprint.util as util import octoprint.util.gcodeInterpreter as gcodeInterpreter + from octoprint.settings import settings +from octoprint.events import eventManager from werkzeug.utils import secure_filename @@ -141,16 +143,11 @@ class GcodeManager: return self.processGcode(absolutePath) curaEnabled = self._settings.get(["cura", "enabled"]) - - if isSTLFileName(filename) and curaEnabled and local: - gcodePath = util.genGcodeFileName(absolutePath) - - callBackArgs = [gcodePath] - callBack = self.processGcode - - self.processSTL(absolutePath, callBack, callBackArgs) + if curaEnabled and isSTLFileName(filename) and local: + self.processStl(absolutePath) return filename + def getFutureFileName(self, file): if not file: return None @@ -161,16 +158,22 @@ class GcodeManager: return self._getBasicFilename(absolutePath) - def processSTL(self, absolutePath, callBack, callBackArgs): + def processStl(self, absolutePath): from octoprint.slicers.cura import CuraFactory cura = CuraFactory.create_slicer() gcodePath = util.genGcodeFileName(absolutePath) config = self._settings.get(["cura", "config"]) - cura.process_file( - config, gcodePath, absolutePath, callBack, callBackArgs) + def stlProcessed(stlPath, gcodePath): + eventManager().fire("SlicingDone", {"stl": self._getBasicFilename(stlPath), "gcode": self._getBasicFilename(gcodePath)}) + self.processGcode(gcodePath) + + eventManager().fire("SlicingStarted", {"stl": self._getBasicFilename(absolutePath), "gcode": self._getBasicFilename(gcodePath)}) + cura.process_file(config, gcodePath, absolutePath, stlProcessed, [absolutePath, gcodePath]) + + def processGcode(self, absolutePath): if absolutePath is None: return None @@ -205,12 +208,9 @@ class GcodeManager: if absolutePath is None: return - # The files may or may not actually exits on the HD. - try: - os.remove(absolutePath) + os.remove(absolutePath) + if os.path.exists(stlPath): os.remove(stlPath) - except OSError: - pass if filename in self._metadata.keys(): del self._metadata[filename] @@ -223,7 +223,7 @@ class GcodeManager: Ensures that the file diff --git a/octoprint/printer.py b/octoprint/printer.py index a118fa73..352b7f4f 100644 --- a/octoprint/printer.py +++ b/octoprint/printer.py @@ -190,9 +190,6 @@ class Printer(): logging.info("Cannot load file: printer not connected or currently busy") return - if self._comm.isBusy() or self._comm.isStreaming(): - return - self._printAfterSelect = printAfterSelect self._comm.selectFile(filename, sd) self._setProgressData(0, None, None, None) @@ -491,7 +488,7 @@ class Printer(): callBackArgs = [gcodeFileName, gcodePath] callBack = self.streamSdFile - self._gcodeManager.processSTL( + self._gcodeManager.processStl( absolutePath, callBack, callBackArgs) def streamSdFile(self, filename, path): diff --git a/octoprint/server.py b/octoprint/server.py index 356c086e..8eef377d 100644 --- a/octoprint/server.py +++ b/octoprint/server.py @@ -81,6 +81,8 @@ class PrinterStateConnection(SockJSConnection): self._eventManager.fire("ClientOpened") self._eventManager.subscribe("MovieDone", self._onMovieDone) + self._eventManager.subscribe("SlicingStarted", self._onSlicingStarted) + self._eventManager.subscribe("SlicingDone", self._onSlicingDone) global timelapse octoprint.timelapse.notifyCallbacks(timelapse) @@ -93,6 +95,8 @@ class PrinterStateConnection(SockJSConnection): self._eventManager.fire("ClientClosed") self._eventManager.unsubscribe("MovieDone", self._onMovieDone) + self._eventManager.unsubscribe("SlicingStarted", self._onSlicingStarted) + self._eventManager.unsubscribe("SlicingDone", self._onSlicingDone) def on_message(self, message): pass @@ -121,8 +125,8 @@ class PrinterStateConnection(SockJSConnection): def sendHistoryData(self, data): self._emit("history", data) - def sendUpdateTrigger(self, type): - self._emit("updateTrigger", type) + def sendUpdateTrigger(self, type, payload=None): + self._emit("updateTrigger", {"type": type, "payload": payload}) def sendFeedbackCommandOutput(self, name, output): self._emit("feedbackCommandOutput", {"name": name, "output": output}) @@ -145,6 +149,12 @@ class PrinterStateConnection(SockJSConnection): def _onMovieDone(self, event, payload): self.sendUpdateTrigger("timelapseFiles") + def _onSlicingStarted(self, event, payload): + self.sendUpdateTrigger("slicingStarted", payload) + + def _onSlicingDone(self, event, payload): + self.sendUpdateTrigger("slicingDone", payload) + def _emit(self, type, payload): self.send({type: payload}) @@ -756,11 +766,9 @@ def setSettings(): if "system" in data.keys(): if "actions" in data["system"].keys(): s.set(["system", "actions"], data["system"]["actions"]) - - if "events" in data["system"].keys(): s.set(["system", "events"], data["system"]["events"]) + if "events" in data["system"].keys(): s.set(["system", "events"], data["system"]["events"]) cura = data.get("cura", None) - if cura: path = cura.get("path") if path: diff --git a/octoprint/slicers/cura/__init__.py b/octoprint/slicers/cura/__init__.py index a91b2bae..0851e959 100644 --- a/octoprint/slicers/cura/__init__.py +++ b/octoprint/slicers/cura/__init__.py @@ -2,6 +2,7 @@ __author__ = "Ross Hendrickson savorywatt" __license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' import logging +import os from octoprint.settings import settings @@ -30,6 +31,7 @@ class Cura(object): raise Exception("Unable to create CuraEngine - no path specified") self.cura_path = cura_path + self._logger = logging.getLogger(__name__) def process_file( @@ -48,12 +50,18 @@ class Cura(object): def start_thread(call_back, call_back_args, call_args, cwd): import subprocess - process = subprocess.call(call_args, cwd=cwd) - call_back(*call_back_args) + self._logger.info("Running %r in %s" % (call_args, cwd)) + try: + subprocess.check_call(call_args, cwd=cwd) + call_back(*call_back_args) + except subprocess.CalledProcessError as (e): + self._logger.warn("Could not slice via Cura, got return code %r" % e.returncode) - args = ['python', '-m', 'Cura.cura', '-i', config, '-s', file_path, '-o', gcode] + executable = self.cura_path + (workingDir, ignored) = os.path.split(executable) + args = [executable, '-i', config, '-s', file_path, '-o', gcode] thread = threading.Thread(target=start_thread, args=(call_back, - call_back_args, args, self.cura_path)) + call_back_args, args, workingDir)) thread.start() diff --git a/octoprint/static/js/app/dataupdater.js b/octoprint/static/js/app/dataupdater.js index 919ea763..226bc6f6 100644 --- a/octoprint/static/js/app/dataupdater.js +++ b/octoprint/static/js/app/dataupdater.js @@ -74,45 +74,52 @@ function DataUpdater(loginStateViewModel, connectionViewModel, printerStateViewM self._onmessage = function(e) { for (var prop in e.data) { - var payload = e.data[prop]; + var data = e.data[prop]; switch (prop) { case "history": { - self.connectionViewModel.fromHistoryData(payload); - self.printerStateViewModel.fromHistoryData(payload); - self.temperatureViewModel.fromHistoryData(payload); - self.controlViewModel.fromHistoryData(payload); - self.terminalViewModel.fromHistoryData(payload); - self.timelapseViewModel.fromHistoryData(payload); - self.gcodeViewModel.fromHistoryData(payload); - self.gcodeFilesViewModel.fromCurrentData(payload); + self.connectionViewModel.fromHistoryData(data); + self.printerStateViewModel.fromHistoryData(data); + self.temperatureViewModel.fromHistoryData(data); + self.controlViewModel.fromHistoryData(data); + self.terminalViewModel.fromHistoryData(data); + self.timelapseViewModel.fromHistoryData(data); + self.gcodeViewModel.fromHistoryData(data); + self.gcodeFilesViewModel.fromCurrentData(data); break; } case "current": { - self.connectionViewModel.fromCurrentData(payload); - self.printerStateViewModel.fromCurrentData(payload); - self.temperatureViewModel.fromCurrentData(payload); - self.controlViewModel.fromCurrentData(payload); - self.terminalViewModel.fromCurrentData(payload); - self.timelapseViewModel.fromCurrentData(payload); - self.gcodeViewModel.fromCurrentData(payload); - self.gcodeFilesViewModel.fromCurrentData(payload); + self.connectionViewModel.fromCurrentData(data); + self.printerStateViewModel.fromCurrentData(data); + self.temperatureViewModel.fromCurrentData(data); + self.controlViewModel.fromCurrentData(data); + self.terminalViewModel.fromCurrentData(data); + self.timelapseViewModel.fromCurrentData(data); + self.gcodeViewModel.fromCurrentData(data); + self.gcodeFilesViewModel.fromCurrentData(data); break; } case "updateTrigger": { - if (payload == "gcodeFiles") { + var type = data["type"]; + var payload = data["payload"]; + if (type == "gcodeFiles") { gcodeFilesViewModel.requestData(); - } else if (payload == "timelapseFiles") { + } else if (type == "timelapseFiles") { timelapseViewModel.requestData(); + } else if (type == "slicingStarted") { + $.pnotify({title: "Slicing started", text: "Slicing " + payload.stl + " to " + payload.gcode}); + } else if (type == "slicingDone") { + $.pnotify({title: "Slicing done", text: "Sliced " + payload.stl + " to " + payload.gcode}); + gcodeFilesViewModel.requestData(payload.gcode); } break; } case "feedbackCommandOutput": { - self.controlViewModel.fromFeedbackCommandData(payload); + self.controlViewModel.fromFeedbackCommandData(data); break; } case "timelapse": { - self.printerStateViewModel.fromTimelapseData(payload); + self.printerStateViewModel.fromTimelapseData(data); break; } } diff --git a/octoprint/static/js/app/viewmodels/gcodefiles.js b/octoprint/static/js/app/viewmodels/gcodefiles.js index fdf2c411..c14ad2a2 100644 --- a/octoprint/static/js/app/viewmodels/gcodefiles.js +++ b/octoprint/static/js/app/viewmodels/gcodefiles.js @@ -96,12 +96,15 @@ function GcodeFilesViewModel(printerStateViewModel, loginStateViewModel) { self.isSdReady(data.flags.sdReady); } - self.requestData = function() { + self.requestData = function(filenameOverride) { $.ajax({ url: AJAX_BASEURL + "gcodefiles", method: "GET", dataType: "json", success: function(response) { + if (filenameOverride) { + response.filename = filenameOverride + } self.fromResponse(response); } }); diff --git a/octoprint/static/js/app/viewmodels/settings.js b/octoprint/static/js/app/viewmodels/settings.js index c8cdd214..2561e535 100644 --- a/octoprint/static/js/app/viewmodels/settings.js +++ b/octoprint/static/js/app/viewmodels/settings.js @@ -48,6 +48,10 @@ function SettingsViewModel(loginStateViewModel, usersViewModel) { self.folder_timelapseTmp = ko.observable(undefined); self.folder_logs = ko.observable(undefined); + self.cura_enabled = ko.observable(undefined); + self.cura_path = ko.observable(undefined); + self.cura_config = ko.observable(undefined); + self.temperature_profiles = ko.observableArray(undefined); self.system_actions = ko.observableArray([]); @@ -121,6 +125,10 @@ function SettingsViewModel(loginStateViewModel, usersViewModel) { self.folder_timelapseTmp(response.folder.timelapseTmp); self.folder_logs(response.folder.logs); + self.cura_enabled(response.cura.enabled); + self.cura_path(response.cura.path); + self.cura_config(response.cura.config); + self.temperature_profiles(response.temperature.profiles); self.system_actions(response.system.actions); @@ -182,6 +190,11 @@ function SettingsViewModel(loginStateViewModel, usersViewModel) { "system": { "actions": self.system_actions() }, + "cura": { + "enabled": self.cura_enabled(), + "path": self.cura_path(), + "config": self.cura_config() + }, "terminalFilters": self.terminalFilters() } diff --git a/octoprint/templates/settings.jinja2 b/octoprint/templates/settings.jinja2 index 9d81323c..c8187a7c 100644 --- a/octoprint/templates/settings.jinja2 +++ b/octoprint/templates/settings.jinja2 @@ -14,7 +14,7 @@
  • Features
  • Webcam
  • -
  • Cura
  • +
  • Cura
  • {% if enableAccessControl %}
  • Access Control
  • {% endif %}
  • Api
  • @@ -22,8 +22,6 @@
  • Appearance
  • -
    -
    @@ -317,28 +315,28 @@
    -
    -
    -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    -
    +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    +
    diff --git a/octoprint/util/__init__.py b/octoprint/util/__init__.py index 5bd7578b..48b3a55d 100644 --- a/octoprint/util/__init__.py +++ b/octoprint/util/__init__.py @@ -9,6 +9,7 @@ import time from octoprint.settings import settings + def getFormattedSize(num): """ Taken from http://stackoverflow.com/a/1094933/2028598 @@ -51,8 +52,6 @@ def getClass(name): m = getattr(m, comp) return m -def matchesGcode(line, gcode): - return re.search("^\s*%s\D" % gcode, line, re.I) def isGcodeFileName(filename): """Simple helper to determine if a filename has the .gcode extension. @@ -61,7 +60,8 @@ def isGcodeFileName(filename): :returns boolean: """ - return "." in filename and filename.rsplit(".", 1)[1] in ["gcode", "GCODE"] + return "." in filename and filename.rsplit(".", 1)[1].lower() in ["gcode", "gco"] + def isSTLFileName(filename): """Simple helper to determine if a filename has the .stl extension. @@ -70,10 +70,10 @@ def isSTLFileName(filename): :returns boolean: """ - return "." in filename and filename.rsplit(".", 1)[1] in ["stl", "STL"] - -def genGcodeFileName(filename): + return "." in filename and filename.rsplit(".", 1)[1].lower() in ["stl"] + +def genGcodeFileName(filename): if not filename: return None @@ -81,9 +81,9 @@ def genGcodeFileName(filename): return filename + ".gcode" return filename.replace('.stl', '.gcode') - -def genStlFileName(filename): + +def genStlFileName(filename): if not filename: return None @@ -91,7 +91,8 @@ def genStlFileName(filename): return filename + ".stl" return filename.replace('.gcode', '.stl') - + + def isDevVersion(): gitPath = os.path.abspath(os.path.join(os.path.split(os.path.abspath(__file__))[0], "../../.git")) return os.path.exists(gitPath) @@ -143,3 +144,10 @@ def getFreeBytes(path): else: st = os.statvfs(path) return st.f_bavail * st.f_frsize + + +def getRemoteAddress(request): + forwardedFor = request.headers.get("X-Forwarded-For", None) + if forwardedFor is not None: + return forwardedFor.split(",")[0] + return request.remote_addr