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