Revamped gcode analysis.

Now UI and backend take data from saved metadata (if available). Metadata gets written after file upload and also on startup (for files that have not been added yet). Gcode analysis is interrupted if a printjob is started and resumed when it ends. Frontend is notified when new metadata comes available and UI triggers reload of gcode file list.  Also started on implementing proper logging.
master
Gina Häußge 2013-02-03 21:14:22 +01:00
parent 8d90ad26ba
commit 6fd0646128
7 changed files with 169 additions and 66 deletions

View File

@ -8,6 +8,7 @@ import threading
import datetime import datetime
import yaml import yaml
import time import time
import logging
import octoprint.util as util import octoprint.util as util
import octoprint.util.gcodeInterpreter as gcodeInterpreter import octoprint.util.gcodeInterpreter as gcodeInterpreter
from octoprint.settings import settings from octoprint.settings import settings
@ -16,8 +17,12 @@ from werkzeug.utils import secure_filename
class GcodeManager: class GcodeManager:
def __init__(self): def __init__(self):
self._logger = logging.getLogger(__name__)
self._uploadFolder = settings().getBaseFolder("uploads") self._uploadFolder = settings().getBaseFolder("uploads")
self._callbacks = []
self._metadata = {} self._metadata = {}
self._metadataDirty = False self._metadataDirty = False
self._metadataFile = os.path.join(self._uploadFolder, "metadata.yaml") self._metadataFile = os.path.join(self._uploadFolder, "metadata.yaml")
@ -26,9 +31,22 @@ class GcodeManager:
self._metadataAnalyzer = MetadataAnalyzer(getPathCallback=self.getAbsolutePath, loadedCallback=self._onMetadataAnalysisFinished) self._metadataAnalyzer = MetadataAnalyzer(getPathCallback=self.getAbsolutePath, loadedCallback=self._onMetadataAnalysisFinished)
self._loadMetadata() self._loadMetadata()
self._processAnalysisBacklog()
def _processAnalysisBacklog(self):
for osFile in os.listdir(self._uploadFolder):
filename = self._getBasicFilename(osFile)
absolutePath = self.getAbsolutePath(filename)
if absolutePath is None:
continue
fileData = self.getFileData(filename)
if fileData is not None and "gcodeAnalysis" in fileData.keys():
continue
self._metadataAnalyzer.addFileToBacklog(filename)
def _onMetadataAnalysisFinished(self, filename, gcode): def _onMetadataAnalysisFinished(self, filename, gcode):
print("Got gcode analysis for file %s: %r" % (filename, gcode))
if filename is None or gcode is None: if filename is None or gcode is None:
return return
@ -59,6 +77,8 @@ class GcodeManager:
with self._metadataFileAccessMutex: with self._metadataFileAccessMutex:
with open(self._metadataFile, "r") as f: with open(self._metadataFile, "r") as f:
self._metadata = yaml.safe_load(f) self._metadata = yaml.safe_load(f)
if self._metadata is None:
self._metadata = {}
def _saveMetadata(self, force=False): def _saveMetadata(self, force=False):
if not self._metadataDirty and not force: if not self._metadataDirty and not force:
@ -69,6 +89,7 @@ class GcodeManager:
yaml.safe_dump(self._metadata, f, default_flow_style=False, indent=" ", allow_unicode=True) yaml.safe_dump(self._metadata, f, default_flow_style=False, indent=" ", allow_unicode=True)
self._metadataDirty = False self._metadataDirty = False
self._loadMetadata() self._loadMetadata()
self._sendUpdateTrigger("gcodeFiles")
def _getBasicFilename(self, filename): def _getBasicFilename(self, filename):
if filename.startswith(self._uploadFolder): if filename.startswith(self._uploadFolder):
@ -76,6 +97,22 @@ class GcodeManager:
else: else:
return filename return filename
#~~ callback handling
def registerCallback(self, callback):
self._callbacks.append(callback)
def unregisterCallback(self, callback):
if callback in self._callbacks:
self._callbacks.remove(callback)
def _sendUpdateTrigger(self, type):
for callback in self._callbacks:
try: callback.sendUpdateTrigger(type)
except: pass
#~~ file handling
def addFile(self, file): def addFile(self, file):
if file: if file:
absolutePath = self.getAbsolutePath(file.filename, mustExist=False) absolutePath = self.getAbsolutePath(file.filename, mustExist=False)
@ -90,6 +127,10 @@ class GcodeManager:
absolutePath = self.getAbsolutePath(filename) absolutePath = self.getAbsolutePath(filename)
if absolutePath is not None: if absolutePath is not None:
os.remove(absolutePath) os.remove(absolutePath)
if filename in self._metadata.keys():
del self._metadata[filename]
self._metadataDirty = True
self._saveMetadata()
def getAbsolutePath(self, filename, mustExist=True): def getAbsolutePath(self, filename, mustExist=True):
""" """
@ -177,6 +218,8 @@ class GcodeManager:
self._metadata[filename] = metadata self._metadata[filename] = metadata
self._metadataDirty = True self._metadataDirty = True
#~~ print job data
def printSucceeded(self, filename): def printSucceeded(self, filename):
filename = self._getBasicFilename(filename) filename = self._getBasicFilename(filename)
absolutePath = self.getAbsolutePath(filename) absolutePath = self.getAbsolutePath(filename)
@ -207,8 +250,18 @@ class GcodeManager:
self.setFileMetadata(filename, metadata) self.setFileMetadata(filename, metadata)
self._saveMetadata() self._saveMetadata()
#~~ analysis control
def pauseAnalysis(self):
self._metadataAnalyzer.pause()
def resumeAnalysis(self):
self._metadataAnalyzer.resume()
class MetadataAnalyzer: class MetadataAnalyzer:
def __init__(self, getPathCallback, loadedCallback): def __init__(self, getPathCallback, loadedCallback):
self._logger = logging.getLogger(__name__)
self._getPathCallback = getPathCallback self._getPathCallback = getPathCallback
self._loadedCallback = loadedCallback self._loadedCallback = loadedCallback
@ -218,7 +271,7 @@ class MetadataAnalyzer:
self._currentFile = None self._currentFile = None
self._currentProgress = None self._currentProgress = None
self._queue = Queue.Queue() self._queue = Queue.PriorityQueue()
self._gcode = None self._gcode = None
self._worker = threading.Thread(target=self._work) self._worker = threading.Thread(target=self._work)
@ -226,7 +279,12 @@ class MetadataAnalyzer:
self._worker.start() self._worker.start()
def addFileToQueue(self, filename): def addFileToQueue(self, filename):
self._queue.put(filename) self._logger.debug("Adding file %s to analysis queue (high priority)" % filename)
self._queue.put((0, filename))
def addFileToBacklog(self, filename):
self._logger.debug("Adding file %s to analysis backlog (low priority)" % filename)
self._queue.put((100, filename))
def working(self): def working(self):
return self.isActive() and not (self._queue.empty() and self._currentFile is None) return self.isActive() and not (self._queue.empty() and self._currentFile is None)
@ -235,11 +293,14 @@ class MetadataAnalyzer:
return self._active.is_set() return self._active.is_set()
def pause(self): def pause(self):
self._logger.debug("Pausing Gcode analyzer")
self._active.clear() self._active.clear()
if self._gcode is not None: if self._gcode is not None:
self._logger.debug("Aborting running analysis, will restart when Gcode analyzer is resumed")
self._gcode.abort() self._gcode.abort()
def resume(self): def resume(self):
self._logger.debug("Resuming Gcode analyzer")
self._active.set() self._active.set()
def _work(self): def _work(self):
@ -250,14 +311,17 @@ class MetadataAnalyzer:
if aborted is not None: if aborted is not None:
filename = aborted filename = aborted
aborted = None aborted = None
self._logger.debug("Got an aborted analysis job for file %s, processing this instead of first item in queue" % filename)
else: else:
filename = self._queue.get() (priority, filename) = self._queue.get()
self._logger.debug("Processing file %s from queue (priority %d)" % (filename, priority))
try: try:
self._analyzeGcode(filename) self._analyzeGcode(filename)
self._queue.task_done() self._queue.task_done()
except gcodeInterpreter.AnalysisAborted: except gcodeInterpreter.AnalysisAborted:
aborted = filename aborted = filename
self._logger.debug("Running analysis of file %s aborted" % filename)
def _analyzeGcode(self, filename): def _analyzeGcode(self, filename):
path = self._getPathCallback(filename) path = self._getPathCallback(filename)
@ -268,9 +332,11 @@ class MetadataAnalyzer:
self._currentProgress = 0 self._currentProgress = 0
try: try:
self._logger.debug("Starting analysis of file %s" % filename)
self._gcode = gcodeInterpreter.gcode() self._gcode = gcodeInterpreter.gcode()
self._gcode.progressCallback = self._onParsingProgress self._gcode.progressCallback = self._onParsingProgress
self._gcode.load(path) self._gcode.load(path)
self._logger.debug("Analysis of file %s finished, notifying callback" % filename)
self._loadedCallback(self._currentFile, self._gcode) self._loadedCallback(self._currentFile, self._gcode)
finally: finally:
self._gcode = None self._gcode = None

View File

@ -10,7 +10,6 @@ import os
import octoprint.util.comm as comm import octoprint.util.comm as comm
import octoprint.util as util import octoprint.util as util
from octoprint.util import gcodeInterpreter
from octoprint.settings import settings from octoprint.settings import settings
@ -59,7 +58,6 @@ class Printer():
self._printTimeLeft = None self._printTimeLeft = None
# gcode handling # gcode handling
self._gcode = None
self._gcodeList = None self._gcodeList = None
self._filename = None self._filename = None
self._gcodeLoader = None self._gcodeLoader = None
@ -168,7 +166,7 @@ class Printer():
if (self._comm is not None and self._comm.isPrinting()) or (self._gcodeLoader is not None): if (self._comm is not None and self._comm.isPrinting()) or (self._gcodeLoader is not None):
return return
self._setJobData(None, None, None) self._setJobData(None, None)
self._gcodeLoader = GcodeLoader(file, self._onGcodeLoadingProgress, self._onGcodeLoaded) self._gcodeLoader = GcodeLoader(file, self._onGcodeLoadingProgress, self._onGcodeLoaded)
self._gcodeLoader.start() self._gcodeLoader.start()
@ -285,31 +283,28 @@ class Printer():
self._stateMonitor.addTemperature({"currentTime": currentTimeUtc, "temp": self._temp, "bedTemp": self._bedTemp, "targetTemp": self._targetTemp, "targetBedTemp": self._targetBedTemp}) self._stateMonitor.addTemperature({"currentTime": currentTimeUtc, "temp": self._temp, "bedTemp": self._bedTemp, "targetTemp": self._targetTemp, "targetBedTemp": self._targetBedTemp})
def _setJobData(self, filename, gcode, gcodeList): def _setJobData(self, filename, gcodeList):
self._filename = filename self._filename = filename
self._gcode = gcode
self._gcodeList = gcodeList self._gcodeList = gcodeList
lines = None lines = None
if self._gcodeList: if self._gcodeList:
lines = len(self._gcodeList) lines = len(self._gcodeList)
formattedPrintTimeEstimation = None
formattedFilament = None
if self._gcode:
if self._gcode.totalMoveTimeMinute:
formattedPrintTimeEstimation = util.getFormattedTimeDelta(datetime.timedelta(minutes=self._gcode.totalMoveTimeMinute))
if self._gcode.extrusionAmount:
formattedFilament = "%.2fm" % (self._gcode.extrusionAmount / 1000)
elif not settings().getBoolean("feature", "analyzeGcode"):
formattedPrintTimeEstimation = "unknown"
formattedFilament = "unknown"
formattedFilename = None formattedFilename = None
estimatedPrintTime = None
filament = None
if self._filename: if self._filename:
formattedFilename = os.path.basename(self._filename) formattedFilename = os.path.basename(self._filename)
self._stateMonitor.setJobData({"filename": formattedFilename, "lines": lines, "estimatedPrintTime": formattedPrintTimeEstimation, "filament": formattedFilament}) fileData = self._gcodeManager.getFileData(filename)
if fileData is not None and "gcodeAnalysis" in fileData.keys():
if "estimatedPrintTime" in fileData["gcodeAnalysis"].keys():
estimatedPrintTime = fileData["gcodeAnalysis"]["estimatedPrintTime"]
if "filament" in fileData["gcodeAnalysis"].keys():
filament = fileData["gcodeAnalysis"]["filament"]
self._stateMonitor.setJobData({"filename": formattedFilename, "lines": lines, "estimatedPrintTime": estimatedPrintTime, "filament": filament})
def _sendInitialStateUpdate(self, callback): def _sendInitialStateUpdate(self, callback):
try: try:
@ -353,18 +348,22 @@ class Printer():
""" """
oldState = self._state oldState = self._state
# # forward relevant state changes to timelapse
if self._timelapse is not None: if self._timelapse is not None:
if oldState == self._comm.STATE_PRINTING and state != self._comm.STATE_PAUSED: if oldState == self._comm.STATE_PRINTING and state != self._comm.STATE_PAUSED:
self._timelapse.onPrintjobStopped() self._timelapse.onPrintjobStopped()
elif state == self._comm.STATE_PRINTING and oldState != self._comm.STATE_PAUSED: elif state == self._comm.STATE_PRINTING and oldState != self._comm.STATE_PAUSED:
self._timelapse.onPrintjobStarted(self._filename) self._timelapse.onPrintjobStarted(self._filename)
# forward relevant state changes to gcode manager
if oldState == self._comm.STATE_PRINTING: if oldState == self._comm.STATE_PRINTING:
if state == self._comm.STATE_OPERATIONAL: if state == self._comm.STATE_OPERATIONAL:
self._gcodeManager.printSucceeded(self._filename) self._gcodeManager.printSucceeded(self._filename)
elif state == self._comm.STATE_CLOSED or state == self._comm.STATE_ERROR or state == self._comm.STATE_CLOSED_WITH_ERROR: elif state == self._comm.STATE_CLOSED or state == self._comm.STATE_ERROR or state == self._comm.STATE_CLOSED_WITH_ERROR:
self._gcodeManager.printFailed(self._filename) self._gcodeManager.printFailed(self._filename)
self._gcodeManager.resumeAnalysis() # do not analyse gcode while printing
elif state == self._comm.STATE_PRINTING:
self._gcodeManager.pauseAnalysis() # printing done, put those cpu cycles to good use
self._setState(state) self._setState(state)
@ -408,12 +407,8 @@ class Printer():
self._stateMonitor.setGcodeData({"filename": formattedFilename, "progress": progress, "mode": mode}) self._stateMonitor.setGcodeData({"filename": formattedFilename, "progress": progress, "mode": mode})
def _onGcodeLoaded(self, filename, gcode, gcodeList): def _onGcodeLoaded(self, filename, gcodeList):
formattedFilename = None self._setJobData(filename, gcodeList)
if filename is not None:
formattedFilename = os.path.basename(filename)
self._setJobData(formattedFilename, gcode, gcodeList)
self._setCurrentZ(None) self._setCurrentZ(None)
self._setProgressData(None, None, None) self._setProgressData(None, None, None)
self._gcodeLoader = None self._gcodeLoader = None
@ -480,7 +475,6 @@ class GcodeLoader(threading.Thread):
self._loadedCallback = loadedCallback self._loadedCallback = loadedCallback
self._filename = filename self._filename = filename
self._gcode = None
self._gcodeList = None self._gcodeList = None
def run(self): def run(self):
@ -504,12 +498,7 @@ class GcodeLoader(threading.Thread):
self._onLoadingProgress(float(file.tell()) / float(filesize)) self._onLoadingProgress(float(file.tell()) / float(filesize))
self._gcodeList = gcodeList self._gcodeList = gcodeList
if settings().getBoolean("feature", "analyzeGcode"): self._loadedCallback(self._filename, self._gcodeList)
self._gcode = gcodeInterpreter.gcode()
self._gcode.progressCallback = self._onParsingProgress
self._gcode.loadList(self._gcodeList)
self._loadedCallback(self._filename, self._gcode, self._gcodeList)
def _onLoadingProgress(self, progress): def _onLoadingProgress(self, progress):
self._progressCallback(self._filename, progress, "loading") self._progressCallback(self._filename, progress, "loading")
@ -517,22 +506,6 @@ class GcodeLoader(threading.Thread):
def _onParsingProgress(self, progress): def _onParsingProgress(self, progress):
self._progressCallback(self._filename, progress, "parsing") self._progressCallback(self._filename, progress, "parsing")
class PrinterCallback(object):
def sendCurrentData(self, data):
pass
def sendHistoryData(self, data):
pass
def addTemperature(self, data):
pass
def addLog(self, data):
pass
def addMessage(self, data):
pass
class StateMonitor(object): class StateMonitor(object):
def __init__(self, ratelimit, updateCallback, addTemperatureCallback, addLogCallback, addMessageCallback): def __init__(self, ratelimit, updateCallback, addTemperatureCallback, addLogCallback, addMessageCallback):
self._ratelimit = ratelimit self._ratelimit = ratelimit

