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.
This commit is contained in:
parent
b352078917
commit
f07305ae4f
9 changed files with 128 additions and 86 deletions
|
|
@ -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
|
||||
<ul>
|
||||
<li>has the extension ".gcode" or ".stl"</li>
|
||||
<li>has any of the extensions listed in SUPPORTED_EXTENSIONS</li>
|
||||
<li>exists and is a file (not a directory) if "mustExist" is set to True</li>
|
||||
</ul>
|
||||
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
<li class="nav-header">Features</li>
|
||||
<li><a href="#settings_features" data-toggle="tab">Features</a></li>
|
||||
<li><a href="#settings_webcam" data-toggle="tab">Webcam</a></li>
|
||||
<li><a href="#settings_cura" data-toggle="tab">Cura</a></li>
|
||||
<li><a href="#settings_cura" data-toggle="tab">Cura</a></li>
|
||||
{% if enableAccessControl %}<li><a href="#settings_users" data-toggle="tab">Access Control</a></li>{% endif %}
|
||||
<li><a href="#settings_api" data-toggle="tab">Api</a></li>
|
||||
<li class="nav-header">OctoPrint</li>
|
||||
|
|
@ -22,8 +22,6 @@
|
|||
<li><a href="#settings_appearance" data-toggle="tab">Appearance</a></li>
|
||||
</ul>
|
||||
|
||||
<div class="tab-content span8">
|
||||
<div class="tab-pane active" id="settings_printerParameters">
|
||||
<div class="tab-content span8">
|
||||
<div class="tab-pane active" id="settings_serialConnection">
|
||||
<form class="form-horizontal">
|
||||
|
|
@ -317,28 +315,28 @@
|
|||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="tab-pane" id="settings_cura">
|
||||
<form class="form-horizontal">
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="settings-curaEnabled">Cura Enabled</label>
|
||||
<div class="controls">
|
||||
<input type="checkbox" data-bind="checked: cura_enabled">
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="settings-curaPath">CuraEngine Path</label>
|
||||
<div class="controls">
|
||||
<input type="text" class="input-block-level" data-bind="value: cura_path">
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="settings-curaConfig">Cura Config</label>
|
||||
<div class="controls">
|
||||
<input type="text" class="input-block-level" data-bind="value: cura_config">
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="tab-pane" id="settings_cura">
|
||||
<form class="form-horizontal">
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="settings-curaEnabled">Enable slicing via Cura</label>
|
||||
<div class="controls">
|
||||
<input type="checkbox" data-bind="checked: cura_enabled">
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="settings-curaPath">Path to Cura</label>
|
||||
<div class="controls">
|
||||
<input type="text" class="input-block-level" data-bind="value: cura_path">
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="settings-curaConfig">Path to Cura config</label>
|
||||
<div class="controls">
|
||||
<input type="text" class="input-block-level" data-bind="value: cura_config">
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="tab-pane" id="settings_api">
|
||||
<form class="form-horizontal">
|
||||
<div class="control-group">
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue