First parts of a webui for Cura

master
Gina Häußge 2012-12-25 11:55:00 +01:00
parent 5e1998d6a1
commit 580ebedfa9
17 changed files with 12521 additions and 0 deletions

View File

@ -52,6 +52,8 @@ def main():
help="Internal option, do not use!")
parser.add_option("-s", "--slice", action="store_true", dest="slice",
help="Slice the given files instead of opening them in Cura")
parser.add_option("-w", "--web", action="store_true", dest="webui",
help="Start the webui instead of the normal Cura UI")
(options, args) = parser.parse_args()
if options.profile is not None:
@ -65,6 +67,9 @@ def main():
elif options.slice is not None:
from Cura.util import sliceRun
sliceRun.runSlice(args)
elif options.webui:
import Cura.webui as webapp
webapp.run()
else:
#Place any unused arguments as last file, so Cura starts with opening those files.
if len(args) > 0:

91
Cura/webui/__init__.py Normal file
View File

@ -0,0 +1,91 @@
#!/usr/bin/env python
# coding=utf-8
__author__ = 'Gina Häußge <osd@foosel.net>'
from flask import Flask, request, render_template, jsonify
from printer import Printer
import tempfile
app = Flask("Cura.webui")
printer = Printer()
printer.connect()
@app.route('/')
def index():
return render_template('index.html')
@app.route('/api/printer/connect', methods=['POST'])
def connect():
printer.connect()
return jsonify(state='Connecting')
@app.route('/api/printer/disconnect', methods=['POST'])
def disconnect():
printer.disconnect()
return jsonify(state='Offline')
@app.route('/api/printer', methods=['GET'])
def printerState():
temp = printer.currentTemp
bedTemp = printer.currentBedTemp
jobData = printer.jobData()
if jobData != None:
return jsonify(state=printer.getStateString(),
operational=printer.isOperational(),
closedOrError=printer.isClosedOrError(),
temp=temp,
bedTemp=bedTemp,
job=jobData)
else:
return jsonify(state=printer.getStateString(),
temperature=temp,
bedTemperature=bedTemp)
@app.route('/api/printer/messages', methods=['GET'])
def printerMessages():
return jsonify(messages=printer.messages)
@app.route('/api/printer/log', methods=['GET'])
def printerMessages():
return jsonify(log=printer.log)
@app.route('/api/printer/command', methods=['POST'])
def printerCommand():
command = request.form['command']
printer.command(command)
return jsonify(state=printer.getStateString())
@app.route('/api/printer/temperatures', methods=['GET'])
def printerTemperatures():
return jsonify(temperatures = printer.temps)
@app.route('/api/printer/gcode', methods=['POST'])
def uploadGcodeFile():
file = request.files['gcode_file']
if file != None:
(handle, filename) = tempfile.mkstemp(suffix='.gcode', prefix='tmp_', text=True)
file.save(filename)
printer.loadGcode(filename)
return jsonify(state=printer.getStateString())
@app.route('/api/printer/print', methods=['POST'])
def printGcode():
printer.startPrint()
return jsonify(state=printer.getStateString())
@app.route('/api/printer/pause', methods=['POST'])
def pausePrint():
printer.togglePausePrint()
return jsonify(state=printer.getStateString())
@app.route('/api/printer/cancel', methods=['POST'])
def cancelPrint():
printer.cancelPrint()
return jsonify(state=printer.getStateString())
def run():
app.debug = True
app.run()

208
Cura/webui/printer.py Normal file
View File

@ -0,0 +1,208 @@
# coding=utf-8
__author__ = 'Gina Häußge <osd@foosel.net>'
import time
import os
import Cura.util.machineCom as machineCom
from Cura.util import gcodeInterpreter
class Printer():
def __init__(self):
# state
self.temps = {
'actual': [],
'target': [],
'actualBed': [],
'targetBed': []
}
self.messages = []
self.log = []
self.state = None
self.currentZ = None
self.progress = None
self.printTime = None
self.printTimeLeft = None
self.currentTemp = None
self.currentBedTemp = None
self.gcode = None
self.gcodeList = None
self.filename = None
# comm
self.comm = None
def connect(self):
if self.comm != None:
self.comm.close()
self.comm = machineCom.MachineCom(port='VIRTUAL', callbackObject=self)
def disconnect(self):
if self.comm != None:
self.comm.close()
self.comm = None
def command(self, command):
self.comm.sendCommand(command)
def mcLog(self, message):
self.log.append(message)
self.log = self.log[-300:]
def mcTempUpdate(self, temp, bedTemp, targetTemp, bedTargetTemp):
currentTime = time.time()
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['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.currentTemp = temp
self.currentBedTemp = bedTemp
def mcStateChange(self, state):
self.state = state
def mcMessage(self, message):
self.messages.append(message)
self.messages = self.message[-300:]
def mcProgress(self, lineNr):
self.printTime = self.comm.getPrintTime()
self.printTimeLeft = self.comm.getPrintTimeRemainingEstimate()
self.progress = self.comm.getPrintPos()
def mcZChange(self, newZ):
self.currentZ = newZ
def jobData(self):
if self.gcode != None:
formattedPrintTime = None
if (self.printTime):
"%02d:%02d" % (int(self.printTime / 60), int(self.printTime % 60))
formattedPrintTimeLeft = None
if (self.printTimeLeft):
"%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" % (
self.gcode.extrusionAmount / 1000,
self.gcode.calculateWeight() * 1000
),
'estimatedPrintTime': "%02d:%02d" % (
int(self.gcode.totalMoveTimeMinute / 60),
int(self.gcode.totalMoveTimeMinute % 60)
)
}
else:
data = None
return data
def getStateString(self):
if self.comm == None:
return 'Offline'
else:
return self.comm.getStateString()
def isClosedOrError(self):
return self.comm == None or self.comm.isClosedOrError()
def isOperational(self):
return self.comm != None and self.comm.isOperational()
def loadGcode(self, file):
if self.comm != None and self.comm.isPrinting():
return
# delete old temporary file
if self.filename != None:
os.remove(self.filename)
#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
def startPrint(self):
if self.comm == None or not self.comm.isOperational():
return
if self.gcodeList == None:
return
if self.comm.isPrinting():
return
self.currentZ = -1
self.comm.printGCode(self.gcodeList)
def togglePausePrint(self):
if self.comm == None:
return
self.comm.setPause(not self.comm.isPaused())
def cancelPrint(self):
if self.comm == None:
return
self.comm.cancelPrint()
self.comm.sendCommand("M84")
class Temperature():
def __init__(self, actual=None, target=None):
self._actual = actual
self._target = target
def actual(self):
return self._actual
def target(self):
return self._target
def asDict(self):
return {'actual': self._actual, 'target': self._target}
class Position():
def __init__(self, x=None, y=None, z=None):
self._x = x
self._y = y
self._z = z
def update(self, x=None, y=None, z=None):
if x != None:
self._x = x
if y != None:
self._y = y
if z != None:
self._z = z
def get(self):
return {'x': self._x, 'y': self._y, 'z': self._z}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

File diff suppressed because it is too large Load Diff