View File

@ -8,8 +8,9 @@ import tornadio2
import os import os
import threading import threading
import logging, logging.config
from octoprint.printer import Printer, getConnectionOptions, PrinterCallback from octoprint.printer import Printer, getConnectionOptions
from octoprint.settings import settings from octoprint.settings import settings
import octoprint.timelapse as timelapse import octoprint.timelapse as timelapse
import octoprint.gcodefiles as gcodefiles import octoprint.gcodefiles as gcodefiles
@ -29,16 +30,17 @@ def index():
return render_template( return render_template(
"index.html", "index.html",
webcamStream=settings().get("webcam", "stream"), webcamStream=settings().get("webcam", "stream"),
enableTimelapse=(settings().get("webcam", "snapshot") is not None and settings().get("webcam", "ffmpeg") is not None), enableTimelapse=(settings().get("webcam", "snapshot") is not None and settings().get("webcam", "ffmpeg") is not None)
enableEstimations=(settings().getBoolean("feature", "analyzeGcode"))
) )
#~~ Printer state #~~ Printer state
class PrinterStateConnection(tornadio2.SocketConnection, PrinterCallback): class PrinterStateConnection(tornadio2.SocketConnection):
def __init__(self, session, endpoint=None): def __init__(self, session, endpoint=None):
tornadio2.SocketConnection.__init__(self, session, endpoint) tornadio2.SocketConnection.__init__(self, session, endpoint)
self._logger = logging.getLogger(__name__)
self._temperatureBacklog = [] self._temperatureBacklog = []
self._temperatureBacklogMutex = threading.Lock() self._temperatureBacklogMutex = threading.Lock()
self._logBacklog = [] self._logBacklog = []
@ -47,12 +49,14 @@ class PrinterStateConnection(tornadio2.SocketConnection, PrinterCallback):
self._messageBacklogMutex = threading.Lock() self._messageBacklogMutex = threading.Lock()
def on_open(self, info): def on_open(self, info):
print("New connection from client") self._logger.info("New connection from client")
printer.registerCallback(self) printer.registerCallback(self)
gcodeManager.registerCallback(self)
def on_close(self): def on_close(self):
print("Closed client connection") self._logger.info("Closed client connection")
printer.unregisterCallback(self) printer.unregisterCallback(self)
gcodeManager.unregisterCallback(self)
def on_message(self, message): def on_message(self, message):
pass pass
@ -81,6 +85,9 @@ class PrinterStateConnection(tornadio2.SocketConnection, PrinterCallback):
def sendHistoryData(self, data): def sendHistoryData(self, data):
self.emit("history", data) self.emit("history", data)
def sendUpdateTrigger(self, type):
self.emit("updateTrigger", type)
def addLog(self, data): def addLog(self, data):
with self._logBacklogMutex: with self._logBacklogMutex:
self._logBacklog.append(data) self._logBacklog.append(data)
@ -334,7 +341,7 @@ def run(host = "0.0.0.0", port = 5000, debug = False):
from tornado.ioloop import IOLoop from tornado.ioloop import IOLoop
from tornado.web import Application, FallbackHandler from tornado.web import Application, FallbackHandler
print "Listening on http://%s:%d" % (host, port) logging.getLogger(__name__).info("Listening on http://%s:%d" % (host, port))
app.debug = debug app.debug = debug
router = tornadio2.TornadioRouter(PrinterStateConnection) router = tornadio2.TornadioRouter(PrinterStateConnection)
@ -345,6 +352,34 @@ def run(host = "0.0.0.0", port = 5000, debug = False):
server.listen(port, address=host) server.listen(port, address=host)
IOLoop.instance().start() IOLoop.instance().start()
def initLogging():
config = {
"version": 1,
"formatters": {
"simple": {
"format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
}
},
"handlers": {
"console": {
"class": "logging.StreamHandler",
"level": "DEBUG",
"formatter": "simple",
"stream": "ext://sys.stdout"
}
},
"loggers": {
"octoprint.gcodefiles": {
"level": "DEBUG"
}
},
"root": {
"level": "INFO",
"handlers": ["console"]
}
}
logging.config.dictConfig(config)
def main(): def main():
from optparse import OptionParser from optparse import OptionParser
@ -360,6 +395,7 @@ def main():
help="Specify the port on which to bind the server, defaults to %s if not set" % (defaultPort)) help="Specify the port on which to bind the server, defaults to %s if not set" % (defaultPort))
(options, args) = parser.parse_args() (options, args) = parser.parse_args()
initLogging()
run(host=options.host, port=options.port, debug=options.debug) run(host=options.host, port=options.port, debug=options.debug)
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -39,7 +39,6 @@ old_default_settings = {
"timelapse_tmp": None "timelapse_tmp": None
}, },
"feature": { "feature": {
"analyzeGcode": True
}, },
} }

