Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/kliment/Printrun.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--README.md23
-rw-r--r--php/parser.php35
-rw-r--r--printrun/webinterface.py397
-rwxr-xr-xpronserve.py403
-rwxr-xr-xpronsole.py378
-rwxr-xr-xpronterface.py52
-rw-r--r--server/__init__.py0
-rw-r--r--server/basic_auth.py93
-rw-r--r--server/static/.DS_Storebin0 -> 6148 bytes
-rw-r--r--server/static/css/index.css34
-rw-r--r--server/static/css/inspect.css54
-rw-r--r--server/static/img/.DS_Storebin0 -> 6148 bytes
-rw-r--r--server/static/img/background.jpgbin0 -> 454961 bytes
-rw-r--r--server/static/js/inspect.js145
-rw-r--r--server/templates/index.html24
-rw-r--r--server/templates/inspect.html57
-rw-r--r--testfiles/quick-test.gcode101
-rw-r--r--web/css/style.css206
-rw-r--r--web/js/asyncCommand.js79
20 files changed, 1113 insertions, 969 deletions
diff --git a/.gitignore b/.gitignore
index e8c9a13..6ade96c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,4 @@
.pronsolerc
*.swp
*.bak
+uploads
diff --git a/README.md b/README.md
index 9a1837d..1912a53 100644
--- a/README.md
+++ b/README.md
@@ -3,7 +3,6 @@ Printrun consists of printcore, pronsole and pronterface, and a small collection
* printcore.py is a library that makes writing reprap hosts easy
* 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
- * webinterface.py is a browser-usable remote control function for Pronterface
# GETTING PRINTRUN
@@ -51,23 +50,19 @@ The command box recognizes all pronsole commands, but has no tabcompletion.
If you want to load stl files, you need to install a slicing program such as Slic3r and add its path to the settings.
See the Slic3r readme for more details on integration.
-# Using the browser interface
-To run the web interface, install Cherrypy and run Pronterface as described above.
-The www server will start on the port/address you have chosen.
+# USING PRONSERVE
-## Webinterface Dependencies
+Pronserve runs a server for remote controlling your 3D printer over your network. To use pronserve you need:
-Cherrypy is required for the web interface. Download and install it by opening a
-command prompt in its directory and running python setup.py install.
+ * python (ideally 2.6.x or 2.7.x),
+ * pyserial (or python-serial on ubuntu/debian) and
+ * tornado
+ * D1plo1d's py-mdns fork (https://github.com/D1plo1d/py-mdns)
+ * pybonjour
+ * bonjour for windows (Windows ONLY)
-## 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.
-
+When you're done setting up Printrun, you can start `pronserve.py` in the directory you unpacked it. Once the server starts you can verify it's working by going to http://localhost:8888 in your web browser.
# USING PRONSOLE
diff --git a/php/parser.php b/php/parser.php
deleted file mode 100644
index 86ac16f..0000000
--- a/php/parser.php
+++ /dev/null
@@ -1,35 +0,0 @@
-<?php
-$pronterfaceIP = "192.168.0.102:8080"; //Format: ip:port
-
-$curl = curl_init();
-curl_setopt($curl, CURLINFO_HEADER_OUT, true);
-curl_setopt($curl, CURLOPT_HEADER, false);
-curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
-curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 1);
-curl_setopt($curl,CURLOPT_TIMEOUT, 1);
-curl_setopt($curl, CURLOPT_URL, "http://" . $pronterfaceIP . "/status/");
-$data = curl_exec($curl);
-
-if (curl_errno($curl) || empty($data))
-{
- die("Printer offline");
-}
-
-curl_close($curl);
-
-try
-{
- $xml = new SimpleXMLElement($data);
- echo "State: " . $xml->state . "<br />";
- echo "Hotend: " . round($xml->hotend, 0) . "&deg;c<br />";
- echo "Bed: " . round($xml->bed, 0) . "&deg;c<br />";
- 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/printrun/webinterface.py b/printrun/webinterface.py
deleted file mode 100644
index bb9690b..0000000
--- a/printrun/webinterface.py
+++ /dev/null
@@ -1,397 +0,0 @@
-#!/usr/bin/python
-# This file is part of the Printrun suite.
-#
-# Printrun is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# Printrun is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with Printrun. If not, see <http://www.gnu.org/licenses/>.
-
-import pronterface
-import cherrypy, re, ConfigParser, threading, sys
-import os.path
-
-from printrun.printrun_utils import configfile, imagefile, sharedfile
-
-users = {}
-
-def PrintHeader():
- return '<html>\n<head>\n<title>Pronterface-Web</title>\n<link rel = "stylesheet" type = "text/css" href = "/css/style.css" type = "text/css"></link>\n<script src="/js/asyncCommand.js"></script>\n</head>\n<body>\n'
-
-def PrintMenu():
- return '<div id = "mainmenu"><ul><li><a href = "/">home</a></li><li><a href = "/settings">settings</a></li><li><a href = "/console">console</a></li><li><a href = "/status">status (XML)</a></li></ul></div>'
-
-def PrintFooter():
- return "</body></html>"
-
-def ReloadPage(action):
- return "<html><head><meta http-equiv='refresh' content='0;url=/'></head><body>"+action+"</body></html>"
-
-def TReloadPage(action):
- return action
-
-def clear_text(mypass):
- return mypass
-
-gPronterPtr = 0
-gWeblog = ""
-gLogRefresh =5
-class SettingsPage(object):
- def __init__(self):
- self.name = "<div id='title'>Pronterface Settings</div>"
-
- def index(self):
- pageText = PrintHeader()+self.name+PrintMenu()
- pageText = pageText+"<div id='settings'><table>\n<tr><th>setting</th><th>value</th>"
- pageText = pageText+"<tr>\n <td><b>Build Dimenstions</b></td><td>"+str(gPronterPtr.settings.build_dimensions)+"</td>\n</tr>"
- pageText = pageText+" <tr>\n <td><b>Last Bed Temp</b></td><td>"+str(gPronterPtr.settings.last_bed_temperature)+"</td>\n</tr>"
- pageText = pageText+" <tr>\n <td><b>Last File Path</b></td><td>"+gPronterPtr.settings.last_file_path+"</td>\n</tr>"
- pageText = pageText+" <tr>\n <td><b>Last Temperature</b></td><td>"+str(gPronterPtr.settings.last_temperature)+"</td>\n</tr>"
- pageText = pageText+" <tr>\n <td><b>Preview Extrusion Width</b></td><td>"+str(gPronterPtr.settings.preview_extrusion_width)+"</td>\n</tr>"
- pageText = pageText+" <tr>\n <td><b>Filename</b></td><td>"+str(gPronterPtr.filename)+"</td></tr></div>"
- pageText = pageText+PrintFooter()
- return pageText
- index.exposed = True
-
-class LogPage(object):
- def __init__(self):
- self.name = "<div id='title'>Pronterface Console</div>"
-
- def index(self):
- pageText = "<html><head><meta http-equiv='refresh' content='"+str(gLogRefresh)+"'></head><body>"
- pageText+="<div id='status'>"
- pageText+=gPronterPtr.status.GetStatusText()
- pageText+="</div>"
- pageText = pageText+"<div id='console'>"+gWeblog+"</div>"
- pageText = pageText+"</body></html>"
- return pageText
- index.exposed = True
-
-class ConsolePage(object):
- def __init__(self):
- self.name = "<div id='title'>Pronterface Settings</div>"
-
- def index(self):
- pageText = PrintHeader()+self.name+PrintMenu()
- pageText+="<div id='logframe'><iframe src='/logpage' width='100%' height='100%'>iFraming Not Supported?? No log for you.</iframe></div>"
- 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='<?xml version = "1.0"?>\n<pronterface>\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>'+state+'</state>\n'
- txt = txt+'<file>'+str(gPronterPtr.filename)+'</file>\n'
- txt = txt+'<status>'+str(gPronterPtr.status.GetStatusText())+'</status>\n'
- try:
- temp = str(float(filter(lambda x:x.startswith("T:"), gPronterPtr.tempreport.split())[0].split(":")[1]))
- txt = txt+'<hotend>'+temp+'</hotend>\n'
- except:
- txt = txt+'<hotend>NA</hotend>\n'
- pass
- try:
- temp = str(float(filter(lambda x:x.startswith("B:"), gPronterPtr.tempreport.split())[0].split(":")[1]))
- txt = txt+'<bed>'+temp+'</bed>\n'
- except:
- txt = txt+'<bed>NA</bed>\n'
- pass
- if gPronterPtr.sdprinting:
- fractioncomplete = float(gPronterPtr.percentdone/100.0)
- txt+= _("<progress>%04.2f") % (gPronterPtr.percentdone,)
- txt+="</progress>\n"
- elif gPronterPtr.p.printing:
- fractioncomplete = float(gPronterPtr.p.queueindex)/len(gPronterPtr.p.mainqueue)
- txt+= _("<progress>%04.2f") % (100*float(gPronterPtr.p.queueindex)/len(gPronterPtr.p.mainqueue),)
- txt+="</progress>\n"
- else:
- txt+="<progress>NA</progress>\n"
- txt+='</pronterface>'
- return txt
- index.exposed = True
-
-class WebInterface(object):
-
- def __init__(self, pface):
- if (sys.version_info[1] > 6):
- # 'allow_no_value' wasn't added until 2.7
- config = ConfigParser.SafeConfigParser(allow_no_value = True)
- else:
- config = ConfigParser.SafeConfigParser()
- config.read(configfile(pface.web_auth_config or 'auth.config'))
- users[config.get("user", "user")] = config.get("user", "pass")
- self.pface = pface
- global gPronterPtr
- global gWeblog
- self.name = "<div id='title'>Pronterface Web-Interface</div>"
- 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+="<div id='content'>\n"
- pageText+="<div id='controls'>\n"
- pageText+="<ul><li><a class='command' href='/connect'>Connect</a></li>\n"
- pageText+="<li><a class='command' href='/disconnect'>Disconnect</a></li>\n"
- pageText+="<li><a class='command' href='/reset'>Reset</a></li>\n"
- pageText+="<li><a class='command' href='/printbutton'>Print</a></li>\n"
- pageText+="<li><a class='command' href='/pausebutton'>Pause</a></li>\n"
-
- for i in gPronterPtr.cpbuttons:
- pageText+="<li><a class='command' href='/custom/button/"+i.command+"'>"+i.label+"</a></li>\n"
-
- #for i in gPronterPtr.custombuttons:
- # print(str(i));
-
- pageText+="</ul>\n"
- pageText+="</div>\n"
- pageText+="<div id='gui'>\n"
- pageText+="<div id='control_xy'>"
- pageText+="<img src='/images/control_xy.png' usemap='#xymap'/>"
- pageText+='<map name = "xymap">'
-
- pageText+='<area shape = "rect" class="command" coords = "8, 5, 51, 48" href = "/home/axis/x" alt = "X Home" title = "X Home" />'
- pageText+='<area shape = "rect" class="command" coords = "195, 6, 236, 46" href = "/home/axis/y" alt = "Y Home" title = "Y Home" />'
- pageText+='<area shape = "rect" class="command" coords = "7, 192, 48, 232" href = "/home/axis/all" alt = "All Home" title = "All Home" />'
- pageText+='<area shape = "rect" class="command" coords = "194, 192, 235, 232" href = "/home/axis/z" alt = "Z Home" title = "Z Home" />'
- pageText+='<area shape = "rect" class="command" coords = "62, 7, 185, 34" href = "/move/axis/y/100" alt = "Y 100" title = "Y 100" />'
- pageText+='<area shape = "rect" class="command" coords = "68, 34, 175, 61" href = "/move/axis/y/10" alt = "Y 10" title = "Y 10" />'
- pageText+='<area shape = "rect" class="command" coords = "80, 60, 163, 84" href = "/move/axis/y/1" alt = "Y 1" title = "Y 1" />'
- pageText+='<area shape = "rect" class="command" coords = "106, 83, 138, 107" href = "/move/axis/y/.1" alt = "Y .1" title = "Y .1" />'
- pageText+='<area shape = "rect" class="command" coords = "110, 135, 142, 159" href = "/move/axis/y/-.1" alt = "Y -.1" title = "Y -.1" />'
- pageText+='<area shape = "rect" class="command" coords = "81, 157, 169, 181" href = "/move/axis/y/-1" alt = "Y -1" title = "Y -1" />'
- pageText+='<area shape = "rect" class="command" coords = "69, 180, 178, 206" href = "/move/axis/y/-10" alt = "Y -10" title = "Y -10" />'
- pageText+='<area shape = "rect" class="command" coords = "60, 205, 186, 231" href = "/move/axis/y/-100" alt = "Y -100" title = "Y -100" />'
- pageText+='<area shape = "rect" class="command" coords = "11, 53, 37, 179" href = "/move/axis/x/-100" alt = "X -100" title = "X -100" />'
- pageText+='<area shape = "rect" class="command" coords = "210, 59, 236, 185" href = "/move/axis/x/100" alt = "X 100" title = "X 100" />'
- pageText+='<area shape = "rect" class="command" coords = "38, 60, 64, 172" href = "/move/axis/x/-10" alt = "X -10" title = "X -10" />'
- pageText+='<area shape = "rect" class="command" coords = "185, 66, 211, 178" href = "/move/axis/x/10" alt = "X 10" title = "X 10" />'
- pageText+='<area shape = "rect" class="command" coords = "62, 84, 83, 157" href = "/move/axis/x/-1" alt = "X -1" title = "X -1" />'
- pageText+='<area shape = "rect" class="command" coords = "163, 87, 187, 160" href = "/move/axis/x/1" alt = "X 1" title = "X 1" />'
- pageText+='<area shape = "rect" class="command" coords = "82, 104, 110, 139" href = "/move/axis/x/-.1" alt = "X -.1" title = "X -.1" />'
- pageText+='<area shape = "rect" class="command" coords = "137, 105, 165, 140" href = "/move/axis/x/.1" alt = "X .1" title = "X .1" />'
-
- pageText+="</map>"
- pageText+="</div>\n" #endxy
- pageText+="<div id='control_z'>"
- pageText+="<img src='/images/control_z.png' usemap='#zmap'/>"
- pageText+='<map name = "zmap">'
- pageText+='<area shape = "rect" class="command" coords = "4, 35, 54, 64" href = "/move/axis/z/10" alt = "Z 10" title = "Z 10" />'
- pageText+='<area shape = "rect" class="command" coords = "4, 60, 54, 89" href = "/move/axis/z/1" alt = "Z 1" title = "Z 1" />'
- pageText+='<area shape = "rect" class="command" coords = "4, 87, 54, 116" href = "/move/axis/z/.1" alt = "Z .1" title = "Z .1" />'
- pageText+='<area shape = "rect" class="command" coords = "4, 121, 54, 150" href = "/move/axis/z/-.1" alt = "Z -.1" title = "Z -.1" />'
- pageText+='<area shape = "rect" class="command" coords = "4, 147, 54, 176" href = "/move/axis/z/-1" alt = "Z -1" title = "Z -1" />'
- pageText+='<area shape = "rect" class="command" coords = "4, 173, 54, 202" href = "/move/axis/z/-10" alt = "Z -10" title = "Z -10" />'
- pageText+="</map>"
- #TODO Map Z Moves
- pageText+="</div>\n" #endz
- pageText+="</div>\n" #endgui
- pageText+="</div>\n" #endcontent
- pageText+="</br>\n"
-
- # Temp Control TBD
- # pageText+="<div id='temp'>"
- # pageText+="<div id='tempmenu'>"
- # pageText+="<ul><li><b>Heater Temp:</b></li><li><a href='/off'>OFF</a></li><li><a href='/185'>185 (PLA)</a></li><li><a href='/240'>240 (ABS)</a></li></ul>"
- # pageText+="</div>"
- # pageText+="<div id='tempmenu'>"
- # pageText+="<ul><li><b>Bed Temp:</b></li><li><a href='/off'>OFF</a></li><li><a href='/185'>185 (PLA)</a></li><li><a href='/240'>240 (ABS)</a></li></ul>"
- # pageText+="</div>"
- # pageText+="</div>"
-
- pageText = pageText+"<div id='file'>File Loaded: <i>"+str(gPronterPtr.filename)+"</i></div>"
- pageText+="<div id='logframe'><iframe src='/logpage' width='100%' height='100%'>iFraming Not Supported?? No log for you.</iframe></div>"
- pageText+=PrintFooter()
- return pageText
-
- def AddLog(self, log):
- global gWeblog
- gWeblog = gWeblog+"</br>"+log
- def AppendLog(self, log):
- global gWeblog
- gWeblog = re.sub("\n", "</br>", gWeblog)+log
- index.exposed = True
-
-class WebInterfaceStub(object):
- def index(self):
- return "<b>Web Interface Must be launched by running Pronterface!</b>"
- 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(configfile(webInterface.pface.web_config or "http.config"))
- conf = {'/css/style.css': {'tools.staticfile.on': True,
- 'tools.staticfile.filename': sharedfile('web/css/style.css'),
- },
- '/js/asyncCommand.js': {'tools.staticfile.on': True,
- 'tools.staticfile.filename': sharedfile('web/js/asyncCommand.js'),
- },
- '/images/control_xy.png': {'tools.staticfile.on': True,
- 'tools.staticfile.filename': imagefile('control_xy.png'),
- },
- '/images/control_z.png': {'tools.staticfile.on': True,
- 'tools.staticfile.filename': imagefile('control_z.png'),
- }}
- cherrypy.config.update(configfile(webInterface.pface.web_config or "http.config"))
- cherrypy.quickstart(webInterface, '/', config = conf)
-
-if __name__ == '__main__':
- cherrypy.config.update(configfile("http.config"))
- cherrypy.quickstart(WebInterfaceStub())
diff --git a/pronserve.py b/pronserve.py
new file mode 100755
index 0000000..f8a5d10
--- /dev/null
+++ b/pronserve.py
@@ -0,0 +1,403 @@
+#!/usr/bin/env python
+
+import tornado.ioloop
+import tornado.web
+import tornado.websocket
+from tornado import gen
+import tornado.httpserver
+import time
+import base64
+import logging
+import logging.config
+import cmd, sys
+import glob, os, time, datetime
+import sys, subprocess
+import math, codecs
+from math import sqrt
+from gcoder import GCode
+import printcore
+from pprint import pprint
+import pronsole
+from server import basic_auth
+import random
+import textwrap
+import SocketServer
+import socket
+import mdns
+import uuid
+from operator import itemgetter, attrgetter
+from collections import deque
+
+log = logging.getLogger("root")
+__UPLOADS__ = "./uploads"
+
+# Authentication
+# -------------------------------------------------
+
+def authenticator(realm,handle,password):
+ """
+ This method is a sample authenticator.
+ It treats authentication as successful
+ if the handle and passwords are the same.
+ It returns a tuple of handle and user name
+ """
+ if handle == "admin" and password == "admin" :
+ return (handle,'Authorized User')
+ return None
+
+def user_extractor(user_data):
+ """
+ This method extracts the user handle from
+ the data structure returned by the authenticator
+ """
+ return user_data[0]
+
+def socket_auth(self):
+ user = self.get_argument("user", None)
+ password = self.get_argument("password", None)
+ return authenticator(None, user, password)
+
+interceptor = basic_auth.interceptor
+auth = basic_auth.authenticate('auth_realm', authenticator, user_extractor)
+#@interceptor(auth)
+
+
+# Routing
+# -------------------------------------------------
+
+class RootHandler(tornado.web.RequestHandler):
+ def get(self):
+ self.render("index.html")
+
+class PrintHandler(tornado.web.RequestHandler):
+ def put(self):
+ pronserve.do_print()
+ self.finish("ACK")
+
+class PauseHandler(tornado.web.RequestHandler):
+ def put(self):
+ pronserve.do_pause()
+ self.finish("ACK")
+
+class StopHandler(tornado.web.RequestHandler):
+ def put(self):
+ pronserve.do_stop()
+ self.finish("ACK")
+
+class JobsHandler(tornado.web.RequestHandler):
+ def post(self):
+ fileinfo = self.request.files['job'][0]
+ pronserve.jobs.add(fileinfo['filename'], fileinfo['body'])
+ self.finish("ACK")
+
+class JobHandler(tornado.web.RequestHandler):
+ def delete(self, job_id):
+ pronserve.jobs.remove(int(job_id))
+ self.finish("ACK")
+
+ def put(self, job_id):
+ args = {'position': int(self.get_argument("job[position]"))}
+ pronserve.jobs.update(int(job_id), args)
+ self.finish("ACK")
+
+
+class InspectHandler(tornado.web.RequestHandler):
+ def prepare(self):
+ auth(self, None)
+
+ def get(self):
+ self.render("inspect.html")
+
+#class EchoWebSocketHandler(tornado.web.RequestHandler):
+class ConstructSocketHandler(tornado.websocket.WebSocketHandler):
+
+ def on_sensor_changed(self):
+ for name in ['bed', 'extruder']:
+ self.send(
+ sensor_changed= {'name': name, 'value': pronserve.sensors[name]},
+ )
+
+ def on_uncaught_event(self, event_name, data):
+ listener = "on_%s"%event_name
+
+ if event_name[:4] == 'job_' and event_name != "job_progress_changed":
+ data = pronserve.jobs.sanitize(data)
+ self.send({event_name: data})
+
+ def _execute(self, transforms, *args, **kwargs):
+ if socket_auth(self):
+ super(ConstructSocketHandler, self)._execute(transforms, *args, **kwargs)
+ else:
+ self.stream.close();
+
+ def open(self):
+ pronserve.listeners.add(self)
+ self.write_message({'connected': {'jobs': pronserve.jobs.public_list()}})
+ print "WebSocket opened. %i sockets currently open." % len(pronserve.listeners)
+
+ def send(self, dict_args = {}, **kwargs):
+ args = dict(dict_args.items() + kwargs.items())
+ args['timestamp']= time.time()
+ self.write_message(args)
+
+ def on_message(self, msg):
+ print "message received: %s"%(msg)
+ # TODO: the read bit of repl!
+ # self.write_message("You said: " + msg)
+
+ def on_close(self):
+ pronserve.listeners.remove(self)
+ print "WebSocket closed. %i sockets currently open." % len(pronserve.listeners)
+
+dir = os.path.dirname(__file__)
+settings = dict(
+ template_path=os.path.join(dir, "server", "templates"),
+ static_path=os.path.join(dir, "server", "static"),
+ debug=True
+)
+
+application = tornado.web.Application([
+ (r"/", RootHandler),
+ (r"/inspect", InspectHandler),
+ (r"/socket", ConstructSocketHandler),
+ (r"/jobs", JobsHandler),
+ (r"/jobs/([0-9]*)", JobHandler),
+ (r"/jobs/print", PrintHandler),
+ (r"/jobs/pause", PauseHandler),
+ (r"/stop", StopHandler)
+], **settings)
+
+
+# Event Emitter
+# -------------------------------------------------
+
+class EventEmitter(object):
+ def __init__(self):
+ self.listeners = set()
+
+ def fire(self, event_name, content=None):
+ callback_name = "on_%s" % event_name
+ for listener in self.listeners:
+ if hasattr(listener, callback_name):
+ callback = getattr(listener, callback_name)
+ if content == None: callback()
+ else: callback(content)
+ elif hasattr(listener, "on_uncaught_event"):
+ listener.on_uncaught_event(event_name, content)
+ else:
+ continue
+
+
+# Pronserve: Server-specific functionality
+# -------------------------------------------------
+
+class Pronserve(pronsole.pronsole, EventEmitter):
+
+ def __init__(self):
+ pronsole.pronsole.__init__(self)
+ EventEmitter.__init__(self)
+ self.settings.sensor_names = {'T': 'extruder', 'B': 'bed'}
+ self.stdout = sys.stdout
+ self.ioloop = tornado.ioloop.IOLoop.instance()
+ self.settings.sensor_poll_rate = 1 # seconds
+ self.sensors = {'extruder': -1, 'bed': -1}
+ self.load_default_rc()
+ self.jobs = PrintJobQueue()
+ self.job_id_incr = 0
+ self.printing_jobs = False
+ self.current_job = None
+ self.previous_job_progress = 0
+ self.silent = True
+ services = ({'type': '_construct._tcp', 'port': 8888, 'domain': "local."})
+ self.mdns = mdns.publisher().save_group({'name': 'pronserve', 'services': services })
+ self.jobs.listeners.add(self)
+
+ def do_print(self):
+ if self.p.online:
+ self.printing_jobs = True
+
+ def run_print_queue_loop(self):
+ # This is a polling work around to the current lack of events in printcore
+ # A better solution would be one in which a print_finised event could be
+ # listend for asynchronously without polling.
+ p = self.p
+ if self.printing_jobs and p.printing == False and p.paused == False and p.online:
+ if self.current_job != None:
+ self.update_job_progress(100)
+ self.fire("job_finished", self.jobs.sanitize(self.current_job))
+ if len(self.jobs.list) > 0:
+ print "Starting the next print job"
+ self.current_job = self.jobs.list.popleft()
+ self.p.startprint(self.current_job['body'].split("\n"))
+ self.fire("job_started", self.jobs.sanitize(self.current_job))
+ else:
+ print "Finished all print jobs"
+ self.current_job = None
+ self.printing_jobs = False
+
+ # Updating the job progress
+ self.update_job_progress(self.print_progress())
+
+ #print "print loop"
+ next_timeout = time.time() + 0.3
+ gen.Task(self.ioloop.add_timeout(next_timeout, self.run_print_queue_loop))
+
+ def update_job_progress(self, progress):
+ if progress != self.previous_job_progress and self.current_job != None:
+ self.previous_job_progress = progress
+ self.fire("job_progress_changed", progress)
+
+ def run_sensor_loop(self):
+ self.request_sensor_update()
+ next_timeout = time.time() + self.settings.sensor_poll_rate
+ gen.Task(self.ioloop.add_timeout(next_timeout, self.run_sensor_loop))
+
+ def request_sensor_update(self):
+ if self.p.online: self.p.send_now("M105")
+
+ def recvcb(self, l):
+ """ Parses a line of output from the printer via printcore """
+ l = l.rstrip()
+ #print l
+ if "T:" in l:
+ self._receive_sensor_update(l)
+ if l!="ok" and not l.startswith("ok T") and not l.startswith("T:"):
+ self._receive_printer_error(l)
+
+ def print_progress(self):
+ if(self.p.printing):
+ return 100*float(self.p.queueindex)/len(self.p.mainqueue)
+ if(self.sdprinting):
+ return self.percentdone
+ return 0
+
+
+ def _receive_sensor_update(self, l):
+ words = filter(lambda s: s.find(":") > 0, l.split(" "))
+ d = dict([ s.split(":") for s in words])
+
+ # print "sensor update received!"
+
+ for key, value in d.iteritems():
+ self.__update_sensor(key, value)
+
+ self.fire("sensor_changed")
+
+ def __update_sensor(self, key, value):
+ if (key in self.settings.sensor_names) == False:
+ return
+ sensor_name = self.settings.sensor_names[key]
+ self.sensors[sensor_name] = float(value)
+
+ def on_uncaught_event(self, event_name, content=None):
+ self.fire(event_name, content)
+
+ def log(self, *msg):
+ msg = ''.join(str(i) for i in msg)
+ msg.replace("\r", "")
+ print msg
+ self.fire("log", {'msg': msg, 'level': "debug"})
+
+ def write_prompt(self):
+ None
+
+
+class PrintJobQueue(EventEmitter):
+
+ def __init__(self):
+ super(PrintJobQueue, self).__init__()
+ self.list = deque([])
+ self.__last_id = 0
+
+ def public_list(self):
+ # A sanitized version of list for public consumption via construct
+ l2 = []
+ for job in self.list:
+ l2.append(self.sanitize(job))
+ return l2
+
+ def sanitize(self, job):
+ return dict(
+ id = job["id"],
+ original_file_name = job["original_file_name"],
+ rank = job["rank"]
+ )
+
+ def order(self):
+ sorted(self.list, key=lambda job: job['rank'])
+
+
+ def add(self, original_file_name, body):
+ ext = os.path.splitext(original_file_name)[1]
+ job = dict(
+ id = self.__last_id,
+ rank = len(self.list),
+ original_file_name=original_file_name,
+ body= body,
+ )
+ self.__last_id += 1
+
+ self.list.append(job)
+ print "Added %s"%(original_file_name)
+ self.fire("job_added", job)
+
+ def display_summary(self):
+ print "Print Jobs:"
+ for job in self.list:
+ print " %i: %s"%(job['id'], job['original_file_name'])
+ print ""
+ return True
+
+ def remove(self, job_id):
+ job = self.find_by_id(job_id)
+ if job == None:
+ return False
+ self.list.remove(job)
+ print "Print Job Removed"
+ self.fire("job_removed", job)
+
+ def update(self, job_id, job_attrs):
+ job = self.find_by_id(job_id)
+ if job == None:
+ return False
+ job['rank'] = job_attrs['position']
+ self.order()
+ print "Print Job Updated"
+ self.fire("job_updated", job)
+
+ def find_by_id(self, job_id):
+ for job in self.list:
+ if job['id'] == job_id: return job
+ return None
+
+ def fire(self, event_name, content):
+ self.display_summary()
+ super(PrintJobQueue, self).fire(event_name, content)
+
+
+
+# Server Start Up
+# -------------------------------------------------
+
+print "Pronserve is starting..."
+pronserve = Pronserve()
+pronserve.do_connect("")
+
+time.sleep(1)
+pronserve.run_sensor_loop()
+pronserve.run_print_queue_loop()
+
+if __name__ == "__main__":
+ application.listen(8888)
+ print "\n"+"-"*80
+ welcome = textwrap.dedent(u"""
+ +---+ \x1B[0;32mPronserve: Your printer just got a whole lot better.\x1B[0m
+ | \u2713 | Ready to print.
+ +---+ More details at http://localhost:8888/""")
+ sys.stdout.write(welcome)
+ print "\n\n" + "-"*80 + "\n"
+
+ try:
+ pronserve.ioloop.start()
+ except:
+ pronserve.p.disconnect()
diff --git a/pronsole.py b/pronsole.py
index 6bcae83..f2b2303 100755
--- a/pronsole.py
+++ b/pronsole.py
@@ -77,7 +77,7 @@ def estimate_duration(g):
#TODO:
# get device caps from firmware: max speed, acceleration/axis (including extruder)
# calculate the maximum move duration accounting for above ;)
- # print ".... estimating ...."
+ # self.log(".... estimating ....")
for i in g:
i = i.split(";")[0]
if "G4" in i or "G1" in i:
@@ -119,7 +119,7 @@ def estimate_duration(g):
if z > lastz:
layercount +=1
- #print "layer z: ", lastz, " will take: ", time.strftime('%H:%M:%S', time.gmtime(totalduration-layerbeginduration))
+ #self.log("layer z: ", lastz, " will take: ", time.strftime('%H:%M:%S', time.gmtime(totalduration-layerbeginduration)))
layerbeginduration = totalduration
lastx = x
@@ -128,7 +128,7 @@ def estimate_duration(g):
laste = e
lastf = f
- #print "Total Duration: " #, time.strftime('%H:%M:%S', time.gmtime(totalduration))
+ #self.log("Total Duration: " #, time.strftime('%H:%M:%S', time.gmtime(totalduration)))
return "{0:d} layers, ".format(int(layercount)) + str(datetime.timedelta(seconds = int(totalduration)))
def confirm():
@@ -252,6 +252,7 @@ class pronsole(cmd.Cmd):
self.settings._bedtemp_abs_cb = self.set_temp_preset
self.settings._bedtemp_pla_cb = self.set_temp_preset
self.monitoring = 0
+ self.silent = False
self.helpdict = {}
self.helpdict["baudrate"] = _("Communications Speed (default: 115200)")
self.helpdict["bedtemp_abs"] = _("Heated Build Platform temp for ABS (default: 110 deg C)")
@@ -274,6 +275,9 @@ class pronsole(cmd.Cmd):
"macro" : "%(bold)s..>%(normal)s ",
"online" : "%(bold)sT:%(extruder_temp_fancy)s %(progress_fancy)s >%(normal)s "}
+ def log(self, *msg):
+ print ''.join(str(i) for i in msg)
+
def promptf(self):
"""A function to generate prompts so that we can do dynamic prompts. """
if self.in_macro:
@@ -323,11 +327,11 @@ class pronsole(cmd.Cmd):
if not key.startswith("bed"):
self.temps["pla"] = str(self.settings.temperature_pla)
self.temps["abs"] = str(self.settings.temperature_abs)
- print "Hotend temperature presets updated, pla:%s, abs:%s" % (self.temps["pla"], self.temps["abs"])
+ self.log("Hotend temperature presets updated, pla:%s, abs:%s" % (self.temps["pla"], self.temps["abs"]))
else:
self.bedtemps["pla"] = str(self.settings.bedtemp_pla)
self.bedtemps["abs"] = str(self.settings.bedtemp_abs)
- print "Bed temperature presets updated, pla:%s, abs:%s" % (self.bedtemps["pla"], self.bedtemps["abs"])
+ self.log("Bed temperature presets updated, pla:%s, abs:%s" % (self.bedtemps["pla"], self.bedtemps["abs"]))
def scanserial(self):
"""scan for available ports. return a list of device names."""
@@ -345,7 +349,10 @@ class pronsole(cmd.Cmd):
return baselist+glob.glob('/dev/ttyUSB*') + glob.glob('/dev/ttyACM*') +glob.glob("/dev/tty.*")+glob.glob("/dev/cu.*")+glob.glob("/dev/rfcomm*")
def online(self):
- print "\rPrinter is now online"
+ self.log("\rPrinter is now online")
+ self.write_prompt()
+
+ def write_prompt(self):
sys.stdout.write(self.promptf())
sys.stdout.flush()
@@ -356,7 +363,7 @@ class pronsole(cmd.Cmd):
self.help_gcodes()
def help_gcodes(self):
- print "Gcodes are passed through to the printer as they are"
+ self.log("Gcodes are passed through to the printer as they are")
def complete_macro(self, text, line, begidx, endidx):
if (len(line.split()) == 2 and line[-1] != " ") or (len(line.split()) == 1 and line[-1]==" "):
@@ -386,7 +393,7 @@ class pronsole(cmd.Cmd):
setattr(self.__class__, "do_"+self.cur_macro_name, lambda self, largs, macro = macro:macro(self,*largs.split()))
setattr(self.__class__, "help_"+self.cur_macro_name, lambda self, macro_name = self.cur_macro_name: self.subhelp_macro(macro_name))
if not self.processing_rc:
- print "Macro '"+self.cur_macro_name+"' defined"
+ self.log("Macro '"+self.cur_macro_name+"' defined")
# save it
if not self.processing_args:
macro_key = "macro "+self.cur_macro_name
@@ -398,7 +405,7 @@ class pronsole(cmd.Cmd):
macro_def += self.cur_macro_def
self.save_in_rc(macro_key, macro_def)
else:
- print "Empty macro - cancelled"
+ self.log("Empty macro - cancelled")
del self.cur_macro_name, self.cur_macro_def
def compile_macro_line(self, line):
@@ -413,7 +420,7 @@ class pronsole(cmd.Cmd):
def compile_macro(self, macro_name, macro_def):
if macro_def.strip() == "":
- print "Empty macro - cancelled"
+ self.log("Empty macro - cancelled")
return
pycode = "def macro(self,*arg):\n"
if "\n" not in macro_def.strip():
@@ -427,7 +434,7 @@ class pronsole(cmd.Cmd):
def start_macro(self, macro_name, prev_definition = "", suppress_instructions = False):
if not self.processing_rc and not suppress_instructions:
- print "Enter macro using indented lines, end with empty line"
+ self.log("Enter macro using indented lines, end with empty line")
self.cur_macro_name = macro_name
self.cur_macro_def = ""
self.onecmd = self.hook_macro # override onecmd temporarily
@@ -438,11 +445,11 @@ class pronsole(cmd.Cmd):
if macro_name in self.macros.keys():
delattr(self.__class__, "do_"+macro_name)
del self.macros[macro_name]
- print "Macro '"+macro_name+"' removed"
+ self.log("Macro '"+macro_name+"' removed")
if not self.processing_rc and not self.processing_args:
self.save_in_rc("macro "+macro_name, "")
else:
- print "Macro '"+macro_name+"' is not defined"
+ self.log("Macro '"+macro_name+"' is not defined")
def do_macro(self, args):
if args.strip()=="":
self.print_topics("User-defined macros", self.macros.keys(), 15, 80)
@@ -450,7 +457,7 @@ class pronsole(cmd.Cmd):
arglist = args.split(None, 1)
macro_name = arglist[0]
if macro_name not in self.macros and hasattr(self.__class__, "do_"+macro_name):
- print "Name '"+macro_name+"' is being used by built-in command"
+ self.log("Name '"+macro_name+"' is being used by built-in command")
return
if len(arglist) == 2:
macro_def = arglist[1]
@@ -470,24 +477,24 @@ class pronsole(cmd.Cmd):
self.start_macro(macro_name)
def help_macro(self):
- print "Define single-line macro: macro <name> <definition>"
- print "Define multi-line macro: macro <name>"
- print "Enter macro definition in indented lines. Use {0} .. {N} to substitute macro arguments"
- print "Enter python code, prefixed with ! Use arg[0] .. arg[N] to substitute macro arguments"
- print "Delete macro: macro <name> /d"
- print "Show macro definition: macro <name> /s"
- print "'macro' without arguments displays list of defined macros"
+ self.log("Define single-line macro: macro <name> <definition>")
+ self.log("Define multi-line macro: macro <name>")
+ self.log("Enter macro definition in indented lines. Use {0} .. {N} to substitute macro arguments")
+ self.log("Enter python code, prefixed with ! Use arg[0] .. arg[N] to substitute macro arguments")
+ self.log("Delete macro: macro <name> /d")
+ self.log("Show macro definition: macro <name> /s")
+ self.log("'macro' without arguments displays list of defined macros")
def subhelp_macro(self, macro_name):
if macro_name in self.macros.keys():
macro_def = self.macros[macro_name]
if "\n" in macro_def:
- print "Macro '"+macro_name+"' defined as:"
- print self.macros[macro_name]+"----------------"
+ self.log("Macro '"+macro_name+"' defined as:")
+ self.log(self.macros[macro_name]+"----------------")
else:
- print "Macro '"+macro_name+"' defined as: '"+macro_def+"'"
+ self.log("Macro '"+macro_name+"' defined as: '"+macro_def+"'")
else:
- print "Macro '"+macro_name+"' is not defined"
+ self.log("Macro '"+macro_name+"' is not defined")
def set(self, var, str):
try:
@@ -496,29 +503,29 @@ class pronsole(cmd.Cmd):
if not self.processing_rc and not self.processing_args:
self.save_in_rc("set "+var, "set %s %s" % (var, value))
except AttributeError:
- print "Unknown variable '%s'" % var
+ self.log("Unknown variable '%s'" % var)
except ValueError, ve:
- print "Bad value for variable '%s', expecting %s (%s)" % (var, repr(t)[1:-1], ve.args[0])
+ self.log("Bad value for variable '%s', expecting %s (%s)" % (var, repr(t)[1:-1], ve.args[0]))
def do_set(self, argl):
args = argl.split(None, 1)
if len(args) < 1:
for k in [kk for kk in dir(self.settings) if not kk.startswith("_")]:
- print "%s = %s" % (k, str(getattr(self.settings, k)))
+ self.log("%s = %s" % (k, str(getattr(self.settings, k))))
return
value = getattr(self.settings, args[0])
if len(args) < 2:
try:
- print "%s = %s" % (args[0], getattr(self.settings, args[0]))
+ self.log("%s = %s" % (args[0], getattr(self.settings, args[0])))
except AttributeError:
- print "Unknown variable '%s'" % args[0]
+ self.log("Unknown variable '%s'" % args[0])
return
self.set(args[0], args[1])
def help_set(self):
- print "Set variable: set <variable> <value>"
- print "Show variable: set <variable>"
- print "'set' without arguments displays all variables"
+ self.log("Set variable: set <variable> <value>")
+ self.log("Show variable: set <variable>")
+ self.log("'set' without arguments displays all variables")
def complete_set(self, text, line, begidx, endidx):
if (len(line.split()) == 2 and line[-1] != " ") or (len(line.split()) == 1 and line[-1]==" "):
@@ -600,16 +607,16 @@ class pronsole(cmd.Cmd):
rci.close()
rco.close()
#if definition != "":
- # print "Saved '"+key+"' to '"+self.rc_filename+"'"
+ # self.log("Saved '"+key+"' to '"+self.rc_filename+"'")
#else:
- # print "Removed '"+key+"' from '"+self.rc_filename+"'"
+ # self.log("Removed '"+key+"' from '"+self.rc_filename+"'")
except Exception, e:
- print "Saving failed for", key+":", str(e)
+ self.log("Saving failed for", key+":", str(e))
finally:
del rci, rco
def preloop(self):
- print "Welcome to the printer console! Type \"help\" for a list of available commands."
+ self.log("Welcome to the printer console! Type \"help\" for a list of available commands.")
self.prompt = self.promptf()
cmd.Cmd.preloop(self)
@@ -626,12 +633,12 @@ class pronsole(cmd.Cmd):
try:
baud = int(a[1])
except:
- print "Bad baud value '"+a[1]+"' ignored"
+ self.log("Bad baud value '"+a[1]+"' ignored")
if len(p) == 0 and not port:
- print "No serial ports detected - please specify a port"
+ self.log("No serial ports detected - please specify a port")
return
if len(a) == 0:
- print "No port specified - connecting to %s at %dbps" % (port, baud)
+ self.log("No port specified - connecting to %s at %dbps" % (port, baud))
if port != self.settings.port:
self.settings.port = port
self.save_in_rc("set port", "set port %s" % port)
@@ -641,14 +648,14 @@ class pronsole(cmd.Cmd):
self.p.connect(port, baud)
def help_connect(self):
- print "Connect to printer"
- print "connect <port> <baudrate>"
- print "If port and baudrate are not specified, connects to first detected port at 115200bps"
+ self.log("Connect to printer")
+ self.log("connect <port> <baudrate>")
+ self.log("If port and baudrate are not specified, connects to first detected port at 115200bps")
ports = self.scanserial()
if(len(ports)):
- print "Available ports: ", " ".join(ports)
+ self.log("Available ports: ", " ".join(ports))
else:
- print "No serial ports were automatically found."
+ self.log("No serial ports were automatically found.")
def complete_connect(self, text, line, begidx, endidx):
if (len(line.split()) == 2 and line[-1] != " ") or (len(line.split()) == 1 and line[-1]==" "):
@@ -662,22 +669,22 @@ class pronsole(cmd.Cmd):
self.p.disconnect()
def help_disconnect(self):
- print "Disconnects from the printer"
+ self.log("Disconnects from the printer")
def do_load(self,l):
self._do_load(l)
def _do_load(self,l):
if len(l)==0:
- print "No file name given."
+ self.log("No file name given.")
return
- print "Loading file:"+l
+ self.log("Loading file:"+l)
if not(os.path.exists(l)):
- print "File not found!"
+ self.log("File not found!")
return
self.f = [i.replace("\n", "").replace("\r", "") for i in open(l)]
self.filename = l
- print "Loaded ", l, ", ", len(self.f)," lines."
+ self.log("Loaded ", l, ", ", len(self.f)," lines.")
def complete_load(self, text, line, begidx, endidx):
s = line.split()
@@ -690,32 +697,32 @@ class pronsole(cmd.Cmd):
return glob.glob("*/")+glob.glob("*.g*")
def help_load(self):
- print "Loads a gcode file (with tab-completion)"
+ self.log("Loads a gcode file (with tab-completion)")
def do_upload(self, l):
if len(l) == 0:
- print "No file name given."
+ self.log("No file name given.")
return
- print "Loading file:"+l.split()[0]
+ self.log("Loading file:"+l.split()[0])
if not(os.path.exists(l.split()[0])):
- print "File not found!"
+ self.log("File not found!")
return
if not self.p.online:
- print "Not connected to printer."
+ self.log("Not connected to printer.")
return
self.f = [i.replace("\n", "") for i in open(l.split()[0])]
self.filename = l.split()[0]
- print "Loaded ", l, ", ", len(self.f)," lines."
+ self.log("Loaded ", l, ", ", len(self.f)," lines.")
tname = ""
if len(l.split())>1:
tname = l.split()[1]
else:
- print "please enter target name in 8.3 format."
+ self.log("please enter target name in 8.3 format.")
return
- print "Uploading as ", tname
- print("Uploading "+self.filename)
+ self.log("Uploading as ", tname)
+ self.log(("Uploading "+self.filename))
self.p.send_now("M28 "+tname)
- print("Press Ctrl-C to interrupt upload.")
+ self.log(("Press Ctrl-C to interrupt upload."))
self.p.startprint(self.f)
try:
sys.stdout.write("Progress: 00.0%")
@@ -733,16 +740,16 @@ class pronsole(cmd.Cmd):
self.recvlisteners+=[self.listfiles]
self.p.send_now("M20")
time.sleep(0.5)
- print "\b\b\b\b\b100%. Upload completed. ", tname, " should now be on the card."
+ self.log("\b\b\b\b\b100%. Upload completed. ", tname, " should now be on the card.")
return
except:
- print "...interrupted!"
+ self.log("...interrupted!")
self.p.pause()
self.p.send_now("M29 "+tname)
time.sleep(0.2)
self.p.clear = 1
self.p.startprint([])
- print "A partial file named ", tname, " may have been written to the sd card."
+ self.log("A partial file named ", tname, " may have been written to the sd card.")
def complete_upload(self, text, line, begidx, endidx):
@@ -756,23 +763,23 @@ class pronsole(cmd.Cmd):
return glob.glob("*/")+glob.glob("*.g*")
def help_upload(self):
- print "Uploads a gcode file to the sd card"
+ self.log("Uploads a gcode file to the sd card")
def help_print(self):
if self.f is None:
- print "Send a loaded gcode file to the printer. Load a file with the load command first."
+ self.log("Send a loaded gcode file to the printer. Load a file with the load command first.")
else:
- print "Send a loaded gcode file to the printer. You have "+self.filename+" loaded right now."
+ self.log("Send a loaded gcode file to the printer. You have "+self.filename+" loaded right now.")
def do_print(self, l):
if self.f is None:
- print "No file loaded. Please use load first."
+ self.log("No file loaded. Please use load first.")
return
if not self.p.online:
- print "Not connected to printer."
+ self.log("Not connected to printer.")
return
- print("Printing "+self.filename)
- print("You can monitor the print with the monitor command.")
+ self.log(("printing "+self.filename))
+ self.log(("You can monitor the print with the monitor command."))
self.p.startprint(self.f)
#self.p.pause()
#self.paused = True
@@ -783,7 +790,7 @@ class pronsole(cmd.Cmd):
self.p.send_now("M25")
else:
if(not self.p.printing):
- print "Not printing, cannot pause."
+ self.log("Not self.log(ing, cannot pause.")
return
self.p.pause()
#self.p.connect()# This seems to work, but is not a good solution.
@@ -792,11 +799,11 @@ class pronsole(cmd.Cmd):
#self.do_resume(None)
def help_pause(self):
- print "Pauses a running print"
+ self.log("Pauses a running print")
def do_resume(self, l):
if not self.paused:
- print "Not paused, unable to resume. Start a print first."
+ self.log("Not paused, unable to resume. Start a print first.")
return
self.paused = False
if self.sdprinting:
@@ -806,7 +813,7 @@ class pronsole(cmd.Cmd):
self.p.resume()
def help_resume(self):
- print "Resumes a paused print."
+ self.log("Resumes a paused print.")
def emptyline(self):
pass
@@ -825,33 +832,33 @@ class pronsole(cmd.Cmd):
def do_ls(self, l):
if not self.p.online:
- print "Printer is not online. Try connect to it first."
+ self.log("printer is not online. Try connect to it first.")
return
self.listing = 2
self.sdfiles = []
self.recvlisteners+=[self.listfiles]
self.p.send_now("M20")
time.sleep(0.5)
- print " ".join(self.sdfiles)
+ self.log(" ".join(self.sdfiles))
def help_ls(self):
- print "lists files on the SD card"
+ self.log("lists files on the SD card")
def waitforsdresponse(self, l):
if "file.open failed" in l:
- print "Opening file failed."
+ self.log("Opening file failed.")
self.recvlisteners.remove(self.waitforsdresponse)
return
if "File opened" in l:
- print l
+ self.log(l)
if "File selected" in l:
- print "Starting print"
+ self.log("Starting print")
self.p.send_now("M24")
self.sdprinting = 1
#self.recvlisteners.remove(self.waitforsdresponse)
return
if "Done printing file" in l:
- print l
+ self.log(l)
self.sdprinting = 0
self.recvlisteners.remove(self.waitforsdresponse)
return
@@ -868,11 +875,11 @@ class pronsole(cmd.Cmd):
self.p.reset()
def help_reset(self):
- print "Resets the printer."
+ self.log("Resets the printer.")
def do_sdprint(self, l):
if not self.p.online:
- print "Printer is not online. Try connect to it first."
+ self.log("printer is not online. Try connect to it first.")
return
self.listing = 2
self.sdfiles = []
@@ -880,17 +887,17 @@ class pronsole(cmd.Cmd):
self.p.send_now("M20")
time.sleep(0.5)
if not (l.lower() in self.sdfiles):
- print "File is not present on card. Upload it first"
+ self.log("File is not present on card. Upload it first")
return
self.recvlisteners+=[self.waitforsdresponse]
self.p.send_now("M23 "+l.lower())
- print "Printing file: "+l.lower()+" from SD card."
- print "Requesting SD print..."
+ self.log("printing file: "+l.lower()+" from SD card.")
+ self.log("Requesting SD print...")
time.sleep(1)
def help_sdprint(self):
- print "Print a file from the SD card. Tabcompletes with available file names."
- print "sdprint filename.g"
+ self.log("print a file from the SD card. Tabcompletes with available file names.")
+ self.log("sdprint filename.g")
def complete_sdprint(self, text, line, begidx, endidx):
if self.sdfiles==[] and self.p.online:
@@ -909,32 +916,32 @@ class pronsole(cmd.Cmd):
if(tstring!="ok" and not tstring.startswith("ok T") and not tstring.startswith("T:") and not self.listing and not self.monitoring):
if tstring[:5] == "echo:":
tstring = tstring[5:].lstrip()
- print "\r" + tstring.ljust(15)
+ if self.silent == False: print "\r" + tstring.ljust(15)
sys.stdout.write(self.promptf())
sys.stdout.flush()
for i in self.recvlisteners:
i(l)
def help_shell(self):
- print "Executes a python command. Example:"
- print "! os.listdir('.')"
+ self.log("Executes a python command. Example:")
+ self.log("! os.listdir('.')")
def default(self, l):
if(l[0] in self.commandprefixes.upper()):
if(self.p and self.p.online):
if(not self.p.loud):
- print "SENDING:"+l
+ self.log("SENDING:"+l)
self.p.send_now(l)
else:
- print "Printer is not online."
+ self.log("printer is not online.")
return
elif(l[0] in self.commandprefixes.lower()):
if(self.p and self.p.online):
if(not self.p.loud):
- print "SENDING:"+l.upper()
+ self.log("SENDING:"+l.upper())
self.p.send_now(l.upper())
else:
- print "Printer is not online."
+ self.log("printer is not online.")
return
else:
cmd.Cmd.default(self, l)
@@ -942,6 +949,10 @@ class pronsole(cmd.Cmd):
def help_help(self):
self.do_help("")
+ def tempcb(self, l):
+ if "T:" in l:
+ self.log(l.replace("\r", "").replace("T", "Hotend").replace("B", "Bed").replace("\n", "").replace("ok ", ""))
+
def do_gettemp(self, l):
if "dynamic" in l:
self.dynamic_temp = True
@@ -955,7 +966,7 @@ class pronsole(cmd.Cmd):
print "Bed: %s/%s" % (self.status.bed_temp, self.status.bed_temp_target)
def help_gettemp(self):
- print "Read the extruder and bed temperature."
+ self.log("Read the extruder and bed temperature.")
def do_settemp(self, l):
try:
@@ -970,18 +981,18 @@ class pronsole(cmd.Cmd):
return
if self.p.online:
self.p.send_now("M104 S"+l)
- print "Setting hotend temperature to ", f, " degrees Celsius."
+ self.log("Setting hotend temperature to ", f, " degrees Celsius.")
else:
- print "Printer is not online."
+ self.log("printer is not online.")
else:
- print "You cannot set negative temperatures. To turn the hotend off entirely, set its temperature to 0."
+ self.log("You cannot set negative temperatures. To turn the hotend off entirely, set its temperature to 0.")
except:
- print "You must enter a temperature."
+ self.log("You must enter a temperature.")
def help_settemp(self):
- print "Sets the hotend temperature to the value entered."
- print "Enter either a temperature in celsius or one of the following keywords"
- print ", ".join([i+"("+self.temps[i]+")" for i in self.temps.keys()])
+ self.log("Sets the hotend temperature to the value entered.")
+ self.log("Enter either a temperature in celsius or one of the following keywords")
+ self.log(", ".join([i+"("+self.temps[i]+")" for i in self.temps.keys()]))
def complete_settemp(self, text, line, begidx, endidx):
if (len(line.split()) == 2 and line[-1] != " ") or (len(line.split()) == 1 and line[-1]==" "):
@@ -996,18 +1007,18 @@ class pronsole(cmd.Cmd):
if f>=0:
if self.p.online:
self.p.send_now("M140 S"+l)
- print "Setting bed temperature to ", f, " degrees Celsius."
+ self.log("Setting bed temperature to ", f, " degrees Celsius.")
else:
- print "Printer is not online."
+ self.log("printer is not online.")
else:
- print "You cannot set negative temperatures. To turn the bed off entirely, set its temperature to 0."
+ self.log("You cannot set negative temperatures. To turn the bed off entirely, set its temperature to 0.")
except:
- print "You must enter a temperature."
+ self.log("You must enter a temperature.")
def help_bedtemp(self):
- print "Sets the bed temperature to the value entered."
- print "Enter either a temperature in celsius or one of the following keywords"
- print ", ".join([i+"("+self.bedtemps[i]+")" for i in self.bedtemps.keys()])
+ self.log("Sets the bed temperature to the value entered.")
+ self.log("Enter either a temperature in celsius or one of the following keywords")
+ self.log(", ".join([i+"("+self.bedtemps[i]+")" for i in self.bedtemps.keys()]))
def complete_bedtemp(self, text, line, begidx, endidx):
if (len(line.split()) == 2 and line[-1] != " ") or (len(line.split()) == 1 and line[-1]==" "):
@@ -1015,13 +1026,13 @@ class pronsole(cmd.Cmd):
def do_move(self, l):
if(len(l.split())<2):
- print "No move specified."
+ self.log("No move specified.")
return
if self.p.printing:
- print "Printer is currently printing. Please pause the print before you issue manual commands."
+ self.log("printer is currently printing. Please pause the print before you issue manual commands.")
return
if not self.p.online:
- print "Printer is not online. Unable to move."
+ self.log("printer is not online. Unable to move.")
return
l = l.split()
if(l[0].lower()=="x"):
@@ -1037,13 +1048,13 @@ class pronsole(cmd.Cmd):
feed = self.settings.e_feedrate
axis = "E"
else:
- print "Unknown axis."
+ self.log("Unknown axis.")
return
dist = 0
try:
dist = float(l[1])
except:
- print "Invalid distance"
+ self.log("Invalid distance")
return
try:
feed = int(l[2])
@@ -1054,11 +1065,11 @@ class pronsole(cmd.Cmd):
self.p.send_now("G90")
def help_move(self):
- print "Move an axis. Specify the name of the axis and the amount. "
- print "move X 10 will move the X axis forward by 10mm at ", self.settings.xy_feedrate, "mm/min (default XY speed)"
- print "move Y 10 5000 will move the Y axis forward by 10mm at 5000mm/min"
- print "move Z -1 will move the Z axis down by 1mm at ", self.settings.z_feedrate, "mm/min (default Z speed)"
- print "Common amounts are in the tabcomplete list."
+ self.log("Move an axis. Specify the name of the axis and the amount. ")
+ self.log("move X 10 will move the X axis forward by 10mm at ", self.settings.xy_feedrate, "mm/min (default XY speed)")
+ self.log("move Y 10 5000 will move the Y axis forward by 10mm at 5000mm/min")
+ self.log("move Z -1 will move the Z axis down by 1mm at ", self.settings.z_feedrate, "mm/min (default Z speed)")
+ self.log("Common amounts are in the tabcomplete list.")
def complete_move(self, text, line, begidx, endidx):
if (len(line.split()) == 2 and line[-1] != " ") or (len(line.split()) == 1 and line[-1]==" "):
@@ -1078,29 +1089,29 @@ class pronsole(cmd.Cmd):
length = 5#default extrusion length
feed = self.settings.e_feedrate#default speed
if not self.p.online:
- print "Printer is not online. Unable to move."
+ self.log("printer is not online. Unable to move.")
return
if self.p.printing:
- print "Printer is currently printing. Please pause the print before you issue manual commands."
+ self.log("printer is currently printing. Please pause the print before you issue manual commands.")
return
ls = l.split()
if len(ls):
try:
length = float(ls[0])
except:
- print "Invalid length given."
+ self.log("Invalid length given.")
if len(ls)>1:
try:
feed = int(ls[1])
except:
- print "Invalid speed given."
+ self.log("Invalid speed given.")
if override is not None:
length = override
feed = overridefeed
if length > 0:
- print "Extruding %fmm of filament."%(length,)
+ self.log("Extruding %fmm of filament."%(length,))
elif length <0:
- print "Reversing %fmm of filament."%(-1*length,)
+ self.log("Reversing %fmm of filament."%(-1*length,))
else:
"Length is 0, not doing anything."
self.p.send_now("G91")
@@ -1108,40 +1119,40 @@ class pronsole(cmd.Cmd):
self.p.send_now("G90")
def help_extrude(self):
- print "Extrudes a length of filament, 5mm by default, or the number of mm given as a parameter"
- print "extrude - extrudes 5mm of filament at 300mm/min (5mm/s)"
- print "extrude 20 - extrudes 20mm of filament at 300mm/min (5mm/s)"
- print "extrude -5 - REVERSES 5mm of filament at 300mm/min (5mm/s)"
- print "extrude 10 210 - extrudes 10mm of filament at 210mm/min (3.5mm/s)"
+ self.log("Extrudes a length of filament, 5mm by default, or the number of mm given as a parameter")
+ self.log("extrude - extrudes 5mm of filament at 300mm/min (5mm/s)")
+ self.log("extrude 20 - extrudes 20mm of filament at 300mm/min (5mm/s)")
+ self.log("extrude -5 - REVERSES 5mm of filament at 300mm/min (5mm/s)")
+ self.log("extrude 10 210 - extrudes 10mm of filament at 210mm/min (3.5mm/s)")
def do_reverse(self, l):
length = 5#default extrusion length
feed = self.settings.e_feedrate#default speed
if not self.p.online:
- print "Printer is not online. Unable to move."
+ self.log("printer is not online. Unable to move.")
return
if self.p.printing:
- print "Printer is currently printing. Please pause the print before you issue manual commands."
+ self.log("printer is currently printing. Please pause the print before you issue manual commands.")
return
ls = l.split()
if len(ls):
try:
length = float(ls[0])
except:
- print "Invalid length given."
+ self.log("Invalid length given.")
if len(ls)>1:
try:
feed = int(ls[1])
except:
- print "Invalid speed given."
+ self.log("Invalid speed given.")
self.do_extrude("", length*-1.0, feed)
def help_reverse(self):
- print "Reverses the extruder, 5mm by default, or the number of mm given as a parameter"
- print "reverse - reverses 5mm of filament at 300mm/min (5mm/s)"
- print "reverse 20 - reverses 20mm of filament at 300mm/min (5mm/s)"
- print "reverse 10 210 - extrudes 10mm of filament at 210mm/min (3.5mm/s)"
- print "reverse -5 - EXTRUDES 5mm of filament at 300mm/min (5mm/s)"
+ self.log("Reverses the extruder, 5mm by default, or the number of mm given as a parameter")
+ self.log("reverse - reverses 5mm of filament at 300mm/min (5mm/s)")
+ self.log("reverse 20 - reverses 20mm of filament at 300mm/min (5mm/s)")
+ self.log("reverse 10 210 - extrudes 10mm of filament at 210mm/min (3.5mm/s)")
+ self.log("reverse -5 - EXTRUDES 5mm of filament at 300mm/min (5mm/s)")
def do_exit(self, l):
if self.status.extruder_temp_target != 0:
@@ -1151,35 +1162,35 @@ class pronsole(cmd.Cmd):
if self.status.bed_temp_taret != 0:
print "Setting bed temp to 0"
self.p.send_now("M140 S0.0")
- print "Disconnecting from printer..."
+ self.log("Disconnecting from printer...")
print self.p.printing
if self.p.printing:
print "Are you sure you want to exit while printing?"
print "(this will terminate the print)."
if not confirm():
return False
- print "Exiting program. Goodbye!"
+ self.log("Exiting program. Goodbye!")
self.p.disconnect()
return True
def help_exit(self):
- print "Disconnects from the printer and exits the program."
+ self.log("Disconnects from the printer and exits the program.")
def do_monitor(self, l):
interval = 5
if not self.p.online:
- print "Printer is not online. Please connect first."
+ self.log("printer is not online. Please connect first.")
return
if not (self.p.printing or self.sdprinting):
- print "Printer not printing. Please print something before monitoring."
+ self.log("Printer not printing. Please print something before monitoring.")
return
- print "Monitoring printer, use ^C to interrupt."
+ self.log("Monitoring printer, use ^C to interrupt.")
if len(l):
try:
interval = float(l)
except:
- print "Invalid period given."
- print "Updating values every %f seconds."%(interval,)
+ self.log("Invalid period given.")
+ self.log("Updating values every %f seconds."%(interval,))
self.monitoring = 1
prev_msg_len = 0
try:
@@ -1197,17 +1208,18 @@ class pronsole(cmd.Cmd):
progress = self.percentdone
progress = int(progress*10)/10.0 #limit precision
prev_msg = preface + str(progress) + "%"
- sys.stdout.write("\r" + prev_msg.ljust(prev_msg_len))
- sys.stdout.flush()
+ if self.silent == False:
+ sys.stdout.write("\r" + prev_msg.ljust(prev_msg_len))
+ sys.stdout.flush()
prev_msg_len = len(prev_msg)
except KeyboardInterrupt:
- print "Done monitoring."
+ if self.silent == False: print "Done monitoring."
self.monitoring = 0
def help_monitor(self):
- print "Monitor a machine's temperatures and an SD print's status."
- print "monitor - Reports temperature and SD print status (if SD printing) every 5 seconds"
- print "monitor 2 - Reports temperature and SD print status (if SD printing) every 2 seconds"
+ self.log("Monitor a machine's temperatures and an SD print's status.")
+ self.log("monitor - Reports temperature and SD print status (if SD printing) every 5 seconds")
+ self.log("monitor 2 - Reports temperature and SD print status (if SD printing) every 2 seconds")
def expandcommand(self, c):
return c.replace("$python", sys.executable)
@@ -1215,31 +1227,31 @@ class pronsole(cmd.Cmd):
def do_skein(self, l):
l = l.split()
if len(l) == 0:
- print "No file name given."
+ self.log("No file name given.")
return
settings = 0
if(l[0]=="set"):
settings = 1
else:
- print "Skeining file:"+l[0]
+ self.log("Skeining file:"+l[0])
if not(os.path.exists(l[0])):
- print "File not found!"
+ self.log("File not found!")
return
try:
import shlex
if(settings):
param = self.expandcommand(self.settings.sliceoptscommand).replace("\\", "\\\\").encode()
- print "Entering slicer settings: ", param
+ self.log("Entering slicer settings: ", param)
subprocess.call(shlex.split(param))
else:
param = self.expandcommand(self.settings.slicecommand).encode()
- print "Slicing: ", param
+ self.log("Slicing: ", param)
params = [i.replace("$s", l[0]).replace("$o", l[0].replace(".stl", "_export.gcode").replace(".STL", "_export.gcode")).encode() for i in shlex.split(param.replace("\\", "\\\\").encode())]
subprocess.call(params)
- print "Loading sliced file."
+ self.log("Loading sliced file.")
self.do_load(l[0].replace(".stl", "_export.gcode"))
except Exception, e:
- print "Skeinforge execution failed: ", e
+ self.log("Skeinforge execution failed: ", e)
def complete_skein(self, text, line, begidx, endidx):
s = line.split()
@@ -1252,18 +1264,18 @@ class pronsole(cmd.Cmd):
return glob.glob("*/")+glob.glob("*.stl")
def help_skein(self):
- print "Creates a gcode file from an stl model using the slicer (with tab-completion)"
- print "skein filename.stl - create gcode file"
- print "skein filename.stl view - create gcode file and view using skeiniso"
- print "skein set - adjust slicer settings"
+ self.log("Creates a gcode file from an stl model using the slicer (with tab-completion)")
+ self.log("skein filename.stl - create gcode file")
+ self.log("skein filename.stl view - create gcode file and view using skeiniso")
+ self.log("skein set - adjust slicer settings")
def do_home(self, l):
if not self.p.online:
- print "Printer is not online. Unable to move."
+ self.log("printer is not online. Unable to move.")
return
if self.p.printing:
- print "Printer is currently printing. Please pause the print before you issue manual commands."
+ self.log("printer is currently printing. Please pause the print before you issue manual commands.")
return
if "x" in l.lower():
self.p.send_now("G28 X0")
@@ -1278,32 +1290,26 @@ class pronsole(cmd.Cmd):
self.p.send_now("G92 E0")
def help_home(self):
- print "Homes the printer"
- print "home - homes all axes and zeroes the extruder(Using G28 and G92)"
- print "home xy - homes x and y axes (Using G28)"
- print "home z - homes z axis only (Using G28)"
- print "home e - set extruder position to zero (Using G92)"
- print "home xyze - homes all axes and zeroes the extruder (Using G28 and G92)"
+ self.log("Homes the printer")
+ self.log("home - homes all axes and zeroes the extruder(Using G28 and G92)")
+ self.log("home xy - homes x and y axes (Using G28)")
+ self.log("home z - homes z axis only (Using G28)")
+ self.log("home e - set extruder position to zero (Using G92)")
+ self.log("home xyze - homes all axes and zeroes the extruder (Using G28 and G92)")
def parse_cmdline(self, args):
import getopt
- opts, args = getopt.getopt(args, "c:e:hw", ["conf = ", "config = ", "help", "web", "web-config = ", "web-auth-config = "])
+ opts, args = getopt.getopt(args, "c:e:hw", ["conf = ", "config = ", "help"])
for o, a in opts:
- #print repr((o, a))
+ #self.log(repr((o, a)))
if o in ("-c", "--conf", "--config"):
self.load_rc(a)
- elif o in ("-w", "--web"):
- self.webrequested = True
- elif o == "--web-config":
- self.web_config = a
- elif o == "--web-auth-config":
- self.web_auth_config = a
elif o in ("-h", "--help"):
- print "Usage: "+sys.argv[0]+' [-c filename [-c filename2 ... ] ] [-e "command" ...]'
- print " -c | --conf | --config - override startup .pronsolerc file"
- print " may chain config files, settings auto-save will go into last file in the chain"
- print ' -e <command> - executes command after configuration/.pronsolerc is loaded'
- print " macros/settings from these commands are not autosaved"
+ self.log("Usage: "+sys.argv[0]+' [-c filename [-c filename2 ... ] ] [-e "command" ...]')
+ self.log(" -c | --conf | --config - override startup .pronsolerc file")
+ self.log(" may chain config files, settings auto-save will go into last file in the chain")
+ self.log(' -e <command> - executes command after configuration/.pronsolerc is loaded')
+ self.log(" macros/settings from these commands are not autosaved")
sys.exit()
if not self.rc_loaded:
self.load_default_rc()
diff --git a/pronterface.py b/pronterface.py
index ceb87dc..356dff2 100755
--- a/pronterface.py
+++ b/pronterface.py
@@ -196,21 +196,6 @@ class PronterWindow(MainWindow, pronsole.pronsole):
self.predisconnect_layer = None
self.hsetpoint = 0.0
self.bsetpoint = 0.0
- self.webInterface = None
- if self.webrequested:
- try :
- import cherrypy
- from printrun import webinterface
- try:
- self.webInterface = webinterface.WebInterface(self)
- self.webThread = threading.Thread(target = webinterface.StartWebInterfaceThread, args = (self.webInterface, ))
- self.webThread.start()
- except:
- print _("Failed to start web interface")
- traceback.print_exc(file = sys.stdout)
- self.webInterface = None
- except:
- print _("CherryPy is not installed. Web Interface Disabled.")
if self.filename is not None:
self.do_load(self.filename)
@@ -364,8 +349,6 @@ class PronterWindow(MainWindow, 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 self.webInterface:
- self.webInterface.AddLog("You must enter a temperature. (%s)" % (repr(x),))
def do_bedtemp(self, l = ""):
try:
@@ -382,16 +365,10 @@ class PronterWindow(MainWindow, pronsole.pronsole):
self.setbedgui(f)
else:
print _("Printer is not online.")
- if self.webInterface:
- 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 self.webInterface:
- self.webInterface.AddLog("You cannot set negative temperatures. To turn the bed off entirely, set its temperature to 0.")
except Exception, x:
print _("You must enter a temperature. (%s)") % (repr(x),)
- if self.webInterface:
- self.webInterface.AddLog("You must enter a temperature.")
def end_macro(self):
pronsole.pronsole.end_macro(self)
@@ -411,8 +388,6 @@ class PronterWindow(MainWindow, pronsole.pronsole):
self.delete_macro(macro_name)
return
print _("Cancelled.")
- if self.webInterface:
- self.webInterface.AddLog("Cancelled.")
return
self.cur_macro_name = macro_name
self.cur_macro_def = definition
@@ -452,8 +427,6 @@ class PronterWindow(MainWindow, pronsole.pronsole):
projectlayer.setframe(self,self.p).Show()
else:
print _("Printer is not online.")
- if self.webInterface:
- self.webInterface.AddLog("Printer is not online.")
def popmenu(self):
self.menustrip = wx.MenuBar()
@@ -530,8 +503,6 @@ class PronterWindow(MainWindow, pronsole.pronsole):
old_def = self.macros[macro]
elif len([c for c in macro.encode("ascii", "replace") if not c.isalnum() and c != "_"]):
print _("Macro name may contain only ASCII alphanumeric symbols and underscores")
- if self.webInterface:
- self.webInterface.AddLog("Macro name may contain only alphanumeric symbols and underscores")
return
elif hasattr(self.__class__, "do_"+macro):
print _("Name '%s' is being used by built-in command") % macro
@@ -713,8 +684,6 @@ class PronterWindow(MainWindow, pronsole.pronsole):
def help_button(self):
print _('Defines custom button. Usage: button <num> "title" [/c "colour"] command')
- if self.webInterface:
- self.webInterface.AddLog('Defines custom button. Usage: button <num> "title" [/c "colour"] command')
def do_button(self, argstr):
def nextarg(rest):
@@ -737,8 +706,6 @@ class PronterWindow(MainWindow, pronsole.pronsole):
command = argstr.strip()
if num<0 or num>=64:
print _("Custom button number should be between 0 and 63")
- if self.webInterface:
- self.webInterface.AddLog("Custom button number should be between 0 and 63")
return
while num >= len(self.custombuttons):
self.custombuttons.append(None)
@@ -1004,8 +971,6 @@ class PronterWindow(MainWindow, pronsole.pronsole):
self.cur_button = None
except:
print _("event object missing")
- if self.webInterface:
- self.webInterface.AddLog("event object missing")
self.cur_button = None
raise
@@ -1025,9 +990,6 @@ class PronterWindow(MainWindow, pronsole.pronsole):
except:
pass
self.Destroy()
- if self.webInterface:
- from printrun import webinterface
- webinterface.KillWebInterfaceThread()
def do_monitor(self, l = ""):
if l.strip()=="":
@@ -1040,17 +1002,11 @@ class PronterWindow(MainWindow, pronsole.pronsole):
wx.CallAfter(self.monitorbox.SetValue, self.monitor_interval>0)
except:
print _("Invalid period given.")
- if self.webInterface:
- self.webInterface.AddLog("Invalid period given.")
self.setmonitor(None)
if self.monitor:
print _("Monitoring printer.")
- if self.webInterface:
- self.webInterface.AddLog("Monitoring printer.")
else:
print _("Done monitoring.")
- if self.webInterface:
- self.webInterface.AddLog("Done monitoring.")
def setmonitor(self, e):
self.monitor = self.monitorbox.GetValue()
@@ -1065,8 +1021,6 @@ class PronterWindow(MainWindow, pronsole.pronsole):
except:
print "attempted to write invalid text to console"
pass
- if self.webInterface:
- self.webInterface.AppendLog(text)
def setloud(self,e):
self.p.loud=e.IsChecked()
@@ -1229,8 +1183,6 @@ class PronterWindow(MainWindow, pronsole.pronsole):
import shlex
param = self.expandcommand(self.settings.slicecommand).encode()
print "Slicing: ", param
- if self.webInterface:
- 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)
@@ -1242,8 +1194,6 @@ class PronterWindow(MainWindow, pronsole.pronsole):
self.stopsf = 1
except:
print _("Failed to execute slicing software: ")
- if self.webInterface:
- self.webInterface.AddLog("Failed to execute slicing software: ")
self.stopsf = 1
traceback.print_exc(file = sys.stdout)
@@ -1336,8 +1286,6 @@ class PronterWindow(MainWindow, 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 self.webInterface:
- 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)
try:
diff --git a/server/__init__.py b/server/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/server/__init__.py
diff --git a/server/basic_auth.py b/server/basic_auth.py
new file mode 100644
index 0000000..e54983a
--- /dev/null
+++ b/server/basic_auth.py
@@ -0,0 +1,93 @@
+import tornado.ioloop
+import tornado.web
+import tornado.websocket
+import base64
+import logging
+import logging.config
+import tornado.httpserver
+import tornado.ioloop
+import tornado.web
+
+log = logging.getLogger("root")
+
+def authenticate(realm, authenticator,user_extractor) :
+ """
+ This is a basic authentication interceptor which
+ protects the desired URIs and requires
+ authentication as per configuration
+ """
+ def wrapper(self, transforms, *args, **kwargs):
+ def _request_basic_auth(self):
+ if self._headers_written:
+ raise Exception('headers have already been written')
+
+ # If this is a websocket accept parameter-based (user/password) auth:
+ if hasattr(self, 'stream'):
+ """
+ self.stream.write(tornado.escape.utf8(
+ "HTTP/1.1 401 Unauthorized\r\n"+
+ "Date: Wed, 10 Apr 2013 02:09:52 GMT\r\n"+
+ "Content-Length: 0\r\n"+
+ "Content-Type: text/html; charset=UTF-8\r\n"+
+ "Www-Authenticate: Basic realm=\"auth_realm\"\r\n"+
+ "Server: TornadoServer/3.0.1\r\n\r\n"
+ ))
+ self.stream.close()
+ """
+ # If this is a restful request use the standard tornado methods:
+ else:
+ self.set_status(401)
+ self.set_header('WWW-Authenticate','Basic realm="%s"' % realm)
+ self._transforms = []
+ self.finish()
+
+ return False
+ request = self.request
+ format = ''
+ clazz = self.__class__
+ log.debug('intercepting for class : %s', clazz)
+ try:
+ auth_hdr = request.headers.get('Authorization')
+
+ if auth_hdr == None:
+ return _request_basic_auth(self)
+ if not auth_hdr.startswith('Basic '):
+ return _request_basic_auth(self)
+
+ auth_decoded = base64.decodestring(auth_hdr[6:])
+ username, password = auth_decoded.split(':', 2)
+
+ user_info = authenticator(realm, unicode(username), password)
+ if user_info :
+ self._user_info = user_info
+ self._current_user = user_extractor(user_info)
+ log.debug('authenticated user is : %s',
+ str(self._user_info))
+ else:
+ return _request_basic_auth(self)
+ except Exception, e:
+ return _request_basic_auth(self)
+ return True
+ return wrapper
+
+def interceptor(func):
+ """
+ This is a class decorator which is helpful in configuring
+ one or more interceptors which are able to intercept, inspect,
+ process and approve or reject further processing of the request
+ """
+ def classwrapper(cls):
+ def wrapper(old):
+ def inner(self, transforms, *args, **kwargs):
+ log.debug('Invoking wrapper %s',func)
+ ret = func(self,transforms,*args,**kwargs)
+ if ret :
+ return old(self,transforms,*args,**kwargs)
+ else :
+ return ret
+ return inner
+ cls._execute = wrapper(cls._execute)
+ return cls
+ return classwrapper
+
+print "moo" \ No newline at end of file
diff --git a/server/static/.DS_Store b/server/static/.DS_Store
new file mode 100644
index 0000000..f5abd5e
--- /dev/null
+++ b/server/static/.DS_Store
Binary files differ
diff --git a/server/static/css/index.css b/server/static/css/index.css
new file mode 100644
index 0000000..153f467
--- /dev/null
+++ b/server/static/css/index.css
@@ -0,0 +1,34 @@
+html, body
+{
+ margin: 0px;
+ padding: 0px;
+ height: 100%;
+}
+
+body
+{
+ background: url("/static/img/background.jpg");
+ background-color: black;
+ background-repeat: no-repeat;
+ background-position: 50% 50%;
+ background-size: auto 100%;
+}
+
+.lead-box
+{
+ position: absolute;
+ text-align: right;
+ top: 80%;
+ margin-top: -40px;
+ padding: 0px;
+ color: white;
+ padding-left: 100px;
+ padding-right: 20px;
+ background: rgba(0, 0, 0, 0.6);
+}
+
+.lead-box a, .lead-box a:hover
+{
+ color: #3198EC;
+ font-weight: bold;
+}
diff --git a/server/static/css/inspect.css b/server/static/css/inspect.css
new file mode 100644
index 0000000..8de149c
--- /dev/null
+++ b/server/static/css/inspect.css
@@ -0,0 +1,54 @@
+.sensors
+{
+ margin-top: 15px;
+}
+
+.sensors>*
+{
+ margin-left: 40px;
+ font-weight: bold;
+ font-size: 120%;
+}
+
+.sensors .val
+{
+ display: inline-block;
+ width: 50px;
+ text-align: right;
+}
+
+.sensors .val, .sensors .deg
+{
+ font-weight: normal;
+}
+
+.console
+{
+ height: 200px;
+ overflow-y: scroll;
+}
+
+.console pre
+{
+ border: 0px;
+ margin: 0px;
+ padding: 0px;
+}
+
+#temperature-graph
+{
+ height: 200px;
+}
+
+#print-job-panel
+{
+ margin: 0px;
+}
+
+.job-pogress
+{
+ margin: 80px 0;
+ font-size: 40px;
+ line-height: 40px;
+ text-align: center;
+} \ No newline at end of file
diff --git a/server/static/img/.DS_Store b/server/static/img/.DS_Store
new file mode 100644
index 0000000..5008ddf
--- /dev/null
+++ b/server/static/img/.DS_Store
Binary files differ
diff --git a/server/static/img/background.jpg b/server/static/img/background.jpg
new file mode 100644
index 0000000..c462050
--- /dev/null
+++ b/server/static/img/background.jpg
Binary files differ
diff --git a/server/static/js/inspect.js b/server/static/js/inspect.js
new file mode 100644
index 0000000..957edaf
--- /dev/null
+++ b/server/static/js/inspect.js
@@ -0,0 +1,145 @@
+(function() {
+ var $console;
+
+ var windowFocus = true;
+
+ $(window).focus(function() {
+ windowFocus = true;
+ //if ($console) $console.append("Window refocused, restarting graph.\n");
+ $(".focus-lost-overlay").addClass("out").removeClass("in").delay(1000).hide();
+ }).blur(function() {
+ windowFocus = false;
+ //if ($console) $console.append("Window's focus, lost stopping graph...\n");
+ $(".focus-lost-overlay")
+ .stop(true,true)
+ .show()
+ .addClass("in")
+ .removeClass("out");
+ }.debounce());
+
+ var connect = function() {
+ // Let us open a web socket
+ var url = "ws://localhost:8888/socket?user=admin&password=admin";
+ console.log(url);
+ var ws = new WebSocket(url);
+ $(function () {
+ $consoleWrapper = $(".console");
+ $console = $(".console pre");
+ $console.html("Connecting...\n")
+ onConnect(ws)
+ });
+ };
+
+ var updateSensorsUi = function() {
+ $(".sensors .val").each(function() {
+ $(this).html($(this).data("val")||"xx.x");
+ })
+ }.throttle(800);
+
+ var graph = null;
+ var graphData = [];
+ var graphResolution = 40;
+
+ var updateGraphData = function(current) {
+ current.time = Date.now();
+ if(graphData.length == graphResolution) graphData.shift();
+ graphData.push(current);
+ }
+
+ var updateGraphUi = function(current) {
+ if(graph == null)
+ {
+ graph = new Morris.Line({
+ // ID of the element in which to draw the chart.
+ element: "temperature-graph",
+ // Chart data records -- each entry in this array corresponds to a point on
+ // the chart.
+ data: graphData,
+ // The name of the data record attribute that contains x-values.
+ xkey: 'timestamp',
+ // A list of names of data record attributes that contain y-values.
+ ykeys: ['extruder', 'bed'],
+ // Labels for the ykeys -- will be displayed when you hover over the
+ // chart.
+ labels: ['extruder &deg;C', 'bed &deg;C'],
+ hideHover: 'always',
+ ymax: 'auto 250',
+ //pointSize: 0,
+ //parseTime: false,
+ xLabels: "decade"
+ });
+ }
+ else
+ {
+ graph.setData(graphData);
+ }
+ }
+
+ var updateUi = function(msg) {
+ if(windowFocus == false) return;
+ updateSensorsUi();
+ updateGraphUi();
+ }
+
+ var onConnect = function(ws) {
+ ws.onopen = function()
+ {
+ $console.append("Connected.\n");
+ // Web Socket is connected, send data using send()
+
+ };
+ var nextGraphPoint = {};
+ ws.onmessage = function (evt)
+ {
+ msg = JSON.parse(evt.data)
+ if(msg.sensor_changed != undefined)
+ {
+ var sensorNames = ["bed", "extruder"];
+ for (var i = 0; i < sensorNames.length; i++)
+ {
+ var name = msg.sensor_changed.name;
+ var val = parseFloat(msg.sensor_changed.value);
+ nextGraphPoint[name] = val;
+ $("."+name+" .val").data("val", val.format(1))
+ }
+ if(nextGraphPoint.bed != undefined && nextGraphPoint.extruder != undefined)
+ {
+ nextGraphPoint.timestamp = msg.timestamp
+ updateGraphData(nextGraphPoint);
+ nextGraphPoint = {};
+ }
+ requestAnimationFrame(updateUi);
+ }
+ else if (msg.job_progress_changed != undefined)
+ {
+ val = Math.round(parseFloat(msg.job_progress_changed)*10)/10;
+ $(".job-pogress .val").html(val);
+ }
+ else
+ {
+ console.log($consoleWrapper.scrollTop() - $console.innerHeight())
+ var atBottom = $consoleWrapper.scrollTop() - $console.innerHeight() > -220;
+ $console.append(evt.data + "\n");
+ if (atBottom)
+ {
+ $consoleWrapper.scrollTop($console.innerHeight());
+ }
+ }
+ };
+ ws.onclose = function()
+ {
+ // websocket is closed.
+ $console.append("\nConnection closed.");
+ };
+ };
+
+ if ("WebSocket" in window)
+ {
+ connect();
+ }
+ else
+ {
+ // The browser doesn't support WebSocket
+ alert("Error: WebSocket NOT supported by your Browser!");
+ }
+})(); \ No newline at end of file
diff --git a/server/templates/index.html b/server/templates/index.html
new file mode 100644
index 0000000..fe2819f
--- /dev/null
+++ b/server/templates/index.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<html>
+<head>
+ <title>Pronserve</title>
+ <link href="//netdna.bootstrapcdn.com/twitter-bootstrap/2.3.1/css/bootstrap-combined.min.css" rel="stylesheet">
+ <link href="/static/css/index.css" rel="stylesheet">
+</head>
+
+<body>
+
+ <div class="lead-box">
+ <h1>
+ Your printer just got a whole lot better.
+ </h1>
+ <p class="lead">
+ Pronserve is ready to print. Why not try it out with
+ <a href="/inspect">Inspector</a> or
+ <a href="https://github.com/D1plo1d/ctrlpanel">Ctrl Panel</a>?
+ </p>
+ </div>
+
+</body>
+
+</html> \ No newline at end of file
diff --git a/server/templates/inspect.html b/server/templates/inspect.html
new file mode 100644
index 0000000..dd90da1
--- /dev/null
+++ b/server/templates/inspect.html
@@ -0,0 +1,57 @@
+<!doctype html>
+<html>
+<head>
+ <title>Pronserve Inspector</title>
+ <link href="//netdna.bootstrapcdn.com/twitter-bootstrap/2.3.1/css/bootstrap-combined.min.css" rel="stylesheet">
+ <link rel="stylesheet" href="http://cdn.oesmith.co.uk/morris-0.4.1.min.css">
+ <link href="/static/css/inspect.css" rel="stylesheet">
+</head>
+
+<body>
+
+ <div class="container-fluid">
+ <div class="row-fluid">
+ <div class="span12">
+ <h1>
+ Pronserve Inspector
+ </h1>
+ <h2>Console</h2>
+ <div class="well console">
+ <pre>
+ Connecting...
+ </pre>
+ </div>
+ </div>
+ <div class="span3" id="print-job-panel">
+ <h2>Job Progress</h2>
+ <div class="job-pogress"><span class="val">XX.X</span>%</div>
+ </div>
+ <div class="span9">
+ <div class="sensors pull-right">
+ <div class="extruder pull-right">
+ Extruder: <span class="val">xx.x</span><span class="deg">&deg;C</span>
+ </div>
+ <div class="bed pull-right">
+ Bed: <span class="val"/>xx.x</span><span class="deg">&deg;C</span>
+ </div>
+ </div>
+ <h2>Temperature</h2>
+ <div class="clearfix"></div>
+ <div id="temperature-graph">
+ </div>
+ </div>
+ </div>
+ </div>
+
+
+ <div class="focus-lost-overlay modal-backdrop fade out hide"></div>
+
+ <script src="http://code.jquery.com/jquery-1.9.1.min.js"></script>
+ <script src="//netdna.bootstrapcdn.com/twitter-bootstrap/2.3.1/js/bootstrap.min.js"></script>
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/sugar/1.3.9/sugar.min.js"></script>
+ <script src="//cdnjs.cloudflare.com/ajax/libs/raphael/2.1.0/raphael-min.js"></script>
+ <script src="http://cdn.oesmith.co.uk/morris-0.4.1.min.js"></script>
+ <script src="/static/js/inspect.js"></script>
+</body>
+
+</html> \ No newline at end of file
diff --git a/testfiles/quick-test.gcode b/testfiles/quick-test.gcode
new file mode 100644
index 0000000..0ee6b3d
--- /dev/null
+++ b/testfiles/quick-test.gcode
@@ -0,0 +1,101 @@
+; THIS IS A TZST. DO NOT ATTZMPT TO PRINT THIS FILZ.
+; gZnZratZd by Slic3r 0.9.3-dZv on 2012-09-02 at 04:02:31
+
+; layZr_hZight = 0.4
+; pZrimZtZrs = 3
+; solid_layZrs = 3
+; fill_dZnsity = 0.4
+; pZrimZtZr_spZZd = 30
+; infill_spZZd = 60
+; travZl_spZZd = 130
+; scalZ = 1
+; nozzlZ_diamZtZr = 0.5
+; filamZnt_diamZtZr = 3
+; Zxtrusion_multipliZr = 1
+; singlZ wall width = 0.53mm
+; first layZr singlZ wall width = 0.80mm
+
+M104 S200 ; sZt tZmpZraturZ
+;G28 ; homZ all axZs
+;M109 S200 ; wait for tZmpZraturZ to bZ rZachZd
+G90 ; usZ absolutZ coordinatZs
+G21 ; sZt units to millimZtZrs
+G92 Z0
+M82 ; usZ absolutZ distancZs for Zxtrusion
+G1 Z0.400 F71800.000
+G1 X75.725 Y86.681
+G1 F1800.000 Z1.00000
+G1 X87.905 Y75.241 F1040.000 Z1.69560
+G1 X88.365 Y74.871 Z1.72017
+G1 X88.865 Y74.541 Z1.74511
+G1 X89.395 Y74.261 Z1.77006
+G1 X89.945 Y74.031 Z1.79488
+G1 X90.225 Y73.931 Z1.80726
+G1 X90.805 Y73.771 Z1.83230
+G1 X92.375 Y73.501 Z1.89862
+G1 X92.935 Y73.471 Z1.92196
+G1 X109.165 Y73.961 Z2.59789
+G1 X109.475 Y73.991 Z2.61085
+G1 X110.105 Y74.101 Z2.63747
+G1 X110.715 Y74.271 Z2.66383
+G1 X111.795 Y74.681 Z2.71192
+G1 X112.355 Y74.951 Z2.73780
+G1 X112.875 Y75.271 Z2.76322
+G1 X113.135 Y75.451 Z2.77638
+G1 X113.615 Y75.841 Z2.80213
+G1 X124.855 Y87.841 Z3.48656
+G1 X125.485 Y88.631 Z3.52863
+G1 X125.975 Y89.351 Z3.56488
+G1 X126.385 Y90.111 Z3.60083
+G1 X126.605 Y90.651 Z3.62510
+G1 X126.775 Y91.201 Z3.64906
+G1 X126.905 Y91.771 Z3.67340
+G1 X126.975 Y92.341 Z3.69731
+G1 X127.005 Y92.921 Z3.72148
+G1 X126.325 Y109.851 Z4.42681
+G1 X126.255 Y110.391 Z4.44947
+G1 X126.145 Y110.921 Z4.47201
+G1 X125.995 Y111.441 Z4.49453
+G1 X125.805 Y111.951 Z4.51719
+G1 X125.575 Y112.441 Z4.53972
+G1 X125.165 Y113.131 Z4.57313
+G1 X124.835 Y113.571 Z4.59603
+G1 X124.485 Y113.971 Z4.61815
+G1 X124.095 Y114.351 Z4.64082
+G1 X123.885 Y114.531 Z4.65233
+G1 X123.005 Y115.151 Z4.69715
+G1 X122.525 Y115.401 Z4.71967
+G1 X96.195 Y125.661 Z5.89600
+G1 X95.515 Y125.881 Z5.92575
+G1 X94.385 Y126.141 Z5.97402
+G1 X91.335 Y126.551 Z6.10213
+G1 X91.055 Y126.561 Z6.11379
+G1 X90.775 Y126.561 Z6.12545
+G1 X90.215 Y126.521 Z6.14882
+G1 X89.375 Y126.371 Z6.18434
+G1 X88.835 Y126.221 Z6.20767
+G1 X88.305 Y126.021 Z6.23125
+G1 X87.795 Y125.781 Z6.25471
+G1 X87.075 Y125.341 Z6.28984
+G1 X86.415 Y124.811 Z6.32507
+G1 X75.155 Y112.801 Z7.01038
+G1 X74.955 Y112.561 Z7.02339
+G1 X74.595 Y112.041 Z7.04972
+G1 X73.825 Y110.701 Z7.11405
+G1 X73.695 Y110.441 Z7.12615
+G1 X73.475 Y109.911 Z7.15004
+G1 X73.305 Y109.361 Z7.17400
+G1 X73.175 Y108.801 Z7.19794
+G1 X73.095 Y108.231 Z7.22190
+G1 X73.065 Y107.651 Z7.24607
+G1 X73.615 Y91.111 Z7.93497
+G1 X73.725 Y90.281 Z7.96982
+G1 X73.785 Y90.011 Z7.98134
+G1 X74.035 Y89.211 Z8.01623
+G1 X74.255 Y88.701 Z8.03935
+G1 X74.515 Y88.201 Z8.06281
+G1 X74.815 Y87.731 Z8.08602
+G1 X75.335 Y87.081 Z8.12067
+G1 X75.640 Y86.766 Z8.13893
+
+
diff --git a/web/css/style.css b/web/css/style.css
deleted file mode 100644
index 427f6c3..0000000
--- a/web/css/style.css
+++ /dev/null
@@ -1,206 +0,0 @@
-#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/web/js/asyncCommand.js b/web/js/asyncCommand.js
deleted file mode 100644
index f37ec8c..0000000
--- a/web/js/asyncCommand.js
+++ /dev/null
@@ -1,79 +0,0 @@
-function pronterfaceWebInterface_setup(){
- pronterfaceWebInterface_attachAsync();
-}
-
-function pronterfaceWebInterface_attachAsync(){
-
- var list = [];
- if(document.getElementsByClassName){
- list = document.getElementsByClassName('command');
- }else if(document.getElementsByTagName){
- list = document.getElementsByTagName('a');
- list.concat( document.getElementsByTagName('area') );
- //TODO filter list via checking the className attributes
- }else{
- console && console.error && console.error('unable to gather list of elements');
- return false;
- }
-
- for(var i=0; i < list.length; i++){
- list[i].addEventListener && list[i].addEventListener( 'click', function(e){return pronterfaceWebInterface_asyncCommand(null, e);}, true );
- list[i].attachEvent && list[i].attachEvent( 'onclick', function(e){return pronterfaceWebInterface_asyncCommand(null, e);} );
- }
-
- return true;
-}
-
-
-function pronterfaceWebInterface_asyncCommand( urlOrElement, event ){
-
- if( ! urlOrElement && event.target)
- urlOrElement = event.target;
-
- var url = null;
- if( typeof urlOrElement == 'string' ){
- url = urlOrElement;
- }else{
- url = urlOrElement&&urlOrElement.href;
- }
-
- if( typeof url != 'string' ){
- console && console.error && console.error('url not a string', urlOrElement, url);
- return true;
- }
-
- var httpRequest;
- if (window.XMLHttpRequest) { // Mozilla, Safari, ...
- httpRequest = new XMLHttpRequest();
- } else if (window.ActiveXObject) { // IE 8 and older
- httpRequest = new ActiveXObject("Microsoft.XMLHTTP");
- }
-
- if( ! httpRequest ){
- alert('no AJAX available?');
- // follow link
- return true;
- }
-
- //onreadystatechange
- //onerror
- httpRequest.open( 'GET', url, true);
- httpRequest.send(null);
-
- // don't follow link
- if( event ){
- event.stopImmediatePropagation && event.stopImmediatePropagation();
- event.defaultPrevented = true;
- event.preventDefault && event.preventDefault();
- }
- return false;
-}
-
-
-if (document.addEventListener) {
- document.addEventListener("DOMContentLoaded", pronterfaceWebInterface_setup, false);
-} else if (document.attachEvent) {
- document.attachEvent("onreadystatechange", pronterfaceWebInterface_setup);
-} else {
- document.onload = pronterfaceWebInterface_setup;
-}