6
Cura/webui/static/js/bootstrap.min.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,88 @@
var printerstate = function() {
var updateInterval = 500;
function update() {
function onDataReceived(response) {
$("#printer_state").text(response.state);
$("#printer_temp").text(response.temp + " °C");
$("#printer_bedTemp").text(response.bedTemp + " °C");
if (response.job) {
var currentLine = (response.job.line ? response.job.line : 0);
var progress = currentLine * 100 / response.job.totalLines;
$("#job_filament").text(response.job.filament);
$("#job_estimatedPrintTime").text(response.job.estimatedPrintTime);
$("#job_line").text((currentLine == 0 ? "-" : currentLine) + "/" + response.job.totalLines + " " + progress + "%");
$("#job_height").text(response.job.currentZ);
$("#job_printTime").text(response.job.printTime);
$("#job_printTimeLeft").text(response.job.printTimeLeft);
$("#job_progressBar").width(progress + "%");
} else {
$("#job_filament").text("-");
$("#job_estimatedPrintTime").text("-");
$("#job_line").text("-");
$("#job_height").text("-");
$("#job_printTime").text("-");
$("#job_printTimeLefT").text("-");
$("#job_progressBar").width("0%");
}
if (!response.closedOrError) {
$("#printer_connect").click(function(){});
$("#printer_connect").addClass("disabled");
} else {
$("#printer_connect").click(connect);
$("#printer_connect").removeClass("disabled");
}
$("#job_print").click(function() {
$.ajax({
url: "/api/printer/print",
type: 'POST',
dataType: 'json',
success: function(){}
})
})
$("#job_pause").click(function() {
$.ajax({
url: "/api/printer/pause",
type: 'POST',
dataType: 'json',
success: function(){}
})
})
$("#job_cancel").click(function() {
$.ajax({
url: "/api/printer/cancel",
type: 'POST',
dataType: 'json',
success: function(){}
})
})
}
$.ajax({
url: "/api/printer",
method: 'GET',
dataType: 'json',
success: onDataReceived
});
setTimeout(update, updateInterval);
}
function connect() {
$.ajax({
url: "/api/printer/connect",
type: 'POST',
dataType: 'json',
success: function(response) {
// do nothing
}
})
}
update();
}
printerstate();

View File

@ -0,0 +1,45 @@
var temperaturegraph = function() {
var options = {
yaxis: {
min: 0,
max: 310,
ticks: 10
},
xaxis: {
mode: "time"
},
legend: {
noColumns: 4
}
};
var updateInterval = 500;
function update() {
function onDataReceived(response) {
var temps = response.temperatures;
var data = [
{label: "Actual", color: "#FF4040", data: temps.actual},
{label: "Target", color: "#FFA0A0", data: temps.target},
{label: "Bed Actual", color: "#4040FF", data: temps.actualBed},
{label: "Bed Target", color: "#A0A0FF", data: temps.targetBed}
]
$.plot($("#temperature-graph"), data, options);
}
$.ajax({
url: "/api/printer/temperatures",
method: 'GET',
dataType: 'json',
success: onDataReceived
})
setTimeout(update, updateInterval);
}
update();
}
temperaturegraph();

View File

@ -0,0 +1,49 @@
var terminaloutput = function() {
var updateInterval = 1000;
function update() {
function onDataReceived(response) {
var log = response.log;
var output = '';
for (var i = 0; i < log.length; i++) {
output += log[i] + '<br>';
}
var container = $("#terminal-output");
var autoscroll = (container.scrollTop() == container[0].scrollHeight - container.height);
container.html(output);
if (autoscroll) {
container.scrollTop(container[0].scrollHeight - container.height())
}
}
$.ajax({
url: "/api/printer/log",
type: 'GET',
dataType: 'json',
success: onDataReceived
})
setTimeout(update, updateInterval);
}
update();
$("#terminal-send").click(function () {
var command = $("#terminal-command").val();
$.ajax({
url: "/api/printer/command",
type: 'POST',
dataType: 'json',
data: 'command=' + command,
success: function(response) {
// do nothing
}
})
})
}
terminaloutput();

View File