View File

@ -84,7 +84,6 @@ table th.gcode_files_action, table td.gcode_files_action {
left: 0; left: 0;
width: 100%; width: 100%;
height: 100%; height: 100%;
display: block;
z-index: 10000; z-index: 10000;
display: none; display: none;
} }

View File

@ -811,7 +811,7 @@ function WebcamViewModel() {
} }
} }
function DataUpdater(connectionViewModel, printerStateViewModel, temperatureViewModel, controlsViewModel, speedViewModel, terminalViewModel, webcamViewModel) { function DataUpdater(connectionViewModel, printerStateViewModel, temperatureViewModel, controlsViewModel, speedViewModel, terminalViewModel, gcodeFilesViewModel, webcamViewModel) {
var self = this; var self = this;
self.connectionViewModel = connectionViewModel; self.connectionViewModel = connectionViewModel;
@ -820,6 +820,7 @@ function DataUpdater(connectionViewModel, printerStateViewModel, temperatureView
self.controlsViewModel = controlsViewModel; self.controlsViewModel = controlsViewModel;
self.terminalViewModel = terminalViewModel; self.terminalViewModel = terminalViewModel;
self.speedViewModel = speedViewModel; self.speedViewModel = speedViewModel;
self.gcodeFilesViewModel = gcodeFilesViewModel;
self.webcamViewModel = webcamViewModel; self.webcamViewModel = webcamViewModel;
self._socket = io.connect(); self._socket = io.connect();
@ -860,6 +861,11 @@ function DataUpdater(connectionViewModel, printerStateViewModel, temperatureView
self.terminalViewModel.fromCurrentData(data); self.terminalViewModel.fromCurrentData(data);
self.webcamViewModel.fromCurrentData(data); self.webcamViewModel.fromCurrentData(data);
}) })
self._socket.on("updateTrigger", function(type) {
if (type == "gcodeFiles") {
gcodeFilesViewModel.requestData();
}
})
self.reconnect = function() { self.reconnect = function() {
self._socket.socket.connect(); self._socket.socket.connect();
@ -877,7 +883,16 @@ $(function() {
var terminalViewModel = new TerminalViewModel(); var terminalViewModel = new TerminalViewModel();
var gcodeFilesViewModel = new GcodeFilesViewModel(); var gcodeFilesViewModel = new GcodeFilesViewModel();
var webcamViewModel = new WebcamViewModel(); var webcamViewModel = new WebcamViewModel();
var dataUpdater = new DataUpdater(connectionViewModel, printerStateViewModel, temperatureViewModel, controlsViewModel, speedViewModel, terminalViewModel, webcamViewModel);
var dataUpdater = new DataUpdater(
connectionViewModel,
printerStateViewModel,
temperatureViewModel,
controlsViewModel,
speedViewModel,
terminalViewModel,
gcodeFilesViewModel,
webcamViewModel);
//~~ Print job control //~~ Print job control
@ -983,6 +998,23 @@ $(function() {
//~~ Offline overlay //~~ Offline overlay
$("#offline_overlay_reconnect").click(function() {dataUpdater.reconnect()}); $("#offline_overlay_reconnect").click(function() {dataUpdater.reconnect()});
//~~ Alert
/*
function displayAlert(text, timeout, type) {
var placeholder = $("#alert_placeholder");
var alertType = "";
if (type == "success" || type == "error" || type == "info") {
alertType = " alert-" + type;
}
placeholder.append($("<div id='activeAlert' class='alert " + alertType + " fade in' data-alert='alert'><p>" + text + "</p></div>"));
placeholder.fadeIn();
$("#activeAlert").delay(timeout).fadeOut("slow", function() {$(this).remove(); $("#alert_placeholder").hide();});
}
*/
//~~ knockout.js bindings //~~ knockout.js bindings
ko.bindingHandlers.popover = { ko.bindingHandlers.popover = {

View File

@ -54,10 +54,8 @@
<div class="accordion-inner"> <div class="accordion-inner">
Machine State: <strong data-bind="text: stateString"></strong><br> Machine State: <strong data-bind="text: stateString"></strong><br>
File: <strong data-bind="text: filename"></strong><br> File: <strong data-bind="text: filename"></strong><br>
{% if enableEstimations %} Filament: <strong data-bind="text: filament"></strong><br>
Filament: <strong data-bind="text: filament"></strong><br> Estimated Print Time: <strong data-bind="text: estimatedPrintTime"></strong><br>
Estimated Print Time: <strong data-bind="text: estimatedPrintTime"></strong><br>
{% endif %}
Line: <strong data-bind="text: lineString"></strong><br> Line: <strong data-bind="text: lineString"></strong><br>
Height: <strong data-bind="text: currentHeight"></strong><br> Height: <strong data-bind="text: currentHeight"></strong><br>
Print Time: <strong data-bind="text: printTime"></strong><br> Print Time: <strong data-bind="text: printTime"></strong><br>