diff --git a/README.md b/README.md index fac0732..b12875f 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,20 @@ Printrun consists of printcore, pronsole and pronterface, and a small collection * pronsole.py is an interactive command-line host software with tabcompletion goodness * pronterface.py is a graphical host software with the same functionality as pronsole +# Modifications by Beardface (Webinterface) + +## Webinterface Dependencies + +Cherrypy is required for the web interface. In my branch it is available in the libs folder, install by opening a +command prompt there and running python setup.py install. You can also download and install from cherrypy. + +## Webinterface Configuration + * The Web interface port / ip is configurable in http.config + * The Default User / Password can be set in auth.config + +## Webinterface Styling + * css/style.css can be modified to change the style of the Web Interface. + # INSTALLING DEPENDENCIES ## Windows diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/auth.config b/auth.config new file mode 100644 index 0000000..17a02cf --- /dev/null +++ b/auth.config @@ -0,0 +1,3 @@ +[user] +user = admin +pass = password \ No newline at end of file diff --git a/css/style.css b/css/style.css new file mode 100644 index 0000000..427f6c3 --- /dev/null +++ b/css/style.css @@ -0,0 +1,206 @@ +#title +{ + text-align:center; + color:red; +} + +#mainmenu +{ +margin: 0; +padding: 0 0 20px 10px; +border-bottom: 1px solid #000; +} +#mainmenu ul, #mainmenu li +{ +margin: 0; +padding: 0; +display: inline; +list-style-type: none; +} + +#mainmenu a:link, #mainmenu a:visited +{ +float: left; +line-height: 14px; +font-weight: bold; +margin: 0 10px 4px 10px; +text-decoration: none; +color: #999; +} + +#mainmenu a:link#current, #mainmenu a:visited#current, #mainmenu a:hover +{ +border-bottom: 4px solid #000; +padding-bottom: 2px; +background: transparent; +color: #000; +} + +#mainmenu a:hover { color: #000; } + +#content{ +padding-top: 25px; +} +#controls{ + float:left; + padding:0 0 1em 0; + overflow:hidden; + width:71%; /* right column content width */ + left:102%; /* 100% plus left column left padding */ +} +#control_xy{ + display:inline; +} + +#control_z{ + display:inline; + position:absolute; +} + +#gui{ + float:left; + padding:0 0 1em 0; + overflow:hidden; + width:21%; /* left column content width (column width minus left and right padding) */ + left:6%; /* (right column left and right padding) plus (left column left padding) */ + +} +#controls +{ + width:21%; /* Width of left column content (column width minus padding on either side) */ + left:31%; /* width of (right column) plus (center column left and right padding) plus (left column left padding) */ + +} + +#controls ul +{ +list-style: none; +margin: 0px; +padding: 0px; +border: none; +} + +#controls ul li +{ +margin: 0px; +padding: 0px; +} + +#controls ul li a +{ +font-size: 80%; +display: block; +border-bottom: 1px dashed #C39C4E; +padding: 5px 0px 2px 4px; +text-decoration: none; +color: #666666; +width:160px; +} + +#controls ul li a:hover, #controls ul li a:focus +{ +color: #000000; +background-color: #eeeeee; +} + +#settings +{ +margin: 0px; +padding-top: 50px; +border: none; +} + +#settings table +{ + font-family: verdana,arial,sans-serif; + font-size:11px; + color:#333333; + border-width: 1px; + border-color: #999999; + border-collapse: collapse; +} +#settings table th { + background-color:#c3dde0; + border-width: 1px; + padding: 8px; + border-style: solid; + border-color: #a9c6c9; +} +#settings table tr { + background-color:#d4e3e5; +} +#settings table td { + border-width: 1px; + padding: 8px; + border-style: solid; + border-color: #a9c6c9; +} + +#status{ + +} + +#console{ + +} + +#file{ + position:relative; + float:left; + width:100%; + height:20px; /* Height of the footer */ + background:#eee; +} + +#logframe{ + +} + +#temp{ +} +#tempmenu +{ +padding: 0 0 10px 10px; +position: relative; +float: left; +width: 100%; +} +#tempmenu ul, #tempmenu li +{ +margin: 0; +display: inline; +list-style-type: none; +} + +#tempmenu b +{ +padding-top: 4px; +float: left; +line-height: 14px; +font-weight: bold; +color: #888; +margin: 0 10px 4px 10px; +text-decoration: none; +color: #999; +} + +#tempmenu a:link, #tempmenu a:visited +{ +float: left; +border-bottom: 1px solid #000; +line-height: 14px; +font-weight: bold; +margin: 0 10px 4px 10px; +text-decoration: none; +color: #999; +} + +#tempmenu a:link#tempmenu, #tempmenu a:visited#current, #tempmenu a:hover +{ +border-bottom: 2px solid #000; +padding-bottom: 2px; +background: transparent; +color: #000; +} + +#tempmenu a:hover { color: #000; } \ No newline at end of file diff --git a/http.config b/http.config new file mode 100644 index 0000000..b5093db --- /dev/null +++ b/http.config @@ -0,0 +1,4 @@ +[global] +server.socket_host: "localhost" +server.socket_port: 8080 + diff --git a/php/parser.php b/php/parser.php new file mode 100644 index 0000000..86ac16f --- /dev/null +++ b/php/parser.php @@ -0,0 +1,35 @@ +state . "
"; + echo "Hotend: " . round($xml->hotend, 0) . "°c
"; + echo "Bed: " . round($xml->bed, 0) . "°c
"; + if ($xml->progress != "NA") + { + echo "Progress: " . $xml->progress . "%"; + } +} +catch(Exception $e) +{ + echo "ERROR:\n" . $e->getMessage(). " (severity " . $e->getCode() . ")"; +} +?> \ No newline at end of file diff --git a/pronterface.py b/pronterface.py index 889bcc6..7b35733 100755 --- a/pronterface.py +++ b/pronterface.py @@ -49,11 +49,20 @@ if os.name=="nt": pass + from xybuttons import XYButtons from zbuttons import ZButtons from graph import Graph import pronsole +webavail = True +try : + import cherrypy, webinterface + from threading import Thread +except: + print _("CherryPy is not installed. Web Interface Disabled.") + webavail = False + def dosify(name): return os.path.split(name)[1].split(".")[0][:8]+".g" @@ -156,6 +165,10 @@ class PronterWindow(wx.Frame,pronsole.pronsole): self.cur_button=None self.hsetpoint=0.0 self.bsetpoint=0.0 + if webavail: + self.webInterface=webinterface.WebInterface(self) + self.webThread = Thread(target=webinterface.StartWebInterfaceThread, args=(self.webInterface, )) + self.webThread.start() def startcb(self): self.starttime=time.time() @@ -284,6 +297,8 @@ class PronterWindow(wx.Frame,pronsole.pronsole): print _("You cannot set negative temperatures. To turn the hotend off entirely, set its temperature to 0.") except Exception,x: print _("You must enter a temperature. (%s)" % (repr(x),)) + if webavail: + self.webInterface.AddLog("You must enter a temperature. (%s)" % (repr(x),)) def do_bedtemp(self,l=""): try: @@ -317,10 +332,16 @@ class PronterWindow(wx.Frame,pronsole.pronsole): wx.CallAfter(self.btemp.Refresh) else: print _("Printer is not online.") + if webavail: + self.webInterface.AddLog("Printer is not online.") else: print _("You cannot set negative temperatures. To turn the bed off entirely, set its temperature to 0.") + if webavail: + self.webInterface.AddLog("You cannot set negative temperatures. To turn the bed off entirely, set its temperature to 0.") except: print _("You must enter a temperature.") + if webavail: + self.webInterface.AddLog("You must enter a temperature.") def end_macro(self): pronsole.pronsole.end_macro(self) @@ -340,6 +361,8 @@ class PronterWindow(wx.Frame,pronsole.pronsole): self.delete_macro(macro_name) return print _("Cancelled.") + if webavail: + self.webInterface.AddLog("Cancelled.") return self.cur_macro_name = macro_name self.cur_macro_def = definition @@ -358,6 +381,8 @@ class PronterWindow(wx.Frame,pronsole.pronsole): self.capture_skip_newline = True return wx.CallAfter(self.logbox.AppendText,l) + if webavail: + self.webInterface.AppendLog(l) def scanserial(self): """scan for available ports. return a list of device names.""" @@ -379,6 +404,8 @@ class PronterWindow(wx.Frame,pronsole.pronsole): projectlayer.setframe(self,self.p).Show() else: print _("Printer is not online.") + if webavail: + self.webInterface.AddLog("Printer is not online.") def popmenu(self): self.menustrip = wx.MenuBar() @@ -453,6 +480,8 @@ class PronterWindow(wx.Frame,pronsole.pronsole): return elif len([c for c in macro if not c.isalnum() and c != "_"]): print _("Macro name may contain only alphanumeric symbols and underscores") + if webavail: + self.webInterface.AddLog("Macro name may contain only alphanumeric symbols and underscores") return else: old_def = "" @@ -906,6 +935,8 @@ class PronterWindow(wx.Frame,pronsole.pronsole): def help_button(self): print _('Defines custom button. Usage: button "title" [/c "colour"] command') + if webavail: + self.webInterface.AddLog('Defines custom button. Usage: button "title" [/c "colour"] command') def do_button(self,argstr): def nextarg(rest): @@ -928,6 +959,8 @@ class PronterWindow(wx.Frame,pronsole.pronsole): command=argstr.strip() if num<0 or num>=64: print _("Custom button number should be between 0 and 63") + if webavail: + self.webInterface.AddLog("Custom button number should be between 0 and 63") return while num >= len(self.custombuttons): self.custombuttons+=[None] @@ -1187,6 +1220,8 @@ class PronterWindow(wx.Frame,pronsole.pronsole): self.cur_button=None except: print _("event object missing") + if webavail: + self.webInterface.AddLog("event object missing") self.cur_button=None raise @@ -1203,6 +1238,8 @@ class PronterWindow(wx.Frame,pronsole.pronsole): except: pass self.Destroy() + if webavail: + webinterface.KillWebInterfaceThread() def do_monitor(self,l=""): if l.strip()=="": @@ -1215,11 +1252,17 @@ class PronterWindow(wx.Frame,pronsole.pronsole): wx.CallAfter(self.monitorbox.SetValue,self.monitor_interval>0) except: print _("Invalid period given.") + if webavail: + self.webInterface.AddLog("Invalid period given.") self.setmonitor(None) if self.monitor: print _("Monitoring printer.") + if webavail: + self.webInterface.AddLog("Monitoring printer.") else: print _("Done monitoring.") + if webavail: + self.webInterface.AddLog("Done monitoring.") def setmonitor(self,e): @@ -1236,6 +1279,8 @@ class PronterWindow(wx.Frame,pronsole.pronsole): if not len(command): return wx.CallAfter(self.logbox.AppendText,">>>"+command+"\n") + if webavail: + self.webInterface.AppendLog(">>>"+command+"\n") self.onecmd(str(command)) self.commandbox.SetSelection(0,len(command)) @@ -1399,6 +1444,8 @@ class PronterWindow(wx.Frame,pronsole.pronsole): import shlex param = self.expandcommand(self.settings.slicecommand).encode() print "Slicing: ",param + if webavail: + self.webInterface.AddLog("Slicing: "+param) pararray=[i.replace("$s",self.filename).replace("$o",self.filename.replace(".stl","_export.gcode").replace(".STL","_export.gcode")).encode() for i in shlex.split(param.replace("\\","\\\\").encode())] #print pararray self.skeinp=subprocess.Popen(pararray,stderr=subprocess.STDOUT,stdout=subprocess.PIPE) @@ -1410,6 +1457,8 @@ class PronterWindow(wx.Frame,pronsole.pronsole): self.stopsf=1 except: print _("Failed to execute slicing software: ") + if webavail: + self.webInterface.AddLog("Failed to execute slicing software: ") self.stopsf=1 traceback.print_exc(file=sys.stdout) @@ -1496,6 +1545,8 @@ class PronterWindow(wx.Frame,pronsole.pronsole): Xtot,Ytot,Ztot,Xmin,Xmax,Ymin,Ymax,Zmin,Zmax = pronsole.measurements(self.f) print pronsole.totalelength(self.f), _("mm of filament used in this print\n") print _("the print goes from %f mm to %f mm in X\nand is %f mm wide\n") % (Xmin, Xmax, Xtot) + if webavail: + self.webInterface.AddLog("the print goes from %f mm to %f mm in X\nand is %f mm wide\n") % (Xmin, Xmax, Xtot) print _("the print goes from %f mm to %f mm in Y\nand is %f mm wide\n") % (Ymin, Ymax, Ytot) print _("the print goes from %f mm to %f mm in Z\nand is %f mm high\n") % (Zmin, Zmax, Ztot) print _("Estimated duration (pessimistic): "), pronsole.estimate_duration(self.f) @@ -1762,6 +1813,8 @@ class macroed(wx.Dialog): self.callback(self.e.GetValue().split("\n")) def close(self,ev): self.Destroy() + if webavail: + webinterface.KillWebInterfaceThread() def unindent(self,text): self.indent_chars = text[:len(text)-len(text.lstrip())] if len(self.indent_chars)==0: diff --git a/webinterface.py b/webinterface.py new file mode 100644 index 0000000..66601f1 --- /dev/null +++ b/webinterface.py @@ -0,0 +1,372 @@ +#!/usr/bin/python +import cherrypy, pronterface, re, ConfigParser, threading +import os.path + +users = {} + +def PrintHeader(): + return '\n\nPronterface-Web\n\n\n\n' + +def PrintMenu(): + return '' + +def PrintFooter(): + return "" + +def ReloadPage(action): + return ""+action+"" + +def TReloadPage(action): + return action + +def clear_text(mypass): + return mypass + +gPronterPtr = 0 +gWeblog = "" +gLogRefresh =5 +class SettingsPage(object): + def __init__(self): + self.name="
Pronterface Settings
" + + def index(self): + pageText=PrintHeader()+self.name+PrintMenu() + pageText=pageText+"
\n" + pageText=pageText+"\n \n" + pageText=pageText+" \n \n" + pageText=pageText+" \n \n" + pageText=pageText+" \n \n" + pageText=pageText+" \n \n" + pageText=pageText+" \n " + pageText=pageText+PrintFooter() + return pageText + index.exposed = True + +class LogPage(object): + def __init__(self): + self.name="
Pronterface Console
" + + def index(self): + pageText="" + pageText+="
" + pageText+=gPronterPtr.status.GetStatusText() + pageText+="
" + pageText=pageText+"
"+gWeblog+"
" + pageText=pageText+"" + return pageText + index.exposed = True + +class ConsolePage(object): + def __init__(self): + self.name="
Pronterface Settings
" + + def index(self): + pageText=PrintHeader()+self.name+PrintMenu() + pageText+="
" + pageText+=PrintFooter() + return pageText + index.exposed = True + +class ConnectButton(object): + def index(self): + #handle connect push, then reload page + gPronterPtr.connect(0) + return ReloadPage("Connect...") + index.exposed = True + index._cp_config = {'tools.basic_auth.on': True, + 'tools.basic_auth.realm': 'My Print Server', + 'tools.basic_auth.users': users, + 'tools.basic_auth.encrypt': clear_text} + +class DisconnectButton(object): + def index(self): + #handle connect push, then reload page + gPronterPtr.disconnect(0) + return ReloadPage("Disconnect...") + index.exposed = True + index._cp_config = {'tools.basic_auth.on': True, + 'tools.basic_auth.realm': 'My Print Server', + 'tools.basic_auth.users': users, + 'tools.basic_auth.encrypt': clear_text} + +class ResetButton(object): + def index(self): + #handle connect push, then reload page + gPronterPtr.reset(0) + return ReloadPage("Reset...") + index.exposed = True + index._cp_config = {'tools.basic_auth.on': True, + 'tools.basic_auth.realm': 'My Print Server', + 'tools.basic_auth.users': users, + 'tools.basic_auth.encrypt': clear_text} + +class PrintButton(object): + def index(self): + #handle connect push, then reload page + gPronterPtr.printfile(0) + return ReloadPage("Print...") + index.exposed = True + index._cp_config = {'tools.basic_auth.on': True, + 'tools.basic_auth.realm': 'My Print Server', + 'tools.basic_auth.users': users, + 'tools.basic_auth.encrypt': clear_text} + +class PauseButton(object): + def index(self): + #handle connect push, then reload page + gPronterPtr.pause(0) + return ReloadPage("Pause...") + index.exposed = True + index._cp_config = {'tools.basic_auth.on': True, + 'tools.basic_auth.realm': 'My Print Server', + 'tools.basic_auth.users': users, + 'tools.basic_auth.encrypt': clear_text} + +class MoveButton(object): + def axis(self, *args): + if not args: + raise cherrypy.HTTPError(400, "No Move Command Provided!") + margs=list(args) + axis = margs.pop(0) + if(margs and axis == "x"): + distance = margs.pop(0) + gPronterPtr.onecmd('move X %s' % distance) + return ReloadPage("Moving X Axis " + str(distance)) + if(margs and axis == "y"): + distance = margs.pop(0) + gPronterPtr.onecmd('move Y %s' % distance) + return ReloadPage("Moving Y Axis " + str(distance)) + if(margs and axis == "z"): + distance = margs.pop(0) + gPronterPtr.onecmd('move Z %s' % distance) + return ReloadPage("Moving Z Axis " + str(distance)) + raise cherrypy.HTTPError(400, "Unmached Move Command!") + axis.exposed = True + axis._cp_config = {'tools.basic_auth.on': True, + 'tools.basic_auth.realm': 'My Print Server', + 'tools.basic_auth.users': users, + 'tools.basic_auth.encrypt': clear_text} + +class CustomButton(object): + def button(self, *args): + if not args: + raise cherrypy.HTTPError(400, "No Custom Command Provided!") + margs=list(args) + command = margs.pop(0) + if(command): + gPronterPtr.onecmd(command) + return ReloadPage(str(command)) + button.exposed = True + button._cp_config = {'tools.basic_auth.on': True, + 'tools.basic_auth.realm': 'My Print Server', + 'tools.basic_auth.users': users, + 'tools.basic_auth.encrypt': clear_text} + +class HomeButton(object): + def axis(self, *args): + if not args: + raise cherrypy.HTTPError(400, "No Axis Provided!") + margs=list(args) + taxis = margs.pop(0) + if(taxis == "x"): + gPronterPtr.onecmd('home X') + return ReloadPage("Home X") + if(taxis == "y"): + gPronterPtr.onecmd('home Y') + return ReloadPage("Home Y") + if(taxis == "z"): + gPronterPtr.onecmd('home Z') + return ReloadPage("Home Z") + if(taxis == "all"): + gPronterPtr.onecmd('home') + return ReloadPage("Home All") + + axis.exposed = True + axis._cp_config = {'tools.basic_auth.on': True, + 'tools.basic_auth.realm': 'My Print Server', + 'tools.basic_auth.users': users, + 'tools.basic_auth.encrypt': clear_text} + +class XMLstatus(object): + def index(self): + #handle connect push, then reload page + txt='\n\n' + state="Offline" + if gPronterPtr.statuscheck or gPronterPtr.p.online: + state="Idle" + if gPronterPtr.sdprinting: + state="SDPrinting" + if gPronterPtr.p.printing: + state="Printing" + if gPronterPtr.paused: + state="Paused" + + txt=txt+''+state+'\n' + txt=txt+''+str(gPronterPtr.filename)+'\n' + txt=txt+''+str(gPronterPtr.status.GetStatusText())+'\n' + try: + temp = str(float(filter(lambda x:x.startswith("T:"),gPronterPtr.tempreport.split())[0].split(":")[1])) + txt=txt+''+temp+'\n' + except: + txt=txt+'NA\n' + pass + try: + temp = str(float(filter(lambda x:x.startswith("B:"),gPronterPtr.tempreport.split())[0].split(":")[1])) + txt=txt+''+temp+'\n' + except: + txt=txt+'NA\n' + pass + if gPronterPtr.sdprinting: + fractioncomplete = float(gPronterPtr.percentdone/100.0) + txt+= _("%04.2f") % (gPronterPtr.percentdone,) + txt+="\n" + elif gPronterPtr.p.printing: + fractioncomplete = float(gPronterPtr.p.queueindex)/len(gPronterPtr.p.mainqueue) + txt+= _("%04.2f") % (100*float(gPronterPtr.p.queueindex)/len(gPronterPtr.p.mainqueue),) + txt+="\n" + else: + txt+="NA\n" + txt+='' + return txt + index.exposed = True + +class WebInterface(object): + + def __init__(self, pface): + config = ConfigParser.SafeConfigParser(allow_no_value=True) + config.read('auth.config') + users[config.get("user", "user")] = config.get("user", "pass") + self.pface = pface + global gPronterPtr + global gWeblog + self.name="
Pronterface Web-Interface
" + gWeblog = "" + gPronterPtr = self.pface + + settings = SettingsPage() + logpage = LogPage() + console = ConsolePage() + + #actions + connect = ConnectButton() + disconnect = DisconnectButton() + reset = ResetButton() + printbutton = PrintButton() + pausebutton = PrintButton() + status = XMLstatus() + home = HomeButton() + move = MoveButton() + custom =CustomButton() + + def index(self): + pageText=PrintHeader()+self.name+PrintMenu() + pageText+="
\n" + pageText+="
\n" + pageText+="
  • Connect
  • \n" + pageText+="
  • Disconnect
  • \n" + pageText+="
  • Reset
  • \n" + pageText+="
  • Print
  • \n" + pageText+="
  • Pause
  • \n" + + for i in gPronterPtr.cpbuttons: + pageText+="
  • "+i[0]+"
  • \n" + + #for i in gPronterPtr.custombuttons: + # print(str(i)); + + pageText+="
\n" + pageText+="
\n" + pageText+="
\n" + pageText+="
" + pageText+="" + pageText+='' + + pageText+='X Home' + pageText+='Y Home' + pageText+='All Home' + pageText+='Z Home' + pageText+='Y 100' + pageText+='Y 10' + pageText+='Y 1' + pageText+='Y .1' + pageText+='Y -.1' + pageText+='Y -1' + pageText+='Y -10' + pageText+='Y -100' + pageText+='X -100' + pageText+='X 100' + pageText+='X -10' + pageText+='X 10' + pageText+='X -1' + pageText+='X 1' + pageText+='X -.1' + pageText+='X .1' + + pageText+="" + pageText+="
\n" #endxy + pageText+="
" + pageText+="" + pageText+='' + pageText+='Z 10' + pageText+='Z 1' + pageText+='Z .1' + pageText+='Z -.1' + pageText+='Z -1' + pageText+='Z -10' + pageText+="" + #TODO Map Z Moves + pageText+="
\n" #endz + pageText+="
\n" #endgui + pageText+="
\n" #endcontent + pageText+="
\n" + + # Temp Control TBD + # pageText+="
" + # pageText+="
" + # pageText+="" + # pageText+="
" + # pageText+="
" + # pageText+="" + # pageText+="
" + # pageText+="
" + + pageText=pageText+"
File Loaded: "+str(gPronterPtr.filename)+"
" + pageText+="
" + pageText+=PrintFooter() + return pageText + + def AddLog(self, log): + global gWeblog + gWeblog=gWeblog+"
"+log + def AppendLog(self, log): + global gWeblog + gWeblog=re.sub("\n", "
", gWeblog)+log + index.exposed = True + +class WebInterfaceStub(object): + def index(self): + return "Web Interface Must be launched by running Pronterface!" + index.exposed = True + +def KillWebInterfaceThread(): + cherrypy.engine.exit() + +def StartWebInterfaceThread(webInterface): + current_dir = os.path.dirname(os.path.abspath(__file__)) + cherrypy.config.update({'engine.autoreload_on':False}) + cherrypy.config.update("http.config") + conf = {'/css/style.css': {'tools.staticfile.on': True, + 'tools.staticfile.filename': os.path.join(current_dir, 'css/style.css'), + }, + '/images/control_xy.png': {'tools.staticfile.on': True, + 'tools.staticfile.filename': os.path.join(current_dir, 'images/control_xy.png'), + }, + '/images/control_z.png': {'tools.staticfile.on': True, + 'tools.staticfile.filename': os.path.join(current_dir, 'images/control_z.png'), + }} + cherrypy.config.update("http.config") + cherrypy.quickstart(webInterface, '/', config=conf) + +if __name__ == '__main__': + cherrypy.config.update("http.config") + cherrypy.quickstart(WebInterfaceStub()) \ No newline at end of file
settingvalue
Build Dimenstions"+str(gPronterPtr.settings.build_dimensions)+"
Last Bed Temp"+str(gPronterPtr.settings.last_bed_temperature)+"
Last File Path"+gPronterPtr.settings.last_file_path+"
Last Temperature"+str(gPronterPtr.settings.last_temperature)+"
Preview Extrusion Width"+str(gPronterPtr.settings.preview_extrusion_width)+"
Filename"+str(gPronterPtr.filename)+"