diff --git a/Cura/webui/__init__.py b/Cura/webui/__init__.py index fa9bd9e..db86ee6 100644 --- a/Cura/webui/__init__.py +++ b/Cura/webui/__init__.py @@ -1,11 +1,11 @@ #!/usr/bin/env python # coding=utf-8 -__author__ = 'Gina Häußge ' +__author__ = "Gina Häußge " from flask import Flask, request, render_template, jsonify, make_response from werkzeug import secure_filename -from printer import Printer +from printer import Printer, getConnectionOptions import sys import os @@ -16,15 +16,15 @@ BASEURL="/ajax/" SUCCESS={} # taken from http://stackoverflow.com/questions/1084697/how-do-i-store-desktop-application-data-in-a-cross-platform-way-for-python -if sys.platform == 'darwin': +if sys.platform == "darwin": from AppKit import NSSearchPathForDirectoriesInDomains # http://developer.apple.com/DOCUMENTATION/Cocoa/Reference/Foundation/Miscellaneous/Foundation_Functions/Reference/reference.html#//apple_ref/c/func/NSSearchPathForDirectoriesInDomains # NSApplicationSupportDirectory = 14 # NSUserDomainMask = 1 # True for expanding the tilde into a fully qualified path appdata = os.path.join(NSSearchPathForDirectoriesInDomains(14, 1, True)[0], APPNAME) -elif sys.platform == 'win32': - appdata = os.path.join(os.environ['APPDATA'], APPNAME) +elif sys.platform == "win32": + appdata = os.path.join(os.environ["APPDATA"], APPNAME) else: appdata = os.path.expanduser(os.path.join("~", "." + APPNAME.lower())) @@ -36,94 +36,111 @@ ALLOWED_EXTENSIONS = set(["gcode"]) app = Flask("Cura.webui") printer = Printer() -@app.route('/') +@app.route("/") def index(): - return render_template('index.html') + return render_template("index.html") #~~ Printer state -@app.route(BASEURL + 'state', methods=['GET']) +@app.route(BASEURL + "state", methods=["GET"]) def printerState(): temp = printer.currentTemp bedTemp = printer.currentBedTemp targetTemp = printer.currentTargetTemp bedTargetTemp = printer.currentBedTargetTemp jobData = printer.jobData() + gcodeState = printer.gcodeState() result = { - 'state': printer.getStateString(), - 'temp': temp, - 'bedTemp': bedTemp, - 'targetTemp': targetTemp, - 'targetBedTemp': bedTargetTemp, - 'operational': printer.isOperational(), - 'closedOrError': printer.isClosedOrError(), - 'error': printer.isError(), - 'printing': printer.isPrinting(), - 'paused': printer.isPaused(), - 'ready': printer.isReady() + "state": printer.getStateString(), + "temp": temp, + "bedTemp": bedTemp, + "targetTemp": targetTemp, + "targetBedTemp": bedTargetTemp, + "operational": printer.isOperational(), + "closedOrError": printer.isClosedOrError(), + "error": printer.isError(), + "printing": printer.isPrinting(), + "paused": printer.isPaused(), + "ready": printer.isReady(), + "loading": printer.isLoading() } - if (jobData != None): - result['job'] = jobData + if jobData is not None: + jobData["filename"] = jobData["filename"].replace(UPLOAD_FOLDER + os.sep, "") + result["job"] = jobData - if (request.values.has_key('temperatures')): - result['temperatures'] = printer.temps + if gcodeState is not None: + gcodeState["filename"] = gcodeState["filename"].replace(UPLOAD_FOLDER + os.sep, "") + result["gcode"] = gcodeState - if (request.values.has_key('log')): - result['log'] = printer.log + if request.values.has_key("temperatures"): + result["temperatures"] = printer.temps - if (request.values.has_key('messages')): - result['messages'] = printer.messages + if request.values.has_key("log"): + result["log"] = printer.log + + if request.values.has_key("messages"): + result["messages"] = printer.messages return jsonify(result) -@app.route(BASEURL + 'state/messages', methods=['GET']) +@app.route(BASEURL + "state/messages", methods=["GET"]) def printerMessages(): return jsonify(messages=printer.messages) -@app.route(BASEURL + 'state/log', methods=['GET']) +@app.route(BASEURL + "state/log", methods=["GET"]) def printerLogs(): return jsonify(log=printer.log) -@app.route(BASEURL + 'state/temperatures', methods=['GET']) +@app.route(BASEURL + "state/temperatures", methods=["GET"]) def printerTemperatures(): return jsonify(temperatures = printer.temps) #~~ Printer control -@app.route(BASEURL + 'control/connect', methods=['POST']) -def connect(): - printer.connect() - return jsonify(state='Connecting') +@app.route(BASEURL + "control/connectionOptions", methods=["GET"]) +def connectionOptions(): + return jsonify(getConnectionOptions()) -@app.route(BASEURL + 'control/disconnect', methods=['POST']) +@app.route(BASEURL + "control/connect", methods=["POST"]) +def connect(): + port = None + baudrate = None + if request.values.has_key("port"): + port = request.values["port"] + if request.values.has_key("baudrate"): + baudrate = request.values["baudrate"] + printer.connect(port=port, baudrate=baudrate) + return jsonify(state="Connecting") + +@app.route(BASEURL + "control/disconnect", methods=["POST"]) def disconnect(): printer.disconnect() - return jsonify(state='Offline') + return jsonify(state="Offline") -@app.route(BASEURL + 'control/command', methods=['POST']) +@app.route(BASEURL + "control/command", methods=["POST"]) def printerCommand(): - command = request.form['command'] + command = request.form["command"] printer.command(command) return jsonify(SUCCESS) -@app.route(BASEURL + 'control/print', methods=['POST']) +@app.route(BASEURL + "control/print", methods=["POST"]) def printGcode(): printer.startPrint() return jsonify(SUCCESS) -@app.route(BASEURL + 'control/pause', methods=['POST']) +@app.route(BASEURL + "control/pause", methods=["POST"]) def pausePrint(): printer.togglePausePrint() return jsonify(SUCCESS) -@app.route(BASEURL + 'control/cancel', methods=['POST']) +@app.route(BASEURL + "control/cancel", methods=["POST"]) def cancelPrint(): printer.cancelPrint() return jsonify(SUCCESS) -@app.route(BASEURL + 'control/temperature', methods=['POST']) +@app.route(BASEURL + "control/temperature", methods=["POST"]) def setTargetTemperature(): if not printer.isOperational(): return jsonify(SUCCESS) @@ -143,7 +160,7 @@ def setTargetTemperature(): @app.route(BASEURL + "control/jog", methods=["POST"]) def jog(): if not printer.isOperational() or printer.isPrinting(): - # do not jog when a print job is running or we don't have a connection + # do not jog when a print job is running or we don"t have a connection return jsonify(SUCCESS) if request.values.has_key("x"): @@ -169,7 +186,7 @@ def jog(): #~~ GCODE file handling -@app.route(BASEURL + 'gcodefiles', methods=['GET']) +@app.route(BASEURL + "gcodefiles", methods=["GET"]) def readGcodeFiles(): files = [] for osFile in os.listdir(UPLOAD_FOLDER): @@ -181,22 +198,22 @@ def readGcodeFiles(): }) return jsonify(files=files) -@app.route(BASEURL + 'gcodefiles/upload', methods=['POST']) +@app.route(BASEURL + "gcodefiles/upload", methods=["POST"]) def uploadGcodeFile(): - file = request.files['gcode_file'] + file = request.files["gcode_file"] if file and allowed_file(file.filename): secure = secure_filename(file.filename) filename = os.path.join(UPLOAD_FOLDER, secure) file.save(filename) return readGcodeFiles() -@app.route(BASEURL + 'gcodefiles/load', methods=['POST']) +@app.route(BASEURL + "gcodefiles/load", methods=["POST"]) def loadGcodeFile(): filename = request.values["filename"] printer.loadGcode(UPLOAD_FOLDER + os.sep + filename) return jsonify(SUCCESS) -@app.route(BASEURL + 'gcodefiles/delete', methods=['POST']) +@app.route(BASEURL + "gcodefiles/delete", methods=["POST"]) def deleteGcodeFile(): if request.values.has_key("filename"): filename = request.values["filename"] @@ -210,14 +227,15 @@ def sizeof_fmt(num): """ Taken from http://stackoverflow.com/questions/1094841/reusable-library-to-get-human-readable-version-of-file-size """ - for x in ['bytes','KB','MB','GB']: + for x in ["bytes","KB","MB","GB"]: if num < 1024.0: return "%3.1f%s" % (num, x) num /= 1024.0 - return "%3.1f%s" % (num, 'TB') + return "%3.1f%s" % (num, "TB") def allowed_file(filename): return "." in filename and filename.rsplit(".", 1)[1] in ALLOWED_EXTENSIONS def run(): + app.debug = True app.run(host="0.0.0.0", port=5000) diff --git a/Cura/webui/printer.py b/Cura/webui/printer.py index 808e1c4..6d4419f 100644 --- a/Cura/webui/printer.py +++ b/Cura/webui/printer.py @@ -1,20 +1,33 @@ # coding=utf-8 -__author__ = 'Gina Häußge ' +__author__ = "Gina Häußge " import time import os +from threading import Thread import Cura.util.machineCom as machineCom from Cura.util import gcodeInterpreter +from Cura.util import profile + +def getConnectionOptions(): + """ + Retrieves the available ports, baudrates, prefered port and baudrate for connecting to the printer. + """ + return { + "ports": sorted(machineCom.serialList(), key=str.lower), + "baudrates": sorted(machineCom.baudrateList(), key=int, reverse=True), + "portPreference": profile.getPreference('serial_port_auto'), + "baudratePreference": int(profile.getPreference('serial_baud_auto')) + } class Printer(): def __init__(self): # state self.temps = { - 'actual': [], - 'target': [], - 'actualBed': [], - 'targetBed': [] + "actual": [], + "target": [], + "actualBed": [], + "targetBed": [] } self.messages = [] self.log = [] @@ -32,44 +45,68 @@ class Printer(): self.gcodeList = None self.filename = None + self.gcodeLoader = None + # comm self.comm = None - def connect(self): - if self.comm != None: + def connect(self, port=None, baudrate=None): + """ + Connects to the printer. If port and/or baudrate is provided, uses these settings, otherwise autodetection + will be attempted. + """ + if self.comm is not None: self.comm.close() - self.comm = machineCom.MachineCom(callbackObject=self) + self.comm = machineCom.MachineCom(port, baudrate, callbackObject=self) def disconnect(self): - if self.comm != None: + """ + Closes the connection to the printer. + """ + if self.comm is not None: self.comm.close() self.comm = None def command(self, command): + """ + Sends a single gcode command to the printer. + """ self.commands([command]) def commands(self, commands): + """ + Sends multiple gcode commands (provided as a list) to the printer. + """ for command in commands: self.comm.sendCommand(command) def mcLog(self, message): + """ + Callback method for the comm object, called upon log output. + Log line is stored in internal buffer, which is truncated to the last 300 lines. + """ self.log.append(message) self.log = self.log[-300:] def mcTempUpdate(self, temp, bedTemp, targetTemp, bedTargetTemp): + """ + Callback method for the comm object, called upon receiving new temperature information. + Temperature information (actual and target) for print head and print bed is stored in corresponding + temperature history (including timestamp), history is truncated to 300 entries. + """ currentTime = int(time.time() * 1000) - self.temps['actual'].append((currentTime, temp)) - self.temps['actual'] = self.temps['actual'][-300:] + self.temps["actual"].append((currentTime, temp)) + self.temps["actual"] = self.temps["actual"][-300:] - self.temps['target'].append((currentTime, targetTemp)) - self.temps['target'] = self.temps['target'][-300:] + self.temps["target"].append((currentTime, targetTemp)) + self.temps["target"] = self.temps["target"][-300:] - self.temps['actualBed'].append((currentTime, bedTemp)) - self.temps['actualBed'] = self.temps['actualBed'][-300:] + self.temps["actualBed"].append((currentTime, bedTemp)) + self.temps["actualBed"] = self.temps["actualBed"][-300:] - self.temps['targetBed'].append((currentTime, bedTargetTemp)) - self.temps['targetBed'] = self.temps['targetBed'][-300:] + self.temps["targetBed"].append((currentTime, bedTargetTemp)) + self.temps["targetBed"] = self.temps["targetBed"][-300:] self.currentTemp = temp self.currentTargetTemp = targetTemp @@ -77,22 +114,55 @@ class Printer(): self.currentBedTargetTemp = bedTargetTemp def mcStateChange(self, state): + """ + Callback method for the comm object, called if the connection state changes. + New state is stored for retrieval by the frontend. + """ self.state = state def mcMessage(self, message): + """ + Callback method for the comm object, called upon message exchanges via serial. + Stores the message in the message buffer, truncates buffer to the last 300 lines. + """ self.messages.append(message) self.messages = self.messages[-300:] def mcProgress(self, lineNr): + """ + Callback method for the comm object, called upon any change in progress of the printjob. + Triggers storage of new values for printTime, printTimeLeft and the current line. + """ self.printTime = self.comm.getPrintTime() self.printTimeLeft = self.comm.getPrintTimeRemainingEstimate() self.progress = self.comm.getPrintPos() def mcZChange(self, newZ): + """ + Callback method for the comm object, called upon change of the z-layer. + """ self.currentZ = newZ + def onGcodeLoaded(self, gcodeLoader): + """ + Callback method for the gcode loader, gets called when the gcode for the new printjob has finished loading. + Takes care to set filename, gcode and commandlist from the gcode loader and reset print job progress. + """ + self.filename = gcodeLoader.filename + self.gcode = gcodeLoader.gcode + self.gcodeList = gcodeLoader.gcodeList + self.currentZ = None + self.progress = None + self.printTime = None + self.printTimeLeft = None + + self.gcodeLoader = None + def jobData(self): - if self.gcode != None: + """ + Returns statistics regarding the currently loaded printjob, or None if no printjob is loaded. + """ + if self.gcode is not None: formattedPrintTime = None if (self.printTime): formattedPrintTime = "%02d:%02d" % (int(self.printTime / 60), int(self.printTime % 60)) @@ -102,16 +172,17 @@ class Printer(): formattedPrintTimeLeft = "%02d:%02d" % (int(self.printTimeLeft / 60), int(self.printTimeLeft % 60)) data = { - 'currentZ': self.currentZ, - 'line': self.progress, - 'totalLines': len(self.gcodeList), - 'printTime': formattedPrintTime, - 'printTimeLeft': formattedPrintTimeLeft, - 'filament': "%.2fm %.2fg" % ( + "filename": self.filename, + "currentZ": self.currentZ, + "line": self.progress, + "totalLines": len(self.gcodeList), + "printTime": formattedPrintTime, + "printTimeLeft": formattedPrintTimeLeft, + "filament": "%.2fm %.2fg" % ( self.gcode.extrusionAmount / 1000, self.gcode.calculateWeight() * 1000 ), - 'estimatedPrintTime': "%02d:%02d" % ( + "estimatedPrintTime": "%02d:%02d" % ( int(self.gcode.totalMoveTimeMinute / 60), int(self.gcode.totalMoveTimeMinute % 60) ) @@ -120,64 +191,68 @@ class Printer(): data = None return data + def gcodeState(self): + if self.gcodeLoader is not None: + return { + "filename": self.gcodeLoader.filename, + "progress": self.gcodeLoader.progress + } + else: + return None + def getStateString(self): - if self.comm == None: - return 'Offline' + """ + Returns a human readable string corresponding to the current communication state. + """ + if self.comm is None: + return "Offline" else: return self.comm.getStateString() def isClosedOrError(self): - return self.comm == None or self.comm.isClosedOrError() + return self.comm is None or self.comm.isClosedOrError() def isOperational(self): - return self.comm != None and self.comm.isOperational() + return self.comm is not None and self.comm.isOperational() def isPrinting(self): - return self.comm != None and self.comm.isPrinting() + return self.comm is not None and self.comm.isPrinting() def isPaused(self): - return self.comm != None and self.comm.isPaused() + return self.comm is not None and self.comm.isPaused() def isError(self): - return self.comm != None and self.comm.isError() + return self.comm is not None and self.comm.isError() def isReady(self): - return self.gcodeList and len(self.gcodeList) > 0 + return self.gcodeLoader is None and self.gcodeList and len(self.gcodeList) > 0 + + def isLoading(self): + return self.gcodeLoader is not None def loadGcode(self, file): - if self.comm != None and self.comm.isPrinting(): + """ + Loads the gcode from the given file as the new print job. + Aborts if the printer is currently printing or another gcode file is currently being loaded. + """ + if (self.comm is not None and self.comm.isPrinting()) or (self.gcodeLoader is not None): return - #Send an initial M110 to reset the line counter to zero. - prevLineType = lineType = 'CUSTOM' - gcodeList = ["M110"] - for line in open(file, 'r'): - if line.startswith(';TYPE:'): - lineType = line[6:].strip() - if ';' in line: - line = line[0:line.find(';')] - line = line.strip() - if len(line) > 0: - if prevLineType != lineType: - gcodeList.append((line, lineType, )) - else: - gcodeList.append(line) - prevLineType = lineType - gcode = gcodeInterpreter.gcode() - gcode.loadList(gcodeList) - #print "Loaded: %s (%d)" % (filename, len(gcodeList)) - self.filename = file - self.gcode = gcode - self.gcodeList = gcodeList - self.currentZ = None - self.progress = None - self.printTime = None - self.printTimeLeft = None + self.filename = None + self.gcode = None + self.gcodeList = None + + self.gcodeLoader = GcodeLoader(file, self) + self.gcodeLoader.start() def startPrint(self): - if self.comm == None or not self.comm.isOperational(): + """ + Starts the currently loaded print job. + Only starts if the printer is connected and operational, not currently printing and a printjob is loaded + """ + if self.comm is None or not self.comm.isOperational(): return - if self.gcodeList == None: + if self.gcodeList is None: return if self.comm.isPrinting(): return @@ -185,13 +260,72 @@ class Printer(): self.comm.printGCode(self.gcodeList) def togglePausePrint(self): - if self.comm == None: + """ + Pause the current printjob. + """ + if self.comm is None: return self.comm.setPause(not self.comm.isPaused()) - def cancelPrint(self): - if self.comm == None: + def cancelPrint(self, disableMotorsAndHeater=True): + """ + Cancel the current printjob. + """ + if self.comm is None: return self.comm.cancelPrint() - self.comm.sendCommands(["M84", "M104 S0", "M140 S0"]) # disable motors, switch off heaters + if disableMotorsAndHeater: + self.commands(["M84", "M104 S0", "M140 S0"]) # disable motors, switch off heaters + + # reset line, height, print time + self.currentZ = None + self.progress = None + self.printTime = None + self.printTimeLeft = None + +class GcodeLoader(Thread): + """ + The GcodeLoader takes care of loading a gcode-File from disk and parsing it into a gcode object in a separate + thread while constantly notifying interested listeners about the current progress. + The progress is returned as a float value between 0 and 1 which is to be interpreted as the percentage of completion. + """ + + def __init__(self, filename, printerCallback): + Thread.__init__(self); + + self.printerCallback = printerCallback; + + self.filename = filename + self.progress = None + + self.gcode = None + self.gcodeList = None + + def run(self): + #Send an initial M110 to reset the line counter to zero. + prevLineType = lineType = "CUSTOM" + gcodeList = ["M110"] + with open(self.filename, "r") as file: + for line in file: + if line.startswith(";TYPE:"): + lineType = line[6:].strip() + if ";" in line: + line = line[0:line.find(";")] + line = line.strip() + if len(line) > 0: + if prevLineType != lineType: + gcodeList.append((line, lineType, )) + else: + gcodeList.append(line) + prevLineType = lineType + + self.gcodeList = gcodeList + self.gcode = gcodeInterpreter.gcode() + self.gcode.progressCallback = self.onProgress + self.gcode.loadList(self.gcodeList) + + self.printerCallback.onGcodeLoaded(self) + + def onProgress(self, progress): + self.progress = progress diff --git a/Cura/webui/static/css/ui.css b/Cura/webui/static/css/ui.css index afbaf2d..09290d6 100644 --- a/Cura/webui/static/css/ui.css +++ b/Cura/webui/static/css/ui.css @@ -50,4 +50,8 @@ table th.gcode_files_action, table td.gcode_files_action { #temp_newTemp { text-align: right; +} + +#connection_ports, #connection_baudrates { + width: 100%; } \ No newline at end of file diff --git a/Cura/webui/static/js/ui.js b/Cura/webui/static/js/ui.js index 3b93ab1..857a8b4 100644 --- a/Cura/webui/static/js/ui.js +++ b/Cura/webui/static/js/ui.js @@ -1,3 +1,65 @@ +function ConnectionViewModel() { + var self = this; + + self.portOptions = ko.observableArray(undefined); + self.baudrateOptions = ko.observableArray(undefined); + self.selectedPort = ko.observable(undefined); + self.selectedBaudrate = ko.observable(undefined); + + self.isErrorOrClosed = ko.observable(undefined); + self.isOperational = ko.observable(undefined); + self.isPrinting = ko.observable(undefined); + self.isPaused = ko.observable(undefined); + self.isError = ko.observable(undefined); + self.isReady = ko.observable(undefined); + self.isLoading = ko.observable(undefined); + + self.buttonText = ko.computed(function() { + if (self.isErrorOrClosed()) + return "Connect"; + else + return "Disconnect"; + }) + + self.fromResponse = function(response) { + self.portOptions(response.ports); + self.baudrateOptions(response.baudrates); + + if (!self.selectedPort() && response.ports && response.ports.indexOf(response.portPreference) >= 0) + self.selectedPort(response.portPreference); + if (!self.selectedBaudrate() && response.baudrates && response.baudrates.indexOf(response.baudratePreference) >= 0) + self.selectedBaudrate(response.baudratePreference); + } + + self.fromStateResponse = function(response) { + self.isErrorOrClosed(response.closedOrError); + self.isOperational(response.operational); + self.isPaused(response.paused); + self.isPrinting(response.printing); + self.isError(response.error); + self.isReady(response.ready); + self.isLoading(response.loading); + } + + self.connect = function() { + if (self.isErrorOrClosed()) { + $.ajax({ + url: AJAX_BASEURL + "control/connect", + type: "POST", + dataType: "json", + data: { "port": self.selectedPort(), "baudrate": self.selectedBaudrate() } + }) + } else { + $.ajax({ + url: AJAX_BASEURL + "control/disconnect", + type: "POST", + dataType: "json" + }) + } + } +} +var connectionViewModel = new ConnectionViewModel(); + function PrinterStateViewModel() { var self = this; @@ -8,7 +70,9 @@ function PrinterStateViewModel() { self.isPaused = ko.observable(undefined); self.isError = ko.observable(undefined); self.isReady = ko.observable(undefined); + self.isLoading = ko.observable(undefined); + self.filename = ko.observable(undefined); self.filament = ko.observable(undefined); self.estimatedPrintTime = ko.observable(undefined); self.printTime = ko.observable(undefined); @@ -35,14 +99,6 @@ function PrinterStateViewModel() { return "Pause"; }); - self.connect = function() { - $.ajax({ - url: AJAX_BASEURL + "control/connect", - type: 'POST', - dataType: 'json' - }) - } - self.fromResponse = function(response) { self.stateString(response.state); self.isErrorOrClosed(response.closedOrError); @@ -51,8 +107,10 @@ function PrinterStateViewModel() { self.isPrinting(response.printing); self.isError(response.error); self.isReady(response.ready); + self.isLoading(response.loading); if (response.job) { + self.filename(response.job.filename); self.filament(response.job.filament); self.estimatedPrintTime(response.job.estimatedPrintTime); self.printTime(response.job.printTime); @@ -61,11 +119,17 @@ function PrinterStateViewModel() { self.totalLines(response.job.totalLines ? response.job.totalLines : 0); self.currentHeight(response.job.currentZ); } else { + if (response.loading && response.gcode) { + self.filename("Loading... (" + Math.round(response.gcode.progress * 100) + "%)"); + } else { + self.filename(undefined); + } self.filament(undefined); self.estimatedPrintTime(undefined); self.printTime(undefined); self.printTimeLeft(undefined); self.currentLine(undefined); + self.totalLines(undefined); self.currentHeight(undefined); } } @@ -79,12 +143,14 @@ function TemperatureViewModel() { self.bedTemp = ko.observable(undefined); self.targetTemp = ko.observable(undefined); self.bedTargetTemp = ko.observable(undefined); + self.isErrorOrClosed = ko.observable(undefined); self.isOperational = ko.observable(undefined); self.isPrinting = ko.observable(undefined); self.isPaused = ko.observable(undefined); self.isError = ko.observable(undefined); self.isReady = ko.observable(undefined); + self.isLoading = ko.observable(undefined); self.tempString = ko.computed(function() { if (!self.temp()) @@ -116,7 +182,6 @@ function TemperatureViewModel() { }, xaxis: { mode: "time", - timeformat: "%H:%M:%S", minTickSize: [2, "minute"], tickFormatter: function(val, axis) { var now = new Date(); @@ -146,6 +211,7 @@ function TemperatureViewModel() { self.isPrinting(response.printing); self.isError(response.error); self.isReady(response.ready); + self.isLoading(response.loading); self.updatePlot(); } @@ -174,9 +240,9 @@ function TerminalViewModel() { } self.updateOutput = function() { - var output = ''; + var output = ""; for (var i = 0; i < self.log.length; i++) { - output += self.log[i] + '\n'; + output += self.log[i] + "\n"; } var container = $("#terminal-output"); @@ -227,13 +293,14 @@ function GcodeFilesViewModel() { } var gcodeFilesViewModel = new GcodeFilesViewModel(); -function DataUpdater(printerStateViewModel, temperatureViewModel, terminalViewModel) { +function DataUpdater(connectionViewModel, printerStateViewModel, temperatureViewModel, terminalViewModel) { var self = this; self.updateInterval = 500; self.includeTemperatures = true; self.includeLogs = true; + self.connectionViewModel = connectionViewModel; self.printerStateViewModel = printerStateViewModel; self.temperatureViewModel = temperatureViewModel; self.terminalViewModel = terminalViewModel; @@ -248,48 +315,51 @@ function DataUpdater(printerStateViewModel, temperatureViewModel, terminalViewMo $.ajax({ url: AJAX_BASEURL + "state", - type: 'GET', - dataType: 'json', + type: "GET", + dataType: "json", data: parameters, success: function(response) { self.printerStateViewModel.fromResponse(response); + self.connectionViewModel.fromStateResponse(response); if (response.temperatures) self.temperatureViewModel.fromResponse(response); if (response.log) self.terminalViewModel.fromResponse(response); + }, + error: function(jqXHR, textState, errorThrows) { + //alert(textState); } }); setTimeout(self.requestData, self.updateInterval); } } -var dataUpdater = new DataUpdater(printerStateViewModel, temperatureViewModel, terminalViewModel); +var dataUpdater = new DataUpdater(connectionViewModel, printerStateViewModel, temperatureViewModel, terminalViewModel); $(function() { - $("#printer_connect").click(printerStateViewModel.connect); $("#job_print").click(function() { $.ajax({ url: AJAX_BASEURL + "control/print", - type: 'POST', - dataType: 'json', + type: "POST", + dataType: "json", success: function(){} }) }) $("#job_pause").click(function() { - $("#job_pause").button('toggle'); + $("#job_pause").button("toggle"); $.ajax({ url: AJAX_BASEURL + "control/pause", - type: 'POST', - dataType: 'json', + type: "POST", + dataType: "json" }) }) $("#job_cancel").click(function() { $.ajax({ url: AJAX_BASEURL + "control/cancel", - type: 'POST', - dataType: 'json', + type: "POST", + dataType: "json" }) }) @@ -343,26 +413,24 @@ $(function() { var command = $("#terminal-command").val(); $.ajax({ url: AJAX_BASEURL + "control/command", - type: 'POST', - dataType: 'json', - data: 'command=' + command, + type: "POST", + dataType: "json", + data: "command=" + command }) }) - $('#gcode_upload').fileupload({ - dataType: 'json', + $("#gcode_upload").fileupload({ + dataType: "json", done: function (e, data) { gcodeFilesViewModel.fromResponse(data.result); }, progressall: function (e, data) { var progress = parseInt(data.loaded / data.total * 100, 10); - $('#gcode_upload_progress .bar').css( - 'width', - progress + '%' - ); + $("#gcode_upload_progress .bar").css("width", progress + "%"); } }); + ko.applyBindings(connectionViewModel, document.getElementById("connection")); ko.applyBindings(printerStateViewModel, document.getElementById("state")); ko.applyBindings(gcodeFilesViewModel, document.getElementById("files")); ko.applyBindings(temperatureViewModel, document.getElementById("temp")); @@ -372,12 +440,20 @@ $(function() { dataUpdater.requestData(); $.ajax({ url: AJAX_BASEURL + "gcodefiles", - method: 'GET', - dataType: 'json', + method: "GET", + dataType: "json", success: function(response) { self.gcodeFilesViewModel.fromResponse(response); } }); + $.ajax({ + url: AJAX_BASEURL + "control/connectionOptions", + method: "GET", + dataType: "json", + success: function(response) { + connectionViewModel.fromResponse(response); + } + }) } ); diff --git a/Cura/webui/templates/index.html b/Cura/webui/templates/index.html index c44a24b..0823551 100644 --- a/Cura/webui/templates/index.html +++ b/Cura/webui/templates/index.html @@ -32,15 +32,28 @@
+
+ +
+
+ + + + + +
+
+
- - Machine State:
+ File:
Filament:
Estimated Print Time:
Line:
@@ -144,21 +157,21 @@
 
-
+
 
-
+
-
-
-
-
+
+
+
+
 
-
+
 
-
+