@ -0,0 +1,121 @@
<!DOCTYPE html>
<html>
<head>
<title>Bootstrap 101 Template</title>
<link href="{{ url_for('static', filename='css/bootstrap.min.css') }}" rel="stylesheet" media="screen">
<script src="http://code.jquery.com/jquery-latest.js"></script>
<script src="{{ url_for('static', filename='js/bootstrap.js') }}"></script>
<script src="{{ url_for('static', filename='js/jquery.flot.js') }}"></script>
</head>
<body>
<div class="container-fluid">
<div class="row-fluid">
<h1>Cura WebUI</h1>
</div>
<div class="row-fluid">
<div class="span3">
<h2>Printer</h2>
Machine State: <strong id="printer_state"></strong><br>
<button class="btn" id="printer_connect">Connect</button>
<h2>Job</h2>
Filament: <strong id="job_filament"></strong><br>
Estimated Print Time: <strong id="job_estimatedPrintTime"></strong><br>
Line: <strong id="job_line"></strong><br>
Height: <strong id="job_currentZ"></strong><br>
Print Time: <strong id="job_printTime"></strong><br>
Print Time Left: <strong id="job_printTimeLeft"></strong><br>
<div class="progress">
<div class="bar" id="job_progressBar" style="width: 30%;"></div>
</div>
<button class="btn" id="job_print">Print</button>
<button class="btn" id="job_pause">Pause</button>
<button class="btn" id="job_cancel">Cancel</button>
<form action="/api/printer/gcode" method="post" enctype="multipart/form-data">
<input type="file" name="gcode_file">
<button class="btn" type="submit">Load</button>
</form>
</div>
<div class="span9 tabbable tabs-left">
<ul class="nav nav-tabs">
<li class="active"><a href="#temp" data-toggle="tab">Temp</a></li>
<li><a href="#jog" data-toggle="tab">Jog</a></li>
<li><a href="#speed" data-toggle="tab">Speed</a></li>
<li><a href="#term" data-toggle="tab">Term</a></li>
</ul>
<div class="tab-content">
<div class="tab-pane active" id="temp">
<div id="temperature-graph" class="span6 " style="height: 350px"></div>
<div class="span3">
Temp: <strong id="printer_temp"></strong><br>
Bed Temp: <strong id="printer_bedTemp"></strong><br>
</div>
</div>
<div class="tab-pane" id="jog">
<div class="span9">
<div class="row">
<div class="span1 offset1 btn-group btn-group-vertical">
<button class="btn btn-block btn-large" type="button"><i class="icon-chevron-up"></i></button>
<button class="btn btn-block" type="button"><i class="icon-chevron-up"></i></button>
<button class="btn btn-block btn-small" type="button"><i class="icon-chevron-up"></i></button>
</div>
<div class="span1 offset1 btn-group btn-group-vertical">
<button class="btn btn-block btn-large" type="button"><i class="icon-chevron-up"></i></button>
<button class="btn btn-block" type="button"><i class="icon-chevron-up"></i></button>
<button class="btn btn-block btn-small" type="button"><i class="icon-chevron-up"></i></button>
</div>
</div>
<div class="row">
<div class="span1 btn-group btn-group">
<button class="btn" type="button"><i class="icon-chevron-left"></i></button>
<button class="btn" type="button"><i class="icon-chevron-left"></i></button>
<button class="btn" type="button"><i class="icon-chevron-left"></i></button>
</div>
<div class="span1">
<button class="btn btn-block" type="button"><i class="icon-home"></i></button>
</div>
<div class="span1 btn-group btn-group">
<button class="btn" type="button"><i class="icon-chevron-right"></i></button>
<button class="btn" type="button"><i class="icon-chevron-right"></i></button>
<button class="btn" type="button"><i class="icon-chevron-right"></i></button>
</div>
<div class="span1">
<button class="btn btn-block" type="button"><i class="icon-home"></i></button>
</div>
</div>
<div class="row">
<div class="span1 offset1 btn-group btn-group-vertical">
<button class="btn btn-block btn-small" type="button"><i class="icon-chevron-down"></i></button>
<button class="btn btn-block" type="button"><i class="icon-chevron-down"></i></button>
<button class="btn btn-block btn-large" type="button"><i class="icon-chevron-down"></i></button>
</div>
</div>
</div>
</div>
<div class="tab-pane" id="speed">
Here be speed settings
</div>
<div class="tab-pane" id="term">
<pre id="terminal-output" class="pre-scrollable"></pre>
<div class="input-append">
<input type="text" class="span9" id="terminal-command">
<button class="btn" type="button" id="terminal-send">Send</button>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Here be scripts -->
<script src="{{ url_for('static', filename='js/printerstate.js') }}"></script>
<script src="{{ url_for('static', filename='js/temperaturegraph.js') }}"></script>
<script src="{{ url_for('static', filename='js/terminaloutput.js') }}"></script>
</body>
</html>

View File

@ -1,3 +1,4 @@
flask>=0.9
PyOpenGL>=3.0.2
numpy>=1.6.2
pyserial>=2.6