├── printrun ├── __init__.py ├── svg │ ├── css │ │ ├── __init__.py │ │ ├── block.py │ │ ├── atrule.py │ │ ├── inline.py │ │ ├── identifier.py │ │ ├── transform.py │ │ ├── values.py │ │ └── colour.py │ ├── attributes.py │ ├── __init__.py │ └── pathdata.py ├── printrun_utils.py ├── bmpDisplay.py ├── bufferedcanvas.py ├── zscaper.py ├── calibrateextruder.py ├── zbuttons.py ├── SkeinforgeQuickEditDialog.py ├── stltool.py ├── pronterface_widgets.py ├── graph.py ├── projectlayer.py ├── xybuttons.py └── gui.py ├── .gitignore ├── auth.config ├── P-face.ico ├── plater.ico ├── pronsole.ico ├── images ├── inject.png ├── zoom_in.png ├── arrow_up.png ├── control_z.png ├── zoom_out.png ├── arrow_down.png ├── arrow_keys.png └── control_xy.png ├── http.config ├── testfiles ├── out.3dlp.zip ├── PCB-milling-and-(comment).gcode └── belt_pulley3.slic3r.small.svg ├── locale ├── de │ └── LC_MESSAGES │ │ ├── plater.mo │ │ ├── pronterface.mo │ │ └── plater.po ├── fr │ └── LC_MESSAGES │ │ ├── plater.mo │ │ ├── pronterface.mo │ │ ├── plater.po │ │ └── pronterface.po ├── it │ └── LC_MESSAGES │ │ ├── plater.mo │ │ ├── pronterface.mo │ │ └── plater.po ├── nl │ └── LC_MESSAGES │ │ └── pronterface.mo ├── plater.pot └── pronterface.pot ├── dot.pronsolerc.example ├── custombtn.txt ├── php └── parser.php ├── README.cleanup ├── README.i18n ├── css └── style.css ├── gcoder.py ├── setup.py ├── README.md └── printcore.py /printrun/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | .pronsolerc 3 | *.swp 4 | *.bak 5 | -------------------------------------------------------------------------------- /auth.config: -------------------------------------------------------------------------------- 1 | [user] 2 | user = admin 3 | pass = password -------------------------------------------------------------------------------- /P-face.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lemio/Printrun/master/P-face.ico -------------------------------------------------------------------------------- /plater.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lemio/Printrun/master/plater.ico -------------------------------------------------------------------------------- /pronsole.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lemio/Printrun/master/pronsole.ico -------------------------------------------------------------------------------- /images/inject.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lemio/Printrun/master/images/inject.png -------------------------------------------------------------------------------- /images/zoom_in.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lemio/Printrun/master/images/zoom_in.png -------------------------------------------------------------------------------- /images/arrow_up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lemio/Printrun/master/images/arrow_up.png -------------------------------------------------------------------------------- /images/control_z.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lemio/Printrun/master/images/control_z.png -------------------------------------------------------------------------------- /images/zoom_out.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lemio/Printrun/master/images/zoom_out.png -------------------------------------------------------------------------------- /http.config: -------------------------------------------------------------------------------- 1 | [global] 2 | server.socket_host: "localhost" 3 | server.socket_port: 8080 4 | 5 | -------------------------------------------------------------------------------- /images/arrow_down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lemio/Printrun/master/images/arrow_down.png -------------------------------------------------------------------------------- /images/arrow_keys.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lemio/Printrun/master/images/arrow_keys.png -------------------------------------------------------------------------------- /images/control_xy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lemio/Printrun/master/images/control_xy.png -------------------------------------------------------------------------------- /testfiles/out.3dlp.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lemio/Printrun/master/testfiles/out.3dlp.zip -------------------------------------------------------------------------------- /locale/de/LC_MESSAGES/plater.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lemio/Printrun/master/locale/de/LC_MESSAGES/plater.mo -------------------------------------------------------------------------------- /locale/fr/LC_MESSAGES/plater.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lemio/Printrun/master/locale/fr/LC_MESSAGES/plater.mo -------------------------------------------------------------------------------- /locale/it/LC_MESSAGES/plater.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lemio/Printrun/master/locale/it/LC_MESSAGES/plater.mo -------------------------------------------------------------------------------- /locale/de/LC_MESSAGES/pronterface.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lemio/Printrun/master/locale/de/LC_MESSAGES/pronterface.mo -------------------------------------------------------------------------------- /locale/fr/LC_MESSAGES/pronterface.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lemio/Printrun/master/locale/fr/LC_MESSAGES/pronterface.mo -------------------------------------------------------------------------------- /locale/it/LC_MESSAGES/pronterface.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lemio/Printrun/master/locale/it/LC_MESSAGES/pronterface.mo -------------------------------------------------------------------------------- /locale/nl/LC_MESSAGES/pronterface.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lemio/Printrun/master/locale/nl/LC_MESSAGES/pronterface.mo -------------------------------------------------------------------------------- /printrun/svg/css/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | from .transform import transformList 3 | from .inline import inlineStyle 4 | -------------------------------------------------------------------------------- /printrun/svg/css/block.py: -------------------------------------------------------------------------------- 1 | """ 2 | CSS blocks 3 | """ 4 | 5 | from pyparsing import nestedExpr 6 | 7 | block = nestedExpr(opener="{", closer="}") 8 | -------------------------------------------------------------------------------- /printrun/svg/css/atrule.py: -------------------------------------------------------------------------------- 1 | """ CSS at-rules""" 2 | 3 | from pyparsing import Literal, Combine 4 | from .identifier import identifier 5 | 6 | atkeyword = Combine(Literal("@") + identifier) 7 | -------------------------------------------------------------------------------- /printrun/svg/css/inline.py: -------------------------------------------------------------------------------- 1 | """ Parser for inline CSS in style attributes """ 2 | 3 | def inlineStyle(styleString): 4 | if not styleString: 5 | return {} 6 | styles = styleString.split(";") 7 | rv = dict(style.split(":") for style in styles if len(style) != 0) 8 | return rv 9 | -------------------------------------------------------------------------------- /dot.pronsolerc.example: -------------------------------------------------------------------------------- 1 | # Sample .pronsolerc file - copy this into your home directory and rename it to .pronsolerc 2 | !print "Loaded " + self.rc_filename 3 | 4 | macro fan 5 | !global _fan 6 | !if '_fan' in globals() and _fan: 7 | !_fan = 0 8 | M107 9 | !if hasattr(self,"cur_button") and self.cur_button is not None: 10 | !self.onecmd('button %d "fan (off)" /c green fan' % self.cur_button) 11 | !else: 12 | !_fan = 1 13 | M106 14 | !if hasattr(self,"cur_button") and self.cur_button is not None: 15 | !self.onecmd('button %d "fan (on)" /c yellow fan' % self.cur_button) 16 | button 0 "fan (off)" /c "green" fan 17 | -------------------------------------------------------------------------------- /custombtn.txt: -------------------------------------------------------------------------------- 1 | btns=[ 2 | ###Defining custom buttons for pronterface is easy. Here's how. 3 | ###Below these instructions, add a line with the following format for each button 4 | # ["button name","command",color(RGB)], <--That comma is important, do not forget it 5 | ###As an example: (Remove the # to try it out): 6 | #["Read temp","M105",(200,100,100)], 7 | #["Disable Axes","M84",(400,100,100)], 8 | ###You can use gcodes or any pronsole/pronterface commands 9 | ###The first four buttons will end up at the top of the window, visible in mini mode 10 | ###The rest of the buttons will be at the bottom under the gcode preview 11 | ###ADD BUTTON DEFINITIONS BELOW THIS LINE 12 | 13 | 14 | 15 | 16 | ###ADD BUTTON DEFINITIONS ABOVE THIS LINE 17 | ] 18 | -------------------------------------------------------------------------------- /php/parser.php: -------------------------------------------------------------------------------- 1 | state . "
"; 24 | echo "Hotend: " . round($xml->hotend, 0) . "°c
"; 25 | echo "Bed: " . round($xml->bed, 0) . "°c
"; 26 | if ($xml->progress != "NA") 27 | { 28 | echo "Progress: " . $xml->progress . "%"; 29 | } 30 | } 31 | catch(Exception $e) 32 | { 33 | echo "ERROR:\n" . $e->getMessage(). " (severity " . $e->getCode() . ")"; 34 | } 35 | ?> -------------------------------------------------------------------------------- /printrun/svg/css/identifier.py: -------------------------------------------------------------------------------- 1 | """ Parse CSS identifiers. More complicated than it sounds""" 2 | 3 | from pyparsing import Word, Literal, Regex, Combine, Optional, White, oneOf, ZeroOrMore 4 | import string 5 | import re 6 | 7 | class White(White): 8 | """ Customize whitespace to match the CSS spec values""" 9 | def __init__(self, ws=" \t\r\n\f", min=1, max=0, exact=0): 10 | super(White, self).__init__(ws, min, max, exact) 11 | 12 | escaped = ( 13 | Literal("\\").suppress() + 14 | #chr(20)-chr(126) + chr(128)-unichr(sys.maxunicode) 15 | Regex(u"[\u0020-\u007e\u0080-\uffff]", re.IGNORECASE) 16 | ) 17 | 18 | def convertToUnicode(t): 19 | return unichr(int(t[0], 16)) 20 | hex_unicode = ( 21 | Literal("\\").suppress() + 22 | Regex("[0-9a-f]{1,6}", re.IGNORECASE) + 23 | Optional(White(exact=1)).suppress() 24 | ).setParseAction(convertToUnicode) 25 | 26 | 27 | escape = hex_unicode | escaped 28 | 29 | #any unicode literal outside the 0-127 ascii range 30 | nonascii = Regex(u"[^\u0000-\u007f]") 31 | 32 | #single character for starting an identifier. 33 | nmstart = Regex(u"[A-Z]", re.IGNORECASE) | nonascii | escape 34 | 35 | nmchar = Regex(u"[0-9A-Z-]", re.IGNORECASE) | nonascii | escape 36 | 37 | identifier = Combine(nmstart + ZeroOrMore(nmchar)) 38 | -------------------------------------------------------------------------------- /testfiles/PCB-milling-and-(comment).gcode: -------------------------------------------------------------------------------- 1 | (Created by G-code exporter) 2 | (Fri Apr 27 22:20:09 2012) 3 | (Board size: 100.00 x 130.00 mm) 4 | (---------------------------------) 5 | G21 6 | G90 7 | G0 X14.392 Y30.94113 Z1. 8 | G4 9 | M104 S255 10 | G1 Z-0.2 F60 11 | G1 X14.05334 Y30.60247 12 | G1 X12.02134 Y30.60247 13 | G1 X11.598 Y30.85647 14 | G1 X11.00534 Y31.5338 15 | G1 X10.074 Y32.5498 16 | G1 X9.98933 Y34.41247 17 | G0 Z1. 18 | (RepRap Gen7 v1.4) 19 | G0 Z1. 20 | (R) 21 | G0 X9.9753 Y6.9723 22 | G1 Z-0.20 F60 23 | G1 X11.4761 Y6.9723 F250 24 | G1 X11.8514 Y6.5971 25 | G1 X11.8514 Y5.8467 26 | G1 X11.4761 Y5.4715 27 | G1 X10.3505 Y5.4715 28 | G1 X11.8514 Y3.9456 29 | G0 Z1. 30 | G0 X10.3505 Y6.9723 31 | G1 Z-0.20 F60 32 | G1 X10.3505 Y3.9456 F250 33 | G0 Z1. 34 | (e) 35 | G0 X12.7519 Y4.696 36 | G1 Z-0.20 F60 37 | G1 X14.2778 Y4.696 F250 38 | G1 X14.2778 Y5.0962 39 | G1 X13.9025 Y5.4715 40 | G1 X13.1271 Y5.4715 41 | G1 X12.7519 Y5.0962 42 | G1 X12.7519 Y4.3208 43 | G1 X13.1271 Y3.9456 44 | G1 X14.2778 Y3.9456 45 | G0 Z1. 46 | (p) 47 | G0 X15.5535 Y2.8199 48 | G1 Z-0.20 F60 49 | G1 X15.5535 Y5.0962 F250 50 | G1 X15.1783 Y5.4715 51 | G1 X15.5535 Y5.0962 52 | G1 X15.9287 Y5.4715 53 | G1 X16.6792 Y5.4715 54 | G1 X17.0544 Y5.0962 55 | G1 X17.0544 Y4.3208 56 | G1 X16.6792 Y3.9456 57 | G1 X15.9287 Y3.9456 58 | G1 X15.5535 Y4.3208 59 | G0 Z1. 60 | G4 61 | M104 S0 62 | (tool change position) 63 | G1 X2. Y2. Z40. F400 64 | M2 65 | -------------------------------------------------------------------------------- /printrun/svg/css/transform.py: -------------------------------------------------------------------------------- 1 | """ 2 | Parsing for CSS and CSS-style values, such as transform and filter attributes. 3 | """ 4 | 5 | from pyparsing import (Literal, Word, CaselessLiteral, 6 | Optional, Combine, Forward, ZeroOrMore, nums, oneOf, Group, delimitedList) 7 | 8 | #some shared definitions from pathdata 9 | 10 | from ..pathdata import number, maybeComma 11 | 12 | paren = Literal("(").suppress() 13 | cparen = Literal(")").suppress() 14 | 15 | def Parenthised(exp): 16 | return Group(paren + exp + cparen) 17 | 18 | skewY = Literal("skewY") + Parenthised(number) 19 | 20 | skewX = Literal("skewX") + Parenthised(number) 21 | 22 | rotate = Literal("rotate") + Parenthised( 23 | number + Optional(maybeComma + number + maybeComma + number) 24 | ) 25 | 26 | 27 | scale = Literal("scale") + Parenthised( 28 | number + Optional(maybeComma + number) 29 | ) 30 | 31 | translate = Literal("translate") + Parenthised( 32 | number + Optional(maybeComma + number) 33 | ) 34 | 35 | matrix = Literal("matrix") + Parenthised( 36 | #there's got to be a better way to write this 37 | number + maybeComma + 38 | number + maybeComma + 39 | number + maybeComma + 40 | number + maybeComma + 41 | number + maybeComma + 42 | number 43 | ) 44 | 45 | transform = (skewY | skewX | rotate | scale | translate | matrix) 46 | 47 | transformList = delimitedList(Group(transform), delim=maybeComma) 48 | 49 | if __name__ == '__main__': 50 | from tests.test_css import * 51 | unittest.main() 52 | -------------------------------------------------------------------------------- /printrun/svg/css/values.py: -------------------------------------------------------------------------------- 1 | """ 2 | Parser for various kinds of CSS values as per CSS2 spec section 4.3 3 | """ 4 | from pyparsing import Word, Combine, Optional, Literal, oneOf, CaselessLiteral, StringEnd 5 | 6 | def asInt(s,l,t): 7 | return int(t[0]) 8 | 9 | def asFloat(s,l,t): 10 | return float(t[0]) 11 | 12 | def asFloatOrInt(s,l,t): 13 | """ Return an int if possible, otherwise a float""" 14 | v = t[0] 15 | try: 16 | return int(v) 17 | except ValueError: 18 | return float(v) 19 | 20 | integer = Word("0123456789").setParseAction(asInt) 21 | 22 | number = Combine( 23 | Optional(Word("0123456789")) + Literal(".") + Word("01234567890") 24 | | integer 25 | ) 26 | number.setName('number') 27 | 28 | 29 | sign = oneOf("+ -") 30 | 31 | signedNumber = Combine(Optional(sign) + number).setParseAction(asFloat) 32 | 33 | lengthValue = Combine(Optional(sign) + number).setParseAction(asFloatOrInt) 34 | lengthValue.setName('lengthValue') 35 | 36 | 37 | #TODO: The physical units like in, mm 38 | lengthUnit = oneOf(['em', 'ex', 'px', 'pt', '%'], caseless=True) 39 | #the spec says that the unit is only optional for a 0 length, but 40 | #there are just too many places where a default is permitted. 41 | #TODO: Maybe should use a ctor like optional to let clients declare it? 42 | length = lengthValue + Optional(lengthUnit, default=None) + StringEnd() 43 | length.leaveWhitespace() 44 | 45 | #set the parse action aftward so it doesn't "infect" the parsers that build on it 46 | number.setParseAction(asFloat) 47 | -------------------------------------------------------------------------------- /printrun/svg/attributes.py: -------------------------------------------------------------------------------- 1 | """ 2 | Parsers for specific attributes 3 | """ 4 | import urlparse 5 | from pyparsing import (Literal, 6 | Optional, oneOf, Group, StringEnd, Combine, Word, alphas, hexnums, 7 | CaselessLiteral, SkipTo 8 | ) 9 | from css.colour import colourValue 10 | import string 11 | 12 | ##Paint values 13 | none = CaselessLiteral("none").setParseAction(lambda t: ["NONE", ()]) 14 | currentColor = CaselessLiteral("currentColor").setParseAction(lambda t: ["CURRENTCOLOR", ()]) 15 | 16 | def parsePossibleURL(t): 17 | possibleURL, fallback = t[0] 18 | return [urlparse.urlsplit(possibleURL), fallback] 19 | 20 | #Normal color declaration 21 | colorDeclaration = none | currentColor | colourValue 22 | 23 | urlEnd = ( 24 | Literal(")").suppress() + 25 | Optional(Group(colorDeclaration), default = ()) + 26 | StringEnd() 27 | ) 28 | 29 | url = ( 30 | CaselessLiteral("URL") 31 | + 32 | Literal("(").suppress()+ 33 | Group(SkipTo(urlEnd, include = True).setParseAction(parsePossibleURL)) 34 | ) 35 | 36 | #paint value will parse into a (type, details) tuple. 37 | #For none and currentColor, the details tuple will be the empty tuple 38 | #for CSS color declarations, it will be (type, (R, G, B)) 39 | #for URLs, it will be ("URL", ((url tuple), fallback)) 40 | #The url tuple will be as returned by urlparse.urlsplit, and can be 41 | #an empty tuple if the parser has an error 42 | #The fallback will be another (type, details) tuple as a parsed 43 | #colorDeclaration, but may be the empty tuple if it is not present 44 | paintValue = url | colorDeclaration 45 | -------------------------------------------------------------------------------- /README.cleanup: -------------------------------------------------------------------------------- 1 | Some cleanup commands : 2 | 3 | To add a space after each comma : 4 | sed -e "s/\(\w\),\(\w\)/\1, \2/g" -i *.py printrun/*.py printrun/svg/*.py 5 | sed -e "s/\(\w\),\(\"\)/\1, \2/g" -i *.py printrun/*.py printrun/svg/*.py 6 | sed -e "s/\(\"\),\(\w\)/\1, \2/g" -i *.py printrun/*.py printrun/svg/*.py 7 | sed -e "s/\(\"\),\(\"\)/\1, \2/g" -i *.py printrun/*.py printrun/svg/*.py 8 | sed -e "s/\([)}\]]\),\(\w\)/\1, \2/g" -i *.py printrun/*.py printrun/svg/*.py 9 | sed -e "s/\([)}\]]\),\([\[{(]\)/\1, \2/g" -i *.py printrun/*.py printrun/svg/*.py 10 | sed -e "s/\(\w\),\([\[{(]\)/\1, \2/g" -i *.py printrun/*.py printrun/svg/*.py 11 | 12 | To add spaces around each = : 13 | sed -e "s/\(\w\)=\(\w\)/\1 = \2/g" -i *.py printrun/*.py printrun/svg/*.py 14 | sed -e "s/\(\w\)=\(\"\)/\1 = \2/g" -i *.py printrun/*.py printrun/svg/*.py 15 | sed -e "s/\(\w\)=\((\)/\1 = \2/g" -i *.py printrun/*.py printrun/svg/*.py 16 | sed -e "s/\(\w\)=\((\)/\1 = \2/g" -i *.py printrun/*.py printrun/svg/*.py 17 | sed -e "s/\(\w\)=\([\[{(]\)/\1 = \2/g" -i *.py printrun/*.py printrun/svg/*.py 18 | 19 | To add spaces around each == : 20 | sed -e "s/\(\w\)==\(\w\)/\1 == \2/g" -i *.py printrun/*.py printrun/svg/*.py 21 | sed -e "s/\(\w\)==\(\"\)/\1 == \2/g" -i *.py printrun/*.py printrun/svg/*.py 22 | sed -e "s/\(\w\)==\((\)/\1 == \2/g" -i *.py printrun/*.py printrun/svg/*.py 23 | sed -e "s/\()\)==\(\w\)/\1 == \2/g" -i *.py printrun/*.py printrun/svg/*.py 24 | sed -e "s/\()\)==\((\)/\1 == \2/g" -i *.py printrun/*.py printrun/svg/*.py 25 | 26 | Obviously this is not a perfect solution, it WILL break the code. Juste check the diff and fix what's wrong before commiting. 27 | -------------------------------------------------------------------------------- /printrun/svg/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | """ 4 | 5 | import wx 6 | 7 | def AddEllipticalArc(self, x, y, w, h, startAngle, endAngle, clockwise = False): 8 | """ Draws an arc of an ellipse within bounding rect (x, y, w, h) 9 | from startArc to endArc (in radians, relative to the horizontal line of the eclipse)""" 10 | 11 | if True: 12 | import warnings 13 | warnings.warn("elliptical arcs are not supported") 14 | w = w/2.0 15 | h = h/2.0 16 | self.AddArc(x+w, y+h, ((w+h)/2), startAngle, endAngle, clockwise) 17 | return 18 | else: 19 | #implement in terms of AddArc by applying a transformation matrix 20 | #Sigh this can't work, still need to patch wx to allow 21 | #either a) AddPath that's not a closed path or 22 | #b) allow pushing and popping of states on a path, not just on a context 23 | #a) is possible in GDI+, need to investigate other renderers. 24 | #b) is possible in Quartz and Cairo, but not in GDI+. It could 25 | #possibly be simulated by combining the current transform with option a. 26 | mtx = wx.GraphicsRenderer_GetDefaultRenderer().CreateMatrix() 27 | path = wx.GraphicsRenderer_GetDefaultRenderer().CreatePath() 28 | 29 | 30 | mtx.Translate(x+(w/2.0), y+(h/2.0)) 31 | mtx.Scale(w/2.0, y/2.0) 32 | 33 | path.AddArc(0, 0, 1, startAngle, endAngle, clockwise) 34 | path.Transform(mtx) 35 | self.AddPath(path) 36 | self.MoveToPoint(path.GetCurrentPoint()) 37 | self.CloseSubpath() 38 | 39 | if not hasattr(wx.GraphicsPath, "AddEllipticalArc"): 40 | wx.GraphicsPath.AddEllipticalArc = AddEllipticalArc 41 | 42 | del AddEllipticalArc 43 | -------------------------------------------------------------------------------- /README.i18n: -------------------------------------------------------------------------------- 1 | 2 | Printrun Internationalization 3 | 4 | Date: 06 August 2011 5 | Author: Jonathan Marsden 6 | 7 | Printrun is in the very early stages of being internationalized. 8 | 9 | The pronterface.py now uses gettext for the messages it generates. 10 | The corresponding pronterface.pot file is at locale/pronterface.pot 11 | and was generated using 12 | 13 | pygettext -o locale/pronterface.pot pronterface.py printrun/pronterface_widgets.py 14 | 15 | followed by minor edits to the generated header. 16 | 17 | This template is the basis for all pronterface mesage catalogs. Right 18 | now there is only one, for German. New ones can be created: 19 | 20 | # Create new pronterface message catalog for a different language 21 | newlang="es" # use the correct code for your language 22 | mkdir -p locale/${newlang}/LC_MESSAGES 23 | cp locale/pronterface.pot locale/${newlanguage}/LC_MESSAGES/pronterface.po 24 | cd locale/${newlanguage}/LC_MESSAGES/ 25 | # Edit the .po file to add messages for newlang 26 | msgfmt -o pronterface.mo pronterface.po 27 | 28 | To update a previously created message catalog from the template, use : 29 | 30 | msgmerge -U locale/fr/LC_MESSAGES/pronterface.po locale/pronterface.pot 31 | 32 | As currently coded, the default location for these message catalogs is 33 | 34 | /usr/share/pronterface/locale/ 35 | 36 | So, to install the catalogs, copy them to there: 37 | 38 | sudo cp -a locale /usr/share/pronterface/ 39 | 40 | To test pronterface in a new language, you can temporarily set LANG to 41 | the language you are testing, for example 42 | 43 | LANG=de python pronterface.py 44 | 45 | Further automation for localization and packaging of Printrun would be 46 | nice to see, but is not here yet. 47 | 48 | -------------------------------------------------------------------------------- /printrun/printrun_utils.py: -------------------------------------------------------------------------------- 1 | import os, sys 2 | import gettext 3 | 4 | # Set up Internationalization using gettext 5 | # searching for installed locales on /usr/share; uses relative folder if not found (windows) 6 | def install_locale(domain): 7 | if os.path.exists('/usr/share/pronterface/locale'): 8 | gettext.install(domain, '/usr/share/pronterface/locale', unicode = 1) 9 | elif os.path.exists('/usr/local/share/pronterface/locale'): 10 | gettext.install(domain, '/usr/local/share/pronterface/locale', unicode = 1) 11 | else: 12 | gettext.install(domain, './locale', unicode = 1) 13 | 14 | def imagefile(filename): 15 | for prefix in ['/usr/local/share/pronterface/images', '/usr/share/pronterface/images']: 16 | candidate = os.path.join(prefix, filename) 17 | if os.path.exists(candidate): 18 | return candidate 19 | local_candidate = os.path.join(os.path.dirname(sys.argv[0]), "images", filename) 20 | if os.path.exists(local_candidate): 21 | return local_candidate 22 | else: 23 | return os.path.join("images", filename) 24 | 25 | def lookup_file(filename, prefixes): 26 | for prefix in prefixes: 27 | candidate = os.path.join(prefix, filename) 28 | if os.path.exists(candidate): 29 | return candidate 30 | local_candidate = os.path.join(os.path.dirname(sys.argv[0]), filename) 31 | if os.path.exists(local_candidate): 32 | return local_candidate 33 | else: 34 | return filename 35 | 36 | def pixmapfile(filename): 37 | return lookup_file(filename, ['/usr/local/share/pixmaps', '/usr/share/pixmaps']) 38 | 39 | def sharedfile(filename): 40 | return lookup_file(filename, ['/usr/local/share/pronterface', '/usr/share/pronterface']) 41 | 42 | def configfile(filename): 43 | return lookup_file(filename, [os.path.expanduser("~/.printrun/"),]) 44 | -------------------------------------------------------------------------------- /locale/plater.pot: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR ORGANIZATION 3 | # FIRST AUTHOR , YEAR. 4 | # 5 | msgid "" 6 | msgstr "" 7 | "Project-Id-Version: PACKAGE VERSION\n" 8 | "POT-Creation-Date: 2012-08-04 21:53+CEST\n" 9 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 10 | "Last-Translator: FULL NAME \n" 11 | "Language-Team: LANGUAGE \n" 12 | "MIME-Version: 1.0\n" 13 | "Content-Type: text/plain; charset=CHARSET\n" 14 | "Content-Transfer-Encoding: ENCODING\n" 15 | "Generated-By: pygettext.py 1.5\n" 16 | 17 | 18 | #: plater.py:246 19 | msgid "Plate building tool" 20 | msgstr "" 21 | 22 | #: plater.py:252 23 | msgid "Clear" 24 | msgstr "" 25 | 26 | #: plater.py:253 27 | msgid "Load" 28 | msgstr "" 29 | 30 | #: plater.py:255 plater.py:258 31 | msgid "Export" 32 | msgstr "" 33 | 34 | #: plater.py:260 35 | msgid "Done" 36 | msgstr "" 37 | 38 | #: plater.py:262 39 | msgid "Cancel" 40 | msgstr "" 41 | 42 | #: plater.py:264 43 | msgid "Snap to Z = 0" 44 | msgstr "" 45 | 46 | #: plater.py:265 47 | msgid "Put at 100, 100" 48 | msgstr "" 49 | 50 | #: plater.py:266 51 | msgid "Delete" 52 | msgstr "" 53 | 54 | #: plater.py:267 55 | msgid "Auto" 56 | msgstr "" 57 | 58 | #: plater.py:291 59 | msgid "Autoplating" 60 | msgstr "" 61 | 62 | #: plater.py:319 63 | msgid "Bed full, sorry sir :(" 64 | msgstr "" 65 | 66 | #: plater.py:329 67 | msgid "Are you sure you want to clear the grid? All unsaved changes will be lost." 68 | msgstr "" 69 | 70 | #: plater.py:329 71 | msgid "Clear the grid?" 72 | msgstr "" 73 | 74 | #: plater.py:371 75 | msgid "Pick file to save to" 76 | msgstr "" 77 | 78 | #: plater.py:372 79 | msgid "STL files (;*.stl;*.STL;)" 80 | msgstr "" 81 | 82 | #: plater.py:393 83 | msgid "wrote %s" 84 | msgstr "" 85 | 86 | #: plater.py:396 87 | msgid "Pick file to load" 88 | msgstr "" 89 | 90 | #: plater.py:397 91 | msgid "STL files (;*.stl;*.STL;)|*.stl|OpenSCAD files (;*.scad;)|*.scad" 92 | msgstr "" 93 | 94 | -------------------------------------------------------------------------------- /locale/de/LC_MESSAGES/plater.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "Project-Id-Version: Plater\n" 4 | "POT-Creation-Date: 2012-01-09 15:07+CET\n" 5 | "PO-Revision-Date: \n" 6 | "Last-Translator: Christian Metzen \n" 7 | "Language-Team: \n" 8 | "MIME-Version: 1.0\n" 9 | "Content-Type: text/plain; charset=UTF-8\n" 10 | "Content-Transfer-Encoding: 8bit\n" 11 | "X-Poedit-Language: German\n" 12 | "X-Poedit-Country: GERMANY\n" 13 | 14 | #: plater.py:223 15 | msgid "Plate building tool" 16 | msgstr "Plate building tool" 17 | 18 | #: plater.py:229 19 | msgid "Clear" 20 | msgstr "Löschen" 21 | 22 | #: plater.py:230 23 | msgid "Load" 24 | msgstr "Laden" 25 | 26 | #: plater.py:232 27 | msgid "Export" 28 | msgstr "Exportieren" 29 | 30 | #: plater.py:235 31 | msgid "Done" 32 | msgstr "Fertig" 33 | 34 | #: plater.py:237 35 | msgid "Cancel" 36 | msgstr "Abbrechen" 37 | 38 | #: plater.py:239 39 | msgid "Snap to Z = 0" 40 | msgstr "Einrasten auf Z = 0" 41 | 42 | #: plater.py:240 43 | msgid "Put at 100, 100" 44 | msgstr "Auf 100, 100 setzen" 45 | 46 | #: plater.py:241 47 | msgid "Delete" 48 | msgstr "Löschen" 49 | 50 | #: plater.py:242 51 | msgid "Auto" 52 | msgstr "Auto" 53 | 54 | #: plater.py:266 55 | msgid "Autoplating" 56 | msgstr "Autoplating" 57 | 58 | #: plater.py:294 59 | msgid "Bed full, sorry sir :(" 60 | msgstr "Das Druckbett ist voll! Sorry." 61 | 62 | #: plater.py:304 63 | msgid "Are you sure you want to clear the grid? All unsaved changes will be lost." 64 | msgstr "Bist du sicher dass du das Raster leeren willst? Alle ungesicherten Änderungen gehen verloren." 65 | 66 | #: plater.py:304 67 | msgid "Clear the grid?" 68 | msgstr "Raster leeren?" 69 | 70 | #: plater.py:346 71 | msgid "Pick file to save to" 72 | msgstr "Wähle die zu sichernde Datei" 73 | 74 | #: plater.py:347 75 | msgid "STL files (;*.stl;)" 76 | msgstr "STL Dateien (;*.stl;)" 77 | 78 | #: plater.py:367 79 | msgid "wrote " 80 | msgstr "geschrieben" 81 | 82 | #: plater.py:370 83 | msgid "Pick file to load" 84 | msgstr "Wähle die zu ladende Datei" 85 | 86 | #: plater.py:371 87 | msgid "STL files (;*.stl;)|*.stl|OpenSCAD files (;*.scad;)|*.scad" 88 | msgstr "STL Dateien (;*.stl;)|*.stl|OpenSCAD Dateien (;*.scad;)|*.scad" 89 | 90 | -------------------------------------------------------------------------------- /locale/it/LC_MESSAGES/plater.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR ORGANIZATION 3 | # FIRST AUTHOR , YEAR. 4 | # 5 | msgid "" 6 | msgstr "" 7 | "Project-Id-Version: PACKAGE VERSION\n" 8 | "POT-Creation-Date: 2012-01-09 15:07+CET\n" 9 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 10 | "Last-Translator: Italian RepRap Community \n" 11 | "Language-Team: Italian RepRap Community \n" 12 | "MIME-Version: 1.0\n" 13 | "Content-Type: text/plain; charset=UTF-8\n" 14 | "Content-Transfer-Encoding: 8bit\n" 15 | "Generated-By: pygettext.py 1.5\n" 16 | 17 | 18 | #: plater.py:223 19 | msgid "Plate building tool" 20 | msgstr "Strumento di posizionamento sul piatto" 21 | 22 | #: plater.py:229 23 | msgid "Clear" 24 | msgstr "Pulisci" 25 | 26 | #: plater.py:230 27 | msgid "Load" 28 | msgstr "Carica" 29 | 30 | #: plater.py:232 31 | msgid "Export" 32 | msgstr "Esporta" 33 | 34 | #: plater.py:235 35 | msgid "Done" 36 | msgstr "Fatto" 37 | 38 | #: plater.py:237 39 | msgid "Cancel" 40 | msgstr "Cancella" 41 | 42 | #: plater.py:239 43 | msgid "Snap to Z = 0" 44 | msgstr "Vai a Z = 0" 45 | 46 | #: plater.py:240 47 | msgid "Put at 100, 100" 48 | msgstr "Metti a 100,100" 49 | 50 | #: plater.py:241 51 | msgid "Delete" 52 | msgstr "Elimina" 53 | 54 | #: plater.py:242 55 | msgid "Auto" 56 | msgstr "Automatico" 57 | 58 | #: plater.py:266 59 | msgid "Autoplating" 60 | msgstr "Posizionamento automatico sul piatto" 61 | 62 | #: plater.py:294 63 | msgid "Bed full, sorry sir :(" 64 | msgstr "Il letto è pieno, mi dispiace :(" 65 | 66 | #: plater.py:304 67 | msgid "Are you sure you want to clear the grid? All unsaved changes will be lost." 68 | msgstr "Sei sicuro di voler pulire la griglia? Tutte le modifiche non salvate saranno perse." 69 | 70 | #: plater.py:304 71 | msgid "Clear the grid?" 72 | msgstr "Pulire la griglia?" 73 | 74 | #: plater.py:346 75 | msgid "Pick file to save to" 76 | msgstr "Scegli un file in cui salvare" 77 | 78 | #: plater.py:347 79 | msgid "STL files (;*.stl;)" 80 | msgstr "files STL (;*.stl;)" 81 | 82 | #: plater.py:367 83 | msgid "wrote " 84 | msgstr "scritti " 85 | 86 | #: plater.py:370 87 | msgid "Pick file to load" 88 | msgstr "Scegli un file da caricare" 89 | 90 | #: plater.py:371 91 | msgid "STL files (;*.stl;)|*.stl|OpenSCAD files (;*.scad;)|*.scad" 92 | msgstr "files STL (;*.stl;)|*.stl|files OpenSCAD (;*.scad;)|*.scad" 93 | -------------------------------------------------------------------------------- /locale/fr/LC_MESSAGES/plater.po: -------------------------------------------------------------------------------- 1 | # French Plater Message Catalog 2 | # Copyright (C) 2012 Guillaume Seguin 3 | # Guillaume Seguin , 2012. 4 | # 5 | msgid "" 6 | msgstr "" 7 | "Project-Id-Version: Plater\n" 8 | "POT-Creation-Date: 2012-08-04 21:53+CEST\n" 9 | "PO-Revision-Date: 2012-02-26 02:41+0100\n" 10 | "Last-Translator: Guillaume Seguin \n" 11 | "Language-Team: FR \n" 12 | "Language: \n" 13 | "MIME-Version: 1.0\n" 14 | "Content-Type: text/plain; charset=UTF-8\n" 15 | "Content-Transfer-Encoding: 8bit\n" 16 | "Generated-By: pygettext.py 1.5\n" 17 | 18 | #: plater.py:246 19 | msgid "Plate building tool" 20 | msgstr "Outil d'assemblage de plateau" 21 | 22 | #: plater.py:252 23 | msgid "Clear" 24 | msgstr "Vider" 25 | 26 | #: plater.py:253 27 | msgid "Load" 28 | msgstr "Charger" 29 | 30 | #: plater.py:255 plater.py:258 31 | msgid "Export" 32 | msgstr "Exporter" 33 | 34 | #: plater.py:260 35 | msgid "Done" 36 | msgstr "Terminé" 37 | 38 | #: plater.py:262 39 | msgid "Cancel" 40 | msgstr "Annuler" 41 | 42 | #: plater.py:264 43 | msgid "Snap to Z = 0" 44 | msgstr "Poser en Z = 0" 45 | 46 | #: plater.py:265 47 | msgid "Put at 100, 100" 48 | msgstr "Placer en 100, 100" 49 | 50 | #: plater.py:266 51 | msgid "Delete" 52 | msgstr "Supprimer" 53 | 54 | #: plater.py:267 55 | msgid "Auto" 56 | msgstr "Auto" 57 | 58 | #: plater.py:291 59 | msgid "Autoplating" 60 | msgstr "Placement auto" 61 | 62 | #: plater.py:319 63 | msgid "Bed full, sorry sir :(" 64 | msgstr "Le lit est plein, désolé :(" 65 | 66 | #: plater.py:329 67 | msgid "" 68 | "Are you sure you want to clear the grid? All unsaved changes will be lost." 69 | msgstr "" 70 | "Êtes vous sur de vouloir vider la grille ? Toutes les modifications non " 71 | "enregistrées seront perdues." 72 | 73 | #: plater.py:329 74 | msgid "Clear the grid?" 75 | msgstr "Vider la grille ?" 76 | 77 | #: plater.py:371 78 | msgid "Pick file to save to" 79 | msgstr "Choisir le fichier dans lequel enregistrer" 80 | 81 | #: plater.py:372 82 | msgid "STL files (;*.stl;*.STL;)" 83 | msgstr "Fichiers STL (;*.stl;*.STL;)" 84 | 85 | #: plater.py:393 86 | msgid "wrote %s" 87 | msgstr "%s écrit" 88 | 89 | #: plater.py:396 90 | msgid "Pick file to load" 91 | msgstr "Choisir le fichier à charger" 92 | 93 | #: plater.py:397 94 | msgid "STL files (;*.stl;*.STL;)|*.stl|OpenSCAD files (;*.scad;)|*.scad" 95 | msgstr "Fichiers STL (;*.stl;*.STL;)|*.stl|Fichiers OpenSCAD (;*.scad;)|*.scad" 96 | -------------------------------------------------------------------------------- /printrun/bmpDisplay.py: -------------------------------------------------------------------------------- 1 | # create a simple image slide show using the 2 | # wx.PaintDC surface as a canvas and 3 | # DrawBitmap(bitmap, x, y, bool transparent) 4 | # Source: vegaseat 5 | 6 | import wx 7 | import os 8 | import zipfile 9 | import tempfile 10 | import shutil 11 | 12 | class MyFrame(wx.Frame): 13 | def __init__(self, parent, mysize): 14 | wx.Frame.__init__(self, parent, wx.ID_ANY, size = mysize) 15 | self.SetBackgroundColour('black') 16 | 17 | # milliseconds per frame 18 | self.delay = 60 19 | # number of loops 20 | self.loops = 1 21 | 22 | zipfilename = 'images/out.3dlp.zip' 23 | if not zipfile.is_zipfile(zipfilename): 24 | raise Exception(zipfilename + " is not a zip file!") 25 | zip = zipfile.ZipFile(zipfilename, 'r') 26 | self.mytmpdir = tempfile.mkdtemp() 27 | zip.extractall(self.mytmpdir) 28 | 29 | image_type = ".bmp" 30 | image_dir = self.mytmpdir 31 | file_list = [] 32 | self.name_list = [] 33 | for file in os.listdir(image_dir): 34 | path = os.path.join(image_dir, file) 35 | if os.path.isfile(path) and path.endswith(image_type): 36 | # just the file name 37 | self.name_list.append(file) 38 | # full path name 39 | file_list.append(path) 40 | # create a list of image objects 41 | self.image_list = [] 42 | for image_file in file_list: 43 | self.image_list.append(wx.Bitmap(image_file)) 44 | 45 | # bind the panel to the paint event 46 | wx.EVT_PAINT(self, self.onPaint) 47 | 48 | def __del__(self): 49 | if self.mytmpdir: 50 | shutil.rmtree(self.mytmpdir) 51 | 52 | def onPaint(self, event = None): 53 | # this is the wxPython drawing surface/canvas 54 | dc = wx.PaintDC(self) 55 | while self.loops: 56 | self.loops -= 1 57 | for ix, bmp in enumerate(self.image_list): 58 | # optionally show some image information 59 | w, h = bmp.GetSize() 60 | info = "%s %dx%d" % (self.name_list[ix], w, h) 61 | self.SetTitle(info) 62 | #self.SetSize((w, h)) 63 | # draw the image 64 | dc.DrawBitmap(bmp, 0, 0, True) 65 | wx.MilliSleep(self.delay) 66 | # don't clear on fast slide shows to avoid flicker 67 | if self.delay > 200: 68 | dc.Clear() 69 | 70 | 71 | app = wx.App() 72 | width = 800 73 | frameoffset = 35 74 | height = 600 + frameoffset 75 | MyFrame(None, (width, height)).Show() 76 | app.MainLoop() 77 | -------------------------------------------------------------------------------- /printrun/bufferedcanvas.py: -------------------------------------------------------------------------------- 1 | """ 2 | BufferedCanvas -- flicker-free canvas widget 3 | Copyright (C) 2005, 2006 Daniel Keep, 2011 Duane Johnson 4 | 5 | To use this widget, just override or replace the draw method. 6 | This will be called whenever the widget size changes, or when 7 | the update method is explicitly called. 8 | 9 | Please submit any improvements/bugfixes/ideas to the following 10 | url: 11 | 12 | http://wiki.wxpython.org/index.cgi/BufferedCanvas 13 | 14 | 2006-04-29: Added bugfix for a crash on Mac provided by Marc Jans. 15 | """ 16 | 17 | # Hint: try removing '.sp4msux0rz' 18 | __author__ = 'Daniel Keep ' 19 | 20 | __license__ = """ 21 | This library is free software; you can redistribute it and/or 22 | modify it under the terms of the GNU Lesser General Public License as 23 | published by the Free Software Foundation; either version 2.1 of the 24 | License, or (at your option) any later version. 25 | 26 | As a special exception, the copyright holders of this library 27 | hereby recind Section 3 of the GNU Lesser General Public License. This 28 | means that you MAY NOT apply the terms of the ordinary GNU General 29 | Public License instead of this License to any given copy of the 30 | Library. This has been done to prevent users of the Library from being 31 | denied access or the ability to use future improvements. 32 | 33 | This library is distributed in the hope that it will be useful, but 34 | WITHOUT ANY WARRANTY; without even the implied warranty of 35 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser 36 | General Public License for more details. 37 | 38 | You should have received a copy of the GNU Lesser General Public 39 | License along with this library; if not, write to the Free Software 40 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 41 | """ 42 | 43 | __all__ = ['BufferedCanvas'] 44 | 45 | import wx 46 | 47 | class BufferedCanvas(wx.Panel): 48 | """ 49 | Implements a flicker-free canvas widget. 50 | 51 | Standard usage is to subclass this class, and override the 52 | draw method. The draw method is passed a device context, which 53 | should be used to do your drawing. 54 | 55 | If you want to force a redraw (for whatever reason), you should 56 | call the update method. This is because the draw method is never 57 | called as a result of an EVT_PAINT event. 58 | """ 59 | 60 | # These are our two buffers. Just be aware that when the buffers 61 | # are flipped, the REFERENCES are swapped. So I wouldn't want to 62 | # try holding onto explicit references to one or the other ;) 63 | buffer = None 64 | backbuffer = None 65 | 66 | def __init__(self, 67 | parent, 68 | ID=-1, 69 | pos = wx.DefaultPosition, 70 | size = wx.DefaultSize, 71 | style = wx.NO_FULL_REPAINT_ON_RESIZE|wx.WANTS_CHARS): 72 | wx.Panel.__init__(self, parent, ID, pos, size, style) 73 | 74 | # Bind events 75 | self.Bind(wx.EVT_PAINT, self.onPaint) 76 | 77 | # Disable background erasing (flicker-licious) 78 | def disable_event(*pargs,**kwargs): 79 | pass # the sauce, please 80 | self.Bind(wx.EVT_ERASE_BACKGROUND, disable_event) 81 | 82 | ## 83 | ## General methods 84 | ## 85 | 86 | def draw(self, dc): 87 | """ 88 | Stub: called when the canvas needs to be re-drawn. 89 | """ 90 | pass 91 | 92 | def update(self): 93 | """ 94 | Causes the canvas to be updated. 95 | """ 96 | self.Refresh() 97 | 98 | def getWidthHeight(self): 99 | width, height = self.GetClientSizeTuple() 100 | if width == 0: 101 | width = 1 102 | if height == 0: 103 | height = 1 104 | return (width, height) 105 | 106 | ## 107 | ## Event handlers 108 | ## 109 | 110 | def onPaint(self, event): 111 | # Blit the front buffer to the screen 112 | w, h = self.GetClientSizeTuple() 113 | if not w or not h: 114 | return 115 | else: 116 | dc = wx.BufferedPaintDC(self) 117 | self.draw(dc, w, h) 118 | -------------------------------------------------------------------------------- /css/style.css: -------------------------------------------------------------------------------- 1 | #title 2 | { 3 | text-align:center; 4 | color:red; 5 | } 6 | 7 | #mainmenu 8 | { 9 | margin: 0; 10 | padding: 0 0 20px 10px; 11 | border-bottom: 1px solid #000; 12 | } 13 | #mainmenu ul, #mainmenu li 14 | { 15 | margin: 0; 16 | padding: 0; 17 | display: inline; 18 | list-style-type: none; 19 | } 20 | 21 | #mainmenu a:link, #mainmenu a:visited 22 | { 23 | float: left; 24 | line-height: 14px; 25 | font-weight: bold; 26 | margin: 0 10px 4px 10px; 27 | text-decoration: none; 28 | color: #999; 29 | } 30 | 31 | #mainmenu a:link#current, #mainmenu a:visited#current, #mainmenu a:hover 32 | { 33 | border-bottom: 4px solid #000; 34 | padding-bottom: 2px; 35 | background: transparent; 36 | color: #000; 37 | } 38 | 39 | #mainmenu a:hover { color: #000; } 40 | 41 | #content{ 42 | padding-top: 25px; 43 | } 44 | #controls{ 45 | float:left; 46 | padding:0 0 1em 0; 47 | overflow:hidden; 48 | width:71%; /* right column content width */ 49 | left:102%; /* 100% plus left column left padding */ 50 | } 51 | #control_xy{ 52 | display:inline; 53 | } 54 | 55 | #control_z{ 56 | display:inline; 57 | position:absolute; 58 | } 59 | 60 | #gui{ 61 | float:left; 62 | padding:0 0 1em 0; 63 | overflow:hidden; 64 | width:21%; /* left column content width (column width minus left and right padding) */ 65 | left:6%; /* (right column left and right padding) plus (left column left padding) */ 66 | 67 | } 68 | #controls 69 | { 70 | width:21%; /* Width of left column content (column width minus padding on either side) */ 71 | left:31%; /* width of (right column) plus (center column left and right padding) plus (left column left padding) */ 72 | 73 | } 74 | 75 | #controls ul 76 | { 77 | list-style: none; 78 | margin: 0px; 79 | padding: 0px; 80 | border: none; 81 | } 82 | 83 | #controls ul li 84 | { 85 | margin: 0px; 86 | padding: 0px; 87 | } 88 | 89 | #controls ul li a 90 | { 91 | font-size: 80%; 92 | display: block; 93 | border-bottom: 1px dashed #C39C4E; 94 | padding: 5px 0px 2px 4px; 95 | text-decoration: none; 96 | color: #666666; 97 | width:160px; 98 | } 99 | 100 | #controls ul li a:hover, #controls ul li a:focus 101 | { 102 | color: #000000; 103 | background-color: #eeeeee; 104 | } 105 | 106 | #settings 107 | { 108 | margin: 0px; 109 | padding-top: 50px; 110 | border: none; 111 | } 112 | 113 | #settings table 114 | { 115 | font-family: verdana,arial,sans-serif; 116 | font-size:11px; 117 | color:#333333; 118 | border-width: 1px; 119 | border-color: #999999; 120 | border-collapse: collapse; 121 | } 122 | #settings table th { 123 | background-color:#c3dde0; 124 | border-width: 1px; 125 | padding: 8px; 126 | border-style: solid; 127 | border-color: #a9c6c9; 128 | } 129 | #settings table tr { 130 | background-color:#d4e3e5; 131 | } 132 | #settings table td { 133 | border-width: 1px; 134 | padding: 8px; 135 | border-style: solid; 136 | border-color: #a9c6c9; 137 | } 138 | 139 | #status{ 140 | 141 | } 142 | 143 | #console{ 144 | 145 | } 146 | 147 | #file{ 148 | position:relative; 149 | float:left; 150 | width:100%; 151 | height:20px; /* Height of the footer */ 152 | background:#eee; 153 | } 154 | 155 | #logframe{ 156 | 157 | } 158 | 159 | #temp{ 160 | } 161 | #tempmenu 162 | { 163 | padding: 0 0 10px 10px; 164 | position: relative; 165 | float: left; 166 | width: 100%; 167 | } 168 | #tempmenu ul, #tempmenu li 169 | { 170 | margin: 0; 171 | display: inline; 172 | list-style-type: none; 173 | } 174 | 175 | #tempmenu b 176 | { 177 | padding-top: 4px; 178 | float: left; 179 | line-height: 14px; 180 | font-weight: bold; 181 | color: #888; 182 | margin: 0 10px 4px 10px; 183 | text-decoration: none; 184 | color: #999; 185 | } 186 | 187 | #tempmenu a:link, #tempmenu a:visited 188 | { 189 | float: left; 190 | border-bottom: 1px solid #000; 191 | line-height: 14px; 192 | font-weight: bold; 193 | margin: 0 10px 4px 10px; 194 | text-decoration: none; 195 | color: #999; 196 | } 197 | 198 | #tempmenu a:link#tempmenu, #tempmenu a:visited#current, #tempmenu a:hover 199 | { 200 | border-bottom: 2px solid #000; 201 | padding-bottom: 2px; 202 | background: transparent; 203 | color: #000; 204 | } 205 | 206 | #tempmenu a:hover { color: #000; } -------------------------------------------------------------------------------- /printrun/zscaper.py: -------------------------------------------------------------------------------- 1 | # This file is part of the Printrun suite. 2 | # 3 | # Printrun is free software: you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation, either version 3 of the License, or 6 | # (at your option) any later version. 7 | # 8 | # Printrun is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with Printrun. If not, see . 15 | 16 | import wx, math 17 | from stltool import * 18 | a = wx.App() 19 | 20 | def genscape(data = [[0, 1, 0, 0],[1, 0, 2, 0],[1, 0, 0, 0],[0, 1, 0, 1]], pscale = 1.0, bheight = 1.0, zscale = 1.0): 21 | o = stl(None) 22 | datal = len(data) 23 | datah = len(data[0]) 24 | #create bottom: 25 | bmidpoint = (pscale*(datal-1)/2.0, pscale*(datah-1)/2.0) 26 | #print range(datal), bmidpoint 27 | for i in zip(range(datal+1)[:-1], range(datal+1)[1:])[:-1]: 28 | #print (pscale*i[0], pscale*i[1]) 29 | o.facets+=[[[0, 0,-1],[[0.0, pscale*i[0], 0.0],[0.0, pscale*i[1], 0.0],[bmidpoint[0], bmidpoint[1], 0.0]]]] 30 | o.facets+=[[[0, 0,-1],[[2.0*bmidpoint[1], pscale*i[1], 0.0],[2.0*bmidpoint[1], pscale*i[0], 0.0],[bmidpoint[0], bmidpoint[1], 0.0]]]] 31 | o.facets+=[genfacet([[0.0, pscale*i[0], data[i[0]][0]*zscale+bheight],[0.0, pscale*i[1], data[i[1]][0]*zscale+bheight],[0.0, pscale*i[1], 0.0]])] 32 | o.facets+=[genfacet([[2.0*bmidpoint[1], pscale*i[1], data[i[1]][datah-1]*zscale+bheight],[2.0*bmidpoint[1], pscale*i[0], data[i[0]][datah-1]*zscale+bheight],[2.0*bmidpoint[1], pscale*i[1], 0.0]])] 33 | o.facets+=[genfacet([[0.0, pscale*i[0], data[i[0]][0]*zscale+bheight],[0.0, pscale*i[1], 0.0],[0.0, pscale*i[0], 0.0]])] 34 | o.facets+=[genfacet([[2.0*bmidpoint[1], pscale*i[1], 0.0],[2.0*bmidpoint[1], pscale*i[0], data[i[0]][datah-1]*zscale+bheight],[2.0*bmidpoint[1], pscale*i[0], 0.0]])] 35 | #print o.facets[-1] 36 | pass 37 | #print o.facets[-4:] 38 | for i in zip(range(datah+1)[:-1], range(datah+1)[1:])[:-1]: 39 | #print (pscale*i[0], pscale*i[1]) 40 | o.facets+=[[[0, 0,-1],[[pscale*i[1], 0.0, 0.0],[pscale*i[0], 0.0, 0.0],[bmidpoint[0], bmidpoint[1], 0.0]]]] 41 | o.facets+=[[[0, 0,-1],[[pscale*i[0], 2.0*bmidpoint[0], 0.0],[pscale*i[1], 2.0*bmidpoint[0], 0.0],[bmidpoint[0], bmidpoint[1], 0.0]]]] 42 | o.facets+=[genfacet([[pscale*i[1], 0.0, data[0][i[1]]*zscale+bheight],[pscale*i[0], 0.0, data[0][i[0]]*zscale+bheight],[pscale*i[1], 0.0, 0.0]])] 43 | #break 44 | o.facets+=[genfacet([[pscale*i[0], 2.0*bmidpoint[0], data[datal-1][i[0]]*zscale+bheight],[pscale*i[1], 2.0*bmidpoint[0], data[datal-1][i[1]]*zscale+bheight],[pscale*i[1], 2.0*bmidpoint[0], 0.0]])] 45 | o.facets+=[genfacet([[pscale*i[1], 0.0, 0.0],[pscale*i[0], 0.0, data[0][i[0]]*zscale+bheight],[pscale*i[0], 0.0, 0.0]])] 46 | o.facets+=[genfacet([[pscale*i[0], 2.0*bmidpoint[0], data[datal-1][i[0]]*zscale+bheight],[pscale*i[1], 2.0*bmidpoint[0], 0.0],[pscale*i[0], 2.0*bmidpoint[0], 0.0]])] 47 | pass 48 | for i in xrange(datah-1): 49 | for j in xrange(datal-1): 50 | o.facets+=[genfacet([[pscale*i, pscale*j, data[j][i]*zscale+bheight],[pscale*(i+1), pscale*(j), data[j][i+1]*zscale+bheight],[pscale*(i+1), pscale*(j+1), data[j+1][i+1]*zscale+bheight]])] 51 | o.facets+=[genfacet([[pscale*(i), pscale*(j+1), data[j+1][i]*zscale+bheight],[pscale*i, pscale*j, data[j][i]*zscale+bheight],[pscale*(i+1), pscale*(j+1), data[j+1][i+1]*zscale+bheight]])] 52 | #print o.facets[-1] 53 | facet = [[0, 0, 0],[[0, 0, 0],[0, 0, 0],[0, 0, 0]]] 54 | return o 55 | def zimage(name, out): 56 | i = wx.Image(name) 57 | s = i.GetSize() 58 | print len(map(ord, i.GetData()[::3])) 59 | b = map(ord, i.GetData()[::3]) 60 | data = [] 61 | for i in xrange(s[0]): 62 | data+=[b[i*s[1]:(i+1)*s[1]]] 63 | #data = [i[::5] for i in data[::5]] 64 | emitstl(out, genscape(data, zscale = 0.1).facets, name) 65 | 66 | """ 67 | class scapewin(wx.Frame): 68 | def __init__(self, size = (400, 530)): 69 | wx.Frame.__init__(self, None, title = "Right-click to load an image", size = size) 70 | self.SetIcon(wx.Icon("plater.ico", wx.BITMAP_TYPE_ICO)) 71 | self.SetClientSize(size) 72 | self.panel = wx.Panel(self, size = size) 73 | 74 | 75 | """ 76 | if __name__ == '__main__': 77 | """ 78 | app = wx.App(False) 79 | main = scapewin() 80 | main.Show() 81 | app.MainLoop() 82 | """ 83 | zimage("catposthtmap2.jpg", "testobj.stl") 84 | del a 85 | -------------------------------------------------------------------------------- /printrun/calibrateextruder.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | #Interactive RepRap e axis calibration program 3 | #(C) Nathan Zadoks 2011 4 | #Licensed under CC-BY-SA or GPLv2 and higher - Pick your poison. 5 | s = 300 #Extrusion speed (mm/min) 6 | n = 100 #Default length to extrude 7 | m= 0 #User-entered measured extrusion length 8 | k = 300 #Default amount of steps per mm 9 | port='/dev/ttyUSB0' #Default serial port to connect to printer 10 | temp = 210 #Default extrusion temperature 11 | 12 | tempmax = 250 #Maximum extrusion temperature 13 | 14 | t = int(n*60)/s #Time to wait for extrusion 15 | 16 | try: 17 | from printdummy import printcore 18 | except ImportError: 19 | from printcore import printcore 20 | import time, getopt, sys, os 21 | 22 | def float_input(prompt=''): 23 | import sys 24 | f = None 25 | while f == None: 26 | s = raw_input(prompt) 27 | try: 28 | f = float(s) 29 | except ValueError: 30 | sys.stderr.write("Not a valid floating-point number.\n") 31 | sys.stderr.flush() 32 | return f 33 | def wait(t, m=''): 34 | import time, sys 35 | sys.stdout.write(m+'['+(' '*t)+']\r'+m+'[') 36 | sys.stdout.flush() 37 | for i in range(t): 38 | for s in ['|\b','/\b','-\b','\\\b','|']: 39 | sys.stdout.write(s) 40 | sys.stdout.flush() 41 | time.sleep(1.0/5) 42 | print 43 | def w(s): 44 | sys.stdout.write(s) 45 | sys.stdout.flush() 46 | 47 | 48 | def heatup(p, temp, s = 0): 49 | curtemp = gettemp(p) 50 | p.send_now('M109 S%03d'%temp) 51 | p.temp = 0 52 | if not s: w("Heating extruder up..") 53 | f = False 54 | while curtemp<=(temp-1): 55 | p.send_now('M105') 56 | time.sleep(0.5) 57 | if not f: 58 | time.sleep(1.5) 59 | f = True 60 | curtemp = gettemp(p) 61 | if curtemp: w(u"\rHeating extruder up.. %3d \xb0C"%curtemp) 62 | if s: print 63 | else: print "\nReady." 64 | 65 | def gettemp(p): 66 | try: p.logl 67 | except: setattr(p,'logl',0) 68 | try: p.temp 69 | except: setattr(p,'temp',0) 70 | for n in range(p.logl, len(p.log)): 71 | line = p.log[n] 72 | if 'T:' in line: 73 | try: 74 | setattr(p,'temp',int(line.split('T:')[1].split()[0])) 75 | except: print line 76 | p.logl = len(p.log) 77 | return p.temp 78 | if not os.path.exists(port): 79 | port = 0 80 | 81 | #Parse options 82 | help = u""" 83 | %s [ -l DISTANCE ] [ -s STEPS ] [ -t TEMP ] [ -p PORT ] 84 | -l --length Length of filament to extrude for each calibration step (default: %d mm) 85 | -s --steps Initial amount of steps to use (default: %d steps) 86 | -t --temp Extrusion temperature in degrees Celsius (default: %d \xb0C, max %d \xb0C) 87 | -p --port Serial port the printer is connected to (default: %s) 88 | -h --help This cruft. 89 | """[1:-1].encode('utf-8')%(sys.argv[0], n, k, temp, tempmax, port if port else 'auto') 90 | try: 91 | opts, args = getopt.getopt(sys.argv[1:],"hl:s:t:p:",["help", "length=", "steps=", "temp=", "port="]) 92 | except getopt.GetoptError, err: 93 | print str(err) 94 | print help 95 | sys.exit(2) 96 | for o, a in opts: 97 | if o in ('-h','--help'): 98 | print help 99 | sys.exit() 100 | elif o in ('-l','--length'): 101 | n = float(a) 102 | elif o in ('-s','--steps'): 103 | k = int(a) 104 | elif o in ('-t','--temp'): 105 | temp = int(a) 106 | if temp>=tempmax: 107 | print (u'%d \xb0C? Are you insane?'.encode('utf-8')%temp)+(" That's over nine thousand!" if temp>9000 else '') 108 | sys.exit(255) 109 | elif o in ('-p','--port'): 110 | port = a 111 | 112 | #Show initial parameters 113 | print "Initial parameters" 114 | print "Steps per mm: %3d steps"%k 115 | print "Length extruded: %3d mm"%n 116 | print 117 | print "Serial port: %s"%(port if port else 'auto') 118 | 119 | p = None 120 | try: 121 | #Connect to printer 122 | w("Connecting to printer..") 123 | try: 124 | p = printcore(port, 115200) 125 | except: 126 | print 'Error.' 127 | raise 128 | while not p.online: 129 | time.sleep(1) 130 | w('.') 131 | print " connected." 132 | 133 | heatup(p, temp) 134 | 135 | #Calibration loop 136 | while n!=m: 137 | heatup(p, temp, True) 138 | p.send_now("G92 E0") #Reset e axis 139 | p.send_now("G1 E%d F%d"%(n, s)) #Extrude length of filament 140 | wait(t,'Extruding.. ') 141 | m = float_input("How many millimeters of filament were extruded? ") 142 | if m == 0: continue 143 | if n!=m: 144 | k = (n/m)*k 145 | p.send_now("M92 E%d"%int(round(k))) #Set new step count 146 | print "Steps per mm: %3d steps"%k #Tell user 147 | print 'Calibration completed.' #Yay! 148 | except KeyboardInterrupt: 149 | pass 150 | finally: 151 | if p: p.disconnect() 152 | -------------------------------------------------------------------------------- /gcoder.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # This file is copied from GCoder. 3 | # 4 | # GCoder is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # GCoder is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with Printrun. If not, see . 16 | 17 | import sys 18 | import re 19 | 20 | class Line(object): 21 | def __init__(self,l): 22 | self._x = None 23 | self._y = None 24 | self._z = None 25 | self.e = None 26 | self.f = 0 27 | 28 | self.regex = re.compile("[-]?\d+[.]?\d*") 29 | self.raw = l.upper().lstrip() 30 | self.imperial = False 31 | self.relative = False 32 | 33 | if ";" in self.raw: 34 | self.raw = self.raw.split(";")[0] 35 | 36 | self._parse_coordinates() 37 | 38 | def _to_mm(self,v): 39 | if v and self.imperial: 40 | return v*25.4 41 | return v 42 | 43 | def _getx(self): 44 | return self._to_mm(self._x) 45 | 46 | def _setx(self,v): 47 | self._x = v 48 | 49 | def _gety(self): 50 | return self._to_mm(self._y) 51 | 52 | def _sety(self,v): 53 | self._y = v 54 | 55 | def _getz(self): 56 | return self._to_mm(self._z) 57 | 58 | def _setz(self,v): 59 | self._z = v 60 | 61 | def _gete(self): 62 | return self._to_mm(self._e) 63 | 64 | def _sete(self,v): 65 | self._e = v 66 | 67 | x = property(_getx,_setx) 68 | y = property(_gety,_sety) 69 | z = property(_getz,_setz) 70 | e = property(_gete,_sete) 71 | 72 | 73 | def command(self): 74 | try: 75 | return self.raw.split(" ")[0] 76 | except: 77 | return "" 78 | 79 | def _get_float(self,which): 80 | return float(self.regex.findall(self.raw.split(which)[1])[0]) 81 | 82 | 83 | def _parse_coordinates(self): 84 | if "X" in self.raw: 85 | self._x = self._get_float("X") 86 | 87 | if "Y" in self.raw: 88 | self._y = self._get_float("Y") 89 | 90 | if "Z" in self.raw: 91 | self._z = self._get_float("Z") 92 | 93 | if "E" in self.raw: 94 | self.e = self._get_float("E") 95 | 96 | if "F" in self.raw: 97 | self.f = self._get_float("F") 98 | 99 | 100 | def is_move(self): 101 | return "G1" in self.raw or "G0" in self.raw 102 | 103 | 104 | class GCode(object): 105 | def __init__(self,data): 106 | self.lines = [Line(i) for i in data] 107 | self._preprocess() 108 | 109 | def _preprocess(self): 110 | #checks for G20, G21, G90 and G91, sets imperial and relative flags 111 | imperial = False 112 | relative = False 113 | for line in self.lines: 114 | if line.command() == "G20": 115 | imperial = True 116 | elif line.command() == "G21": 117 | imperial = False 118 | elif line.command() == "G90": 119 | relative = False 120 | elif line.command() == "G91": 121 | relative = True 122 | elif line.is_move(): 123 | line.imperial = imperial 124 | line.relative = relative 125 | 126 | 127 | def measure(self): 128 | xmin = 999999999 129 | ymin = 999999999 130 | zmin = 0 131 | xmax = -999999999 132 | ymax = -999999999 133 | zmax = -999999999 134 | relative = False 135 | 136 | current_x = 0 137 | current_y = 0 138 | current_z = 0 139 | 140 | for line in self.lines: 141 | if line.command() == "G92": 142 | current_x = line.x or current_x 143 | current_y = line.y or current_y 144 | current_z = line.z or current_z 145 | 146 | if line.is_move(): 147 | x = line.x 148 | y = line.y 149 | z = line.z 150 | 151 | if line.relative: 152 | x = current_x + (x or 0) 153 | y = current_y + (y or 0) 154 | z = current_z + (z or 0) 155 | 156 | 157 | if x and line.e: 158 | if x < xmin: 159 | xmin = x 160 | if x > xmax: 161 | xmax = x 162 | if y and line.e: 163 | if y < ymin: 164 | ymin = y 165 | if y > ymax: 166 | ymax = y 167 | if z: 168 | if z < zmin: 169 | zmin = z 170 | if z > zmax: 171 | zmax = z 172 | 173 | current_x = x or current_x 174 | current_y = y or current_y 175 | current_z = z or current_z 176 | 177 | self.xmin = xmin 178 | self.ymin = ymin 179 | self.zmin = zmin 180 | self.xmax = xmax 181 | self.ymax = ymax 182 | self.zmax = zmax 183 | 184 | self.width = xmax-xmin 185 | self.depth = ymax-ymin 186 | self.height = zmax-zmin 187 | 188 | 189 | def filament_length(self): 190 | total_e = 0 191 | cur_e = 0 192 | 193 | for line in self.lines: 194 | if line.command() == "G92": 195 | if line.e != None: 196 | total_e += cur_e 197 | cur_e = line.e 198 | elif line.is_move() and line.e: 199 | if line.relative: 200 | cur_e += line.e 201 | else: 202 | cur_e = line.e 203 | 204 | 205 | return total_e 206 | 207 | 208 | def main(): 209 | if len(sys.argv) < 2: 210 | print "usage: %s filename.gcode" % sys.argv[0] 211 | return 212 | 213 | gcode = GCode(sys.argv[1]) 214 | 215 | gcode.measure() 216 | 217 | print "Dimensions:" 218 | print "\tX: %0.02f - %0.02f (%0.02f)" % (gcode.xmin,gcode.xmax,gcode.width) 219 | print "\tY: %0.02f - %0.02f (%0.02f)" % (gcode.ymin,gcode.ymax,gcode.depth) 220 | print "\tZ: %0.02f - %0.02f (%0.02f)" % (gcode.zmin,gcode.zmax,gcode.height) 221 | print "Filament used: %0.02fmm" % gcode.filament_length() 222 | 223 | if __name__ == '__main__': 224 | main() -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # This file is part of the Printrun suite. 4 | # 5 | # Printrun is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Printrun is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Printrun. If not, see . 17 | 18 | import sys, os, glob 19 | import subprocess 20 | from stat import * 21 | from distutils.core import setup 22 | from distutils.command.install import install as _install 23 | from distutils.command.install_data import install_data as _install_data 24 | 25 | INSTALLED_FILES = "installed_files" 26 | 27 | class install (_install): 28 | 29 | def run (self): 30 | _install.run (self) 31 | outputs = self.get_outputs () 32 | length = 0 33 | if self.root: 34 | length += len (self.root) 35 | if self.prefix: 36 | length += len (self.prefix) 37 | if length: 38 | for counter in xrange (len (outputs)): 39 | outputs[counter] = outputs[counter][length:] 40 | data = "\n".join (outputs) 41 | try: 42 | file = open (INSTALLED_FILES, "w") 43 | except: 44 | self.warn ("Could not write installed files list %s" % \ 45 | INSTALLED_FILES) 46 | return 47 | file.write (data) 48 | file.close () 49 | 50 | class install_data (_install_data): 51 | 52 | def run (self): 53 | def chmod_data_file (file): 54 | try: 55 | os.chmod (file, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) 56 | except: 57 | self.warn ("Could not chmod data file %s" % file) 58 | _install_data.run (self) 59 | map (chmod_data_file, self.get_outputs ()) 60 | 61 | class uninstall (_install): 62 | 63 | def run (self): 64 | try: 65 | file = open (INSTALLED_FILES, "r") 66 | except: 67 | self.warn ("Could not read installed files list %s" % \ 68 | INSTALLED_FILES) 69 | return 70 | files = file.readlines () 71 | file.close () 72 | prepend = "" 73 | if self.root: 74 | prepend += self.root 75 | if self.prefix: 76 | prepend += self.prefix 77 | if len (prepend): 78 | for counter in xrange (len (files)): 79 | files[counter] = prepend + files[counter].rstrip () 80 | for file in files: 81 | print "Uninstalling %s" % file 82 | try: 83 | os.unlink (file) 84 | except: 85 | self.warn ("Could not remove file %s" % file) 86 | 87 | ops = ("install", "build", "sdist", "uninstall", "clean") 88 | 89 | if len (sys.argv) < 2 or sys.argv[1] not in ops: 90 | print "Please specify operation : %s" % " | ".join (ops) 91 | raise SystemExit 92 | 93 | prefix = None 94 | if len (sys.argv) > 2: 95 | i = 0 96 | for o in sys.argv: 97 | if o.startswith ("--prefix"): 98 | if o == "--prefix": 99 | if len (sys.argv) >= i: 100 | prefix = sys.argv[i + 1] 101 | sys.argv.remove (prefix) 102 | elif o.startswith ("--prefix=") and len (o[9:]): 103 | prefix = o[9:] 104 | sys.argv.remove (o) 105 | i += 1 106 | if not prefix and "PREFIX" in os.environ: 107 | prefix = os.environ["PREFIX"] 108 | if not prefix or not len (prefix): 109 | prefix = "/usr/local" 110 | 111 | if sys.argv[1] in ("install", "uninstall") and len (prefix): 112 | sys.argv += ["--prefix", prefix] 113 | 114 | target_images_path = "share/pronterface/images/" 115 | data_files = [('share/pixmaps/', ['P-face.ico','plater.ico'])] 116 | 117 | for basedir, subdirs, files in os.walk("images"): 118 | images = [] 119 | for filename in files: 120 | if filename.find(".svg") or filename.find(".png"): 121 | file_path = os.path.join(basedir, filename) 122 | images.append(file_path) 123 | data_files.append((target_images_path + basedir[len("images/"):], images)) 124 | 125 | for basedir, subdirs, files in os.walk("locale"): 126 | if not basedir.endswith("LC_MESSAGES"): 127 | continue 128 | destpath = os.path.join("share", "pronterface", basedir) 129 | files = filter(lambda x: x.endswith(".mo"), files) 130 | files = map(lambda x: os.path.join(basedir, x), files) 131 | data_files.append ((destpath, files)) 132 | 133 | extra_data_dirs = ["css"] 134 | for extra_data_dir in extra_data_dirs: 135 | for basedir, subdirs, files in os.walk(extra_data_dir): 136 | files = map(lambda x: os.path.join(basedir, x), files) 137 | destpath = os.path.join("share", "pronterface", basedir) 138 | data_files.append ((destpath, files)) 139 | 140 | setup ( 141 | name = "Printrun", 142 | description = "Host software for 3D printers", 143 | author = "Kliment Yanev", 144 | url = "http://github.com/kliment/Printrun/", 145 | license = "GPLv3", 146 | data_files = data_files, 147 | packages = ["printrun", "printrun.svg"], 148 | scripts = ["pronsole.py", "pronterface.py", "plater.py", "printcore.py"], 149 | cmdclass = {"uninstall" : uninstall, 150 | "install" : install, 151 | "install_data" : install_data} 152 | ) 153 | -------------------------------------------------------------------------------- /printrun/zbuttons.py: -------------------------------------------------------------------------------- 1 | # This file is part of the Printrun suite. 2 | # 3 | # Printrun is free software: you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation, either version 3 of the License, or 6 | # (at your option) any later version. 7 | # 8 | # Printrun is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with Printrun. If not, see . 15 | 16 | import wx, os, math 17 | from bufferedcanvas import * 18 | from printrun_utils import * 19 | 20 | def sign(n): 21 | if n < 0: return -1 22 | elif n > 0: return 1 23 | else: return 0 24 | 25 | class ZButtons(BufferedCanvas): 26 | button_ydistances = [7, 30, 55, 83] # ,112 27 | center = (30, 118) 28 | label_overlay_positions = { 29 | 0: (1, 18, 11), 30 | 1: (1, 41, 13), 31 | 2: (1, 67, 15), 32 | 3: None 33 | } 34 | 35 | def __init__(self, parent, moveCallback = None, bgcolor = "#FFFFFF", ID=-1): 36 | self.bg_bmp = wx.Image(imagefile("control_z.png"), wx.BITMAP_TYPE_PNG).ConvertToBitmap() 37 | self.range = None 38 | self.direction = None 39 | self.orderOfMagnitudeIdx = 0 # 0 means '1', 1 means '10', 2 means '100', etc. 40 | self.moveCallback = moveCallback 41 | self.enabled = False 42 | # Remember the last clicked value, so we can repeat when spacebar pressed 43 | self.lastValue = None 44 | 45 | self.bgcolor = wx.Colour() 46 | self.bgcolor.SetFromName(bgcolor) 47 | self.bgcolormask = wx.Colour(self.bgcolor.Red(), self.bgcolor.Green(), self.bgcolor.Blue(), 128) 48 | 49 | BufferedCanvas.__init__(self, parent, ID) 50 | 51 | self.SetSize(wx.Size(59, 244)) 52 | 53 | # Set up mouse and keyboard event capture 54 | self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown) 55 | self.Bind(wx.EVT_LEFT_DCLICK, self.OnLeftDown) 56 | self.Bind(wx.EVT_MOTION, self.OnMotion) 57 | self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeaveWindow) 58 | 59 | def disable(self): 60 | self.enabled = False 61 | self.update() 62 | 63 | def enable(self): 64 | self.enabled = True 65 | self.update() 66 | 67 | def repeatLast(self): 68 | if self.lastValue: 69 | self.moveCallback(self.lastValue) 70 | 71 | def clearRepeat(self): 72 | self.lastValue = None 73 | 74 | def lookupRange(self, ydist): 75 | idx = -1 76 | for d in ZButtons.button_ydistances: 77 | if ydist < d: 78 | return idx 79 | idx += 1 80 | return None 81 | 82 | def highlight(self, gc, rng, dir): 83 | assert(rng >= -1 and rng <= 3) 84 | assert(dir >= -1 and dir <= 1) 85 | 86 | fudge = 11 87 | x = 0 + fudge 88 | w = 59 - fudge*2 89 | if rng >= 0: 90 | k = 1 if dir > 0 else 0 91 | y = ZButtons.center[1] - (dir * ZButtons.button_ydistances[rng+k]) 92 | h = ZButtons.button_ydistances[rng+1] - ZButtons.button_ydistances[rng] 93 | gc.DrawRoundedRectangle(x, y, w, h, 4) 94 | # gc.DrawRectangle(x, y, w, h) 95 | # self.drawPartialPie(dc, center, r1-inner_ring_radius, r2-inner_ring_radius, a1+fudge, a2-fudge) 96 | 97 | def getRangeDir(self, pos): 98 | ydelta = ZButtons.center[1] - pos[1] 99 | return (self.lookupRange(abs(ydelta)), sign(ydelta)) 100 | 101 | def draw(self, dc, w, h): 102 | dc.SetBackground(wx.Brush(self.bgcolor)) 103 | dc.Clear() 104 | gc = wx.GraphicsContext.Create(dc) 105 | if self.bg_bmp: 106 | w, h = (self.bg_bmp.GetWidth(), self.bg_bmp.GetHeight()) 107 | gc.DrawBitmap(self.bg_bmp, 0, 0, w, h) 108 | 109 | if self.enabled: 110 | # Draw label overlays 111 | gc.SetPen(wx.Pen(wx.Colour(255, 255, 255, 128), 1)) 112 | gc.SetBrush(wx.Brush(wx.Colour(255, 255, 255, 128+64))) 113 | for idx, kpos in ZButtons.label_overlay_positions.items(): 114 | if kpos and idx != self.range: 115 | r = kpos[2] 116 | gc.DrawEllipse(ZButtons.center[0]-kpos[0]-r, ZButtons.center[1]-kpos[1]-r, r*2, r*2) 117 | 118 | # Top 'layer' is the mouse-over highlights 119 | gc.SetPen(wx.Pen(wx.Colour(100, 100, 100, 172), 4)) 120 | gc.SetBrush(wx.Brush(wx.Colour(0, 0, 0, 128))) 121 | if self.range != None and self.direction != None: 122 | self.highlight(gc, self.range, self.direction) 123 | else: 124 | gc.SetPen(wx.Pen(self.bgcolor, 0)) 125 | gc.SetBrush(wx.Brush(self.bgcolormask)) 126 | gc.DrawRectangle(0, 0, w, h) 127 | 128 | ## ------ ## 129 | ## Events ## 130 | ## ------ ## 131 | 132 | def OnMotion(self, event): 133 | if not self.enabled: 134 | return 135 | 136 | oldr, oldd = self.range, self.direction 137 | 138 | mpos = event.GetPosition() 139 | self.range, self.direction = self.getRangeDir(mpos) 140 | 141 | if oldr != self.range or oldd != self.direction: 142 | self.update() 143 | 144 | def OnLeftDown(self, event): 145 | if not self.enabled: 146 | return 147 | 148 | mpos = event.GetPosition() 149 | r, d = self.getRangeDir(mpos) 150 | if r >= 0: 151 | value = math.pow(10, self.orderOfMagnitudeIdx) * math.pow(10, r - 1) * d 152 | if self.moveCallback: 153 | self.lastValue = value 154 | self.moveCallback(value) 155 | 156 | def OnLeaveWindow(self, evt): 157 | self.range = None 158 | self.direction = None 159 | self.update() 160 | -------------------------------------------------------------------------------- /testfiles/belt_pulley3.slic3r.small.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /printrun/svg/pathdata.py: -------------------------------------------------------------------------------- 1 | """ 2 | SVG path data parser 3 | 4 | 5 | Usage: 6 | steps = svg.parseString(pathdata) 7 | for command, arguments in steps: 8 | pass 9 | 10 | """ 11 | 12 | from pyparsing import (ParserElement, Literal, Word, CaselessLiteral, 13 | Optional, Combine, Forward, ZeroOrMore, nums, oneOf, Group, ParseException, OneOrMore) 14 | 15 | #ParserElement.enablePackrat() 16 | 17 | def Command(char): 18 | """ Case insensitive but case preserving""" 19 | return CaselessPreservingLiteral(char) 20 | 21 | def Arguments(token): 22 | return Group(token) 23 | 24 | 25 | class CaselessPreservingLiteral(CaselessLiteral): 26 | """ Like CaselessLiteral, but returns the match as found 27 | instead of as defined. 28 | """ 29 | def __init__( self, matchString ): 30 | super(CaselessPreservingLiteral, self).__init__( matchString.upper() ) 31 | self.name = "'%s'" % matchString 32 | self.errmsg = "Expected " + self.name 33 | self.myException.msg = self.errmsg 34 | 35 | def parseImpl( self, instring, loc, doActions = True ): 36 | test = instring[ loc:loc+self.matchLen ] 37 | if test.upper() == self.match: 38 | return loc+self.matchLen, test 39 | #~ raise ParseException( instring, loc, self.errmsg ) 40 | exc = self.myException 41 | exc.loc = loc 42 | exc.pstr = instring 43 | raise exc 44 | 45 | def Sequence(token): 46 | """ A sequence of the token""" 47 | return OneOrMore(token+maybeComma) 48 | 49 | digit_sequence = Word(nums) 50 | 51 | sign = oneOf("+ -") 52 | 53 | def convertToFloat(s, loc, toks): 54 | try: 55 | return float(toks[0]) 56 | except: 57 | raise ParseException(loc, "invalid float format %s"%toks[0]) 58 | 59 | exponent = CaselessLiteral("e")+Optional(sign)+Word(nums) 60 | 61 | #note that almost all these fields are optional, 62 | #and this can match almost anything. We rely on Pythons built-in 63 | #float() function to clear out invalid values - loosely matching like this 64 | #speeds up parsing quite a lot 65 | floatingPointConstant = Combine( 66 | Optional(sign) + 67 | Optional(Word(nums)) + 68 | Optional(Literal(".") + Optional(Word(nums)))+ 69 | Optional(exponent) 70 | ) 71 | 72 | floatingPointConstant.setParseAction(convertToFloat) 73 | 74 | number = floatingPointConstant 75 | 76 | #same as FP constant but don't allow a - sign 77 | nonnegativeNumber = Combine( 78 | Optional(Word(nums)) + 79 | Optional(Literal(".") + Optional(Word(nums)))+ 80 | Optional(exponent) 81 | ) 82 | nonnegativeNumber.setParseAction(convertToFloat) 83 | 84 | coordinate = number 85 | 86 | #comma or whitespace can seperate values all over the place in SVG 87 | maybeComma = Optional(Literal(',')).suppress() 88 | 89 | coordinateSequence = Sequence(coordinate) 90 | 91 | coordinatePair = (coordinate + maybeComma + coordinate).setParseAction(lambda t: tuple(t)) 92 | coordinatePairSequence = Sequence(coordinatePair) 93 | 94 | coordinatePairPair = coordinatePair + maybeComma + coordinatePair 95 | coordinatePairPairSequence = Sequence(Group(coordinatePairPair)) 96 | 97 | coordinatePairTriple = coordinatePair + maybeComma + coordinatePair + maybeComma + coordinatePair 98 | coordinatePairTripleSequence = Sequence(Group(coordinatePairTriple)) 99 | 100 | #commands 101 | lineTo = Group(Command("L") + Arguments(coordinatePairSequence)) 102 | 103 | moveTo = Group(Command("M") + Arguments(coordinatePairSequence)) 104 | 105 | closePath = Group(Command("Z")).setParseAction(lambda t: ('Z', (None,))) 106 | 107 | flag = oneOf("1 0").setParseAction(lambda t: bool(int((t[0])))) 108 | 109 | arcRadius = ( 110 | nonnegativeNumber + maybeComma + #rx 111 | nonnegativeNumber #ry 112 | ).setParseAction(lambda t: tuple(t)) 113 | 114 | arcFlags = (flag + maybeComma + flag).setParseAction(lambda t: tuple(t)) 115 | 116 | ellipticalArcArgument = Group( 117 | arcRadius + maybeComma + #rx, ry 118 | number + maybeComma +#rotation 119 | arcFlags + #large-arc-flag, sweep-flag 120 | coordinatePair #(x, y) 121 | ) 122 | 123 | 124 | ellipticalArc = Group(Command("A") + Arguments(Sequence(ellipticalArcArgument))) 125 | 126 | smoothQuadraticBezierCurveto = Group(Command("T") + Arguments(coordinatePairSequence)) 127 | 128 | quadraticBezierCurveto = Group(Command("Q") + Arguments(coordinatePairPairSequence)) 129 | 130 | smoothCurve = Group(Command("S") + Arguments(coordinatePairPairSequence)) 131 | 132 | curve = Group(Command("C") + Arguments(coordinatePairTripleSequence)) 133 | 134 | horizontalLine = Group(Command("H") + Arguments(coordinateSequence)) 135 | verticalLine = Group(Command("V") + Arguments(coordinateSequence)) 136 | 137 | drawToCommand = ( 138 | lineTo | moveTo | closePath | ellipticalArc | smoothQuadraticBezierCurveto | 139 | quadraticBezierCurveto | smoothCurve | curve | horizontalLine | verticalLine 140 | ) 141 | 142 | #~ number.debug = True 143 | moveToDrawToCommands = moveTo + ZeroOrMore(drawToCommand) 144 | 145 | svg = ZeroOrMore(moveToDrawToCommands) 146 | svg.keepTabs = True 147 | 148 | def profile(): 149 | import cProfile 150 | p = cProfile.Profile() 151 | p.enable() 152 | ptest() 153 | ptest() 154 | ptest() 155 | p.disable() 156 | p.print_stats() 157 | 158 | bpath = """M204.33 139.83 C196.33 133.33 206.68 132.82 206.58 132.58 C192.33 97.08 169.35 159 | 81.41 167.58 80.58 C162.12 78.02 159.48 78.26 160.45 76.97 C161.41 75.68 167.72 79.72 168.58 160 | 80.33 C193.83 98.33 207.58 132.33 207.58 132.33 C207.58 132.33 209.33 133.33 209.58 132.58 161 | C219.58 103.08 239.58 87.58 246.33 81.33 C253.08 75.08 256.63 74.47 247.33 81.58 C218.58 103.58 162 | 210.34 132.23 210.83 132.33 C222.33 134.83 211.33 140.33 211.83 139.83 C214.85 136.81 214.83 145.83 214.83 163 | 145.83 C214.83 145.83 231.83 110.83 298.33 66.33 C302.43 63.59 445.83 -14.67 395.83 80.83 C393.24 85.79 375.83 164 | 105.83 375.83 105.83 C375.83 105.83 377.33 114.33 371.33 121.33 C370.3 122.53 367.83 134.33 361.83 140.83 C360.14 142.67 165 | 361.81 139.25 361.83 140.83 C362.33 170.83 337.76 170.17 339.33 170.33 C348.83 171.33 350.19 183.66 350.33 183.83 C355.83 166 | 190.33 353.83 191.83 355.83 194.83 C366.63 211.02 355.24 210.05 356.83 212.83 C360.83 219.83 355.99 222.72 357.33 224.83 167 | C360.83 230.33 354.75 233.84 354.83 235.33 C355.33 243.83 349.67 240.73 349.83 244.33 C350.33 255.33 346.33 250.83 343.83 254.83 168 | C336.33 266.83 333.46 262.38 332.83 263.83 C329.83 270.83 325.81 269.15 324.33 270.83 C320.83 274.83 317.33 274.83 315.83 276.33 169 | C308.83 283.33 304.86 278.39 303.83 278.83 C287.83 285.83 280.33 280.17 277.83 280.33 C270.33 280.83 271.48 279.67 269.33 277.83 170 | C237.83 250.83 219.33 211.83 215.83 206.83 C214.4 204.79 211.35 193.12 212.33 195.83 C214.33 201.33 213.33 250.33 207.83 250.33 171 | C202.33 250.33 201.83 204.33 205.33 195.83 C206.43 193.16 204.4 203.72 201.79 206.83 C196.33 213.33 179.5 250.83 147.59 277.83 172 | C145.42 279.67 146.58 280.83 138.98 280.33 C136.46 280.17 128.85 285.83 112.65 278.83 C111.61 278.39 107.58 283.33 100.49 276.33 173 | C98.97 274.83 95.43 274.83 91.88 270.83 C90.39 269.15 86.31 270.83 83.27 263.83 C82.64 262.38 79.73 266.83 72.13 254.83 C69.6 250.83 174 | 65.54 255.33 66.05 244.33 C66.22 240.73 60.48 243.83 60.99 235.33 C61.08 233.84 54.91 230.33 58.45 224.83 C59.81 222.72 54.91 219.83 175 | 58.96 212.83 C60.57 210.05 49.04 211.02 59.97 194.83 C62 191.83 59.97 190.33 65.54 183.83 C65.69 183.66 67.06 171.33 76.69 170.33 176 | C78.28 170.17 53.39 170.83 53.9 140.83 C53.92 139.25 55.61 142.67 53.9 140.83 C47.82 134.33 45.32 122.53 44.27 121.33 C38.19 114.33 177 | 39.71 105.83 39.71 105.83 C39.71 105.83 22.08 85.79 19.46 80.83 C-31.19 -14.67 114.07 63.59 118.22 66.33 C185.58 110.83 202 145.83 178 | 202 145.83 C202 145.83 202.36 143.28 203 141.83 C203.64 140.39 204.56 140.02 204.33 139.83 z""" 179 | 180 | def ptest(): 181 | svg.parseString(bpath) 182 | 183 | 184 | 185 | 186 | 187 | if __name__ == '__main__': 188 | 189 | #~ from tests.test_pathdata import * 190 | #~ unittest.main() 191 | profile() 192 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Printrun consists of printcore, pronsole and pronterface, and a small collection of helpful scripts. 2 | 3 | * printcore.py is a library that makes writing reprap hosts easy 4 | * pronsole.py is an interactive command-line host software with tabcompletion goodness 5 | * pronterface.py is a graphical host software with the same functionality as pronsole 6 | * webinterface.py is a browser-usable remote control function for Pronterface 7 | 8 | # GETTING PRINTRUN 9 | 10 | This section suggests using precompiled binaries, this way you get everything bundled into one single package for an easy installation. 11 | 12 | If you want the newest, shiniest features, you can run Printrun from source using the instructions further down this README. 13 | 14 | ## Windows 15 | 16 | A precompiled version is available at http://koti.kapsi.fi/~kliment/printrun/ 17 | 18 | ## Mac OS X 19 | 20 | A precompiled version is available at http://koti.kapsi.fi/~kliment/printrun/ 21 | 22 | ## Linux 23 | ### Ubuntu/Debian 24 | 25 | You can run Printrun directly from source, as there are no packages available yet. Fetch and install the dependencies using 26 | 27 | `sudo apt-get install python-serial python-wxgtk2.8 python-pyglet` 28 | 29 | ### Fedora 15 and newer 30 | 31 | You can run Printrun directly from source, as there are no packages available yet. Fetch and install the dependencies using 32 | 33 | `sudo yum install pyserial wxpython pyglet` 34 | 35 | ### Archlinux 36 | 37 | Packages are available in AUR. Just run 38 | 39 | `yaourt printrun` 40 | 41 | and enjoy the `pronterface`, `pronsole`, ... commands directly. 42 | 43 | # USING PRONTERFACE 44 | 45 | When you're done setting up Printrun, you can start pronterface.py in the directory you unpacked it. 46 | Select the port name you are using from the first drop-down, select your baud rate, and hit connect. 47 | Load an STL (see the note on skeinforge below) or GCODE file, and you can upload it to SD or print it directly. 48 | The "monitor printer" function, when enabled, checks the printer state (temperatures, SD print progress) every 3 seconds. 49 | The command box recognizes all pronsole commands, but has no tabcompletion. 50 | 51 | If you want to load stl files, you need to install a slicing program such as Slic3r and add its path to the settings. 52 | See the Slic3r readme for more details on integration. 53 | 54 | # Using the browser interface 55 | 56 | To run the web interface, install Cherrypy and run Pronterface as described above. 57 | The www server will start on the port/address you have chosen. 58 | 59 | ## Webinterface Dependencies 60 | 61 | Cherrypy is required for the web interface. Download and install it by opening a 62 | command prompt in its directory and running python setup.py install. 63 | 64 | ## Webinterface Configuration 65 | * The Web interface port / ip is configurable in http.config 66 | * The Default User / Password can be set in auth.config 67 | 68 | ## Webinterface Styling 69 | * css/style.css can be modified to change the style of the Web Interface. 70 | 71 | 72 | 73 | # USING PRONSOLE 74 | 75 | To use pronsole, you need: 76 | 77 | * python (ideally 2.6.x or 2.7.x), 78 | * pyserial (or python-serial on ubuntu/debian) and 79 | * pyreadline (not needed on Linux) 80 | 81 | Start pronsole and you will be greeted with a command prompt. Type help to view the available commands. 82 | All commands have internal help, which you can access by typing "help commandname", for example "help connect" 83 | 84 | If you want to load stl files, you need to put a version of skeinforge (doesn't matter which one) in a folder called "skeinforge". 85 | The "skeinforge" folder must be in the same folder as pronsole.py 86 | 87 | # USING PRINTCORE 88 | 89 | To use printcore you need python (ideally 2.6.x or 2.7.x) and pyserial (or python-serial on ubuntu/debian) 90 | See pronsole for an example of a full-featured host, the bottom of printcore.py for a simple command-line 91 | sender, or the following code example: 92 | 93 | p=printcore('/dev/ttyUSB0',115200) 94 | p.startprint(data) # data is an array of gcode lines 95 | p.send_now("M105") # sends M105 as soon as possible 96 | p.pause() 97 | p.resume() 98 | p.disconnect() 99 | 100 | # RUNNING FROM SOURCE 101 | 102 | Run Printrun for source if you want to test out the latest features. 103 | 104 | ## Dependencies 105 | 106 | To use pronterface, you need: 107 | 108 | * python (ideally 2.6.x or 2.7.x), 109 | * pyserial (or python-serial on ubuntu/debian), 110 | * pyglet 111 | * pyreadline (not needed on Linux) and 112 | * wxPython 113 | 114 | Please see specific instructions for Windows and Mac OS X below. Under Linux, you should use your package manager directly (see the "GETTING PRINTRUN" section) 115 | 116 | ## Windows 117 | 118 | Download the following, and install in this order: 119 | 120 | 1. http://python.org/ftp/python/2.7.2/python-2.7.2.msi 121 | 2. http://pypi.python.org/packages/any/p/pyserial/pyserial-2.5.win32.exe 122 | 3. http://downloads.sourceforge.net/wxpython/wxPython2.8-win32-unicode-2.8.12.0-py27.exe 123 | 4. http://launchpad.net/pyreadline/1.7/1.7/+download/pyreadline-1.7.win32.exe 124 | 5. http://pyglet.googlecode.com/files/pyglet-1.1.4.zip 125 | 126 | For the last one, you will need to unpack it, open a command terminal, 127 | go into the the directory you unpacked it in and run 128 | `python setup.py install` 129 | 130 | ## Mac OS X Lion 131 | 132 | 1. Ensure that the active Python is the system version. (`brew uninstall python` or other appropriate incantations) 133 | 2. Download an install [wxPython2.8-osx-unicode] matching to your python version (most likely 2.7 on Lion, 134 | check with: python --version) from: http://wxpython.org/download.php#stable 135 | Known to work PythonWX: http://superb-sea2.dl.sourceforge.net/project/wxpython/wxPython/2.8.12.1/wxPython2.8-osx-unicode-2.8.12.1-universal-py2.7.dmg 136 | 3. Download and unpack pyserial from http://pypi.python.org/packages/source/p/pyserial/pyserial-2.5.tar.gz 137 | 4. In a terminal, change to the folder you unzipped to, then type in: `sudo python setup.py install` 138 | 5. Repeat 4. with http://http://pyglet.googlecode.com/files/pyglet-1.1.4.zip 139 | 140 | The tools will probably run just fine in 64bit on Lion, you don't need to mess 141 | with any of the 32bit settings. In case they don't, try 142 | 5. export VERSIONER_PYTHON_PREFER_32_BIT=yes 143 | in a terminal before running Pronterface 144 | 145 | ## Mac OS X (pre Lion) 146 | 147 | A precompiled version is available at http://koti.kapsi.fi/~kliment/printrun/ 148 | 149 | 1. Download and install http://downloads.sourceforge.net/wxpython/wxPython2.8-osx-unicode-2.8.12.0-universal-py2.6.dmg 150 | 2. Grab the source for pyserial from http://pypi.python.org/packages/source/p/pyserial/pyserial-2.5.tar.gz 151 | 3. Unzip pyserial to a folder. Then, in a terminal, change to the folder you unzipped to, then type in: 152 | 153 | `defaults write com.apple.versioner.python Prefer-32-Bit -bool yes` 154 | 155 | `sudo python setup.py install` 156 | 157 | Alternatively, you can run python in 32 bit mode by setting the following environment variable before running the setup.py command: 158 | 159 | This alternative approach is confirmed to work on Mac OS X 10.6.8. 160 | 161 | `export VERSIONER_PYTHON_PREFER_32_BIT=yes` 162 | 163 | `sudo python setup.py install` 164 | 165 | Then repeat the same with http://http://pyglet.googlecode.com/files/pyglet-1.1.4.zip 166 | 167 | # LICENSE 168 | 169 | ``` 170 | Printrun is free software: you can redistribute it and/or modify 171 | it under the terms of the GNU General Public License as published by 172 | the Free Software Foundation, either version 3 of the License, or 173 | (at your option) any later version. 174 | 175 | Printrun is distributed in the hope that it will be useful, 176 | but WITHOUT ANY WARRANTY; without even the implied warranty of 177 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 178 | GNU General Public License for more details. 179 | 180 | You should have received a copy of the GNU General Public License 181 | along with Printrun. If not, see . 182 | ``` 183 | -------------------------------------------------------------------------------- /printrun/SkeinforgeQuickEditDialog.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from skeinforge.fabmetheus_utilities import archive 4 | from skeinforge.fabmetheus_utilities import settings 5 | from skeinforge.skeinforge_application.skeinforge_utilities import skeinforge_craft 6 | from skeinforge.skeinforge_application.skeinforge_utilities import skeinforge_profile 7 | import os 8 | import wx 9 | 10 | class SkeinforgeQuickEditDialog(wx.Dialog): 11 | '''Shows a consise list of important settings from the active Skeinforge profile.''' 12 | def __init__(self, *args, **kwds): 13 | kwds["style"] = wx.DEFAULT_DIALOG_STYLE | wx.MAXIMIZE_BOX | wx.MINIMIZE_BOX | wx.RESIZE_BORDER 14 | wx.Dialog.__init__(self, *args, **kwds) 15 | self.okButton = wx.Button(self, wx.ID_OK, "Save") 16 | self.cancelButton = wx.Button(self, wx.ID_CANCEL, "") 17 | self.Bind(wx.EVT_BUTTON, self.OnExit, self.cancelButton) 18 | self.Bind(wx.EVT_BUTTON, self.OnSave, self.okButton) 19 | 20 | """ 21 | The following list determines which settings are shown. 22 | The dictionary key is the plugin name and the value is a list of setting names as found in the corresponding .csv file for that plugin. 23 | 24 | NOTE: Skeinforge is tightly integrated with Tkinter and there appears to be a dependency which stops radio-button values from being saved. 25 | Perhaps this can be solved, but at the moment this dialog cannot modify radio button values. One will have to use the main Skeinforge application. 26 | """ 27 | self.moduleSettingsMap = { 28 | 'dimension':['Filament Diameter (mm):','Retraction Distance (millimeters):', 'Retraction Distance (millimeters):','Extruder Retraction Speed (mm/s):'], 29 | 'carve':['Layer Height = Extrusion Thickness (mm):', 'Extrusion Width (mm):'], 30 | 'chamber':['Heated PrintBed Temperature (Celcius):', 'Turn print Bed Heater Off at Shut Down', 'Turn Extruder Heater Off at Shut Down'], 31 | 'cool':['Activate Cool.. but use with a fan!', 'Use Cool if layer takes shorter than(seconds):'], 32 | 'fill':['Activate Fill:', 'Infill Solidity (ratio):', 'Fully filled Layers (each top and bottom):', 'Extra Shells on Sparse Layer (layers):', 'Extra Shells on Alternating Solid Layer (layers):'], 33 | 'multiply':['Number of Columns (integer):', 'Number of Rows (integer):'], 34 | 'raft':['First Layer Main Feedrate (mm/s):','First Layer Perimeter Feedrate (mm/s):','First Layer Flow Rate Infill(scaler):','First Layer Flow Rate Perimeter(scaler):',], 35 | 'speed':['Main Feed Rate (mm/s):','Main Flow Rate (scaler):','Perimeter Feed Rate (mm/s):','Perimeter Flow Rate (scaler):','Travel Feed Rate (mm/s):'] 36 | } 37 | 38 | self.scrollbarPanel = wx.ScrolledWindow(self, -1, style = wx.TAB_TRAVERSAL) 39 | self.settingsSizer = self.getProfileSettings() 40 | self.scrollbarPanel.SetSizer(self.settingsSizer) 41 | 42 | self.__set_properties() 43 | self.__do_layout() 44 | self.Show() 45 | 46 | def __set_properties(self): 47 | self.profileName = skeinforge_profile.getProfileName(skeinforge_profile.getCraftTypeName()) 48 | self.SetTitle("Skeinforge Quick Edit Profile: " + self.profileName) 49 | 50 | # For some reason the dialog size is not consistent between Windows and Linux - this is a hack to get it working 51 | if (os.name == 'nt'): 52 | self.SetMinSize(wx.DLG_SZE(self, (465, 370))) 53 | else: 54 | self.SetSize(wx.DLG_SZE(self, (465, 325))) 55 | 56 | self.SetPosition((0, 0)) 57 | self.scrollbarPanel.SetScrollRate(10, 10) 58 | 59 | def __do_layout(self): 60 | mainSizer = wx.BoxSizer(wx.VERTICAL) 61 | actionsSizer = wx.BoxSizer(wx.HORIZONTAL) 62 | mainSizer.Add(self.scrollbarPanel, 1, wx.EXPAND | wx.ALL, 5) 63 | actionsSizer.Add(self.okButton, 0, 0, 0) 64 | actionsSizer.Add(self.cancelButton, 0, wx.LEFT, 10) 65 | mainSizer.Add(actionsSizer, 0, wx.ALIGN_RIGHT | wx.ALL, 5) 66 | self.SetSizer(mainSizer) 67 | self.Layout() 68 | 69 | def getProfileSettings(self): 70 | settingsSizer = wx.GridBagSizer(hgap = 2, vgap = 1) 71 | settingsRow = 0 72 | 73 | for craftName in sorted(self.moduleSettingsMap.keys()): 74 | 75 | craftStaticBox = wx.StaticBox(self.scrollbarPanel, -1, craftName.capitalize()) 76 | craftStaticBoxSizer = wx.StaticBoxSizer(craftStaticBox, wx.VERTICAL) 77 | 78 | # For some reason the dialog size is not consistent between Windows and Linux - this is a hack to get it working 79 | if (os.name == 'nt'): 80 | craftStaticBoxSizer.SetMinSize((320, -1)) 81 | else: 82 | craftStaticBoxSizer.SetMinSize((450, -1)) 83 | pluginModule = archive.getModuleWithPath(os.path.join(skeinforge_craft.getPluginsDirectoryPath(), craftName)) 84 | repo = pluginModule.getNewRepository() 85 | 86 | for setting in settings.getReadRepository(repo).preferences: 87 | if setting.name in self.moduleSettingsMap[craftName]: 88 | 89 | settingSizer = wx.GridBagSizer(hgap = 2, vgap = 2) 90 | settingSizer.AddGrowableCol(0) 91 | settingRow = 0 92 | settingLabel = wx.StaticText(self.scrollbarPanel, -1, setting.name) 93 | settingLabel.Wrap(400) 94 | settingSizer.Add(settingLabel, pos = (settingRow, 0)) 95 | 96 | if (isinstance(setting.value, bool)): 97 | checkbox = wx.CheckBox(self.scrollbarPanel) 98 | checkbox.SetName(craftName + '.' + setting.name) 99 | checkbox.SetValue(setting.value) 100 | settingSizer.Add(checkbox, pos = (settingRow, 1)) 101 | settingSizer.AddSpacer((25, -1), pos = (settingRow, 2)) 102 | else: 103 | textCtrl = wx.TextCtrl(self.scrollbarPanel, value = str(setting.value), size = (50, -1)) 104 | textCtrl.SetName(craftName + '.' + setting.name) 105 | settingSizer.Add(textCtrl, pos = (settingRow, 1)) 106 | 107 | craftStaticBoxSizer.Add(settingSizer, 1, wx.EXPAND, 0) 108 | settingRow += 1 109 | col = settingsRow % 2 110 | settingsSizer.Add(craftStaticBoxSizer, pos = (settingsRow - col, col)) 111 | settingsRow += 1 112 | 113 | return settingsSizer 114 | 115 | def OnExit(self, e): 116 | self.Destroy() 117 | 118 | def OnSave(self, e): 119 | for x in self.scrollbarPanel.GetChildren(): 120 | if (isinstance(x, (wx.CheckBox, wx.TextCtrl))): 121 | name = x.GetName().partition('.') 122 | craftName = name[0] 123 | settingName = name[2] 124 | pluginModule = archive.getModuleWithPath(os.path.join(skeinforge_craft.getPluginsDirectoryPath(), craftName)) 125 | repo = pluginModule.getNewRepository() 126 | isDirty = False 127 | for setting in settings.getReadRepository(repo).preferences: 128 | if setting.name == settingName: 129 | if setting.value == None or str(x.GetValue()) != str(setting.value): 130 | print('Saving ... ' + settingName + ' = ' + str(x.GetValue())) 131 | setting.value = x.GetValue() 132 | isDirty = True 133 | if isDirty: 134 | settings.saveRepository(repo) 135 | print("Skeinforge settings have been saved.") 136 | self.Destroy() 137 | 138 | class SkeinforgeQuickEditApp(wx.App): 139 | def OnInit(self): 140 | wx.InitAllImageHandlers() 141 | SkeinforgeQuickEditDialog(None, -1, "") 142 | return 1 143 | 144 | if __name__ == "__main__": 145 | skeinforgeQuickEditApp = SkeinforgeQuickEditApp(0) 146 | skeinforgeQuickEditApp.MainLoop() 147 | -------------------------------------------------------------------------------- /printrun/stltool.py: -------------------------------------------------------------------------------- 1 | # This file is part of the Printrun suite. 2 | # 3 | # Printrun is free software: you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation, either version 3 of the License, or 6 | # (at your option) any later version. 7 | # 8 | # Printrun is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with Printrun. If not, see . 15 | 16 | import sys, struct, math 17 | 18 | def cross(v1, v2): 19 | return [v1[1]*v2[2]-v1[2]*v2[1], v1[2]*v2[0]-v1[0]*v2[2], v1[0]*v2[1]-v1[1]*v2[0]] 20 | 21 | def genfacet(v): 22 | veca = [v[1][0]-v[0][0], v[1][1]-v[0][1], v[1][2]-v[0][2]] 23 | vecb = [v[2][0]-v[1][0], v[2][1]-v[1][1], v[2][2]-v[1][2]] 24 | vecx = cross(veca, vecb) 25 | vlen = math.sqrt(sum(map(lambda x:x*x, vecx))) 26 | if vlen == 0: 27 | vlen = 1 28 | normal = map(lambda x:x/vlen, vecx) 29 | return [normal, v] 30 | 31 | I = [ 32 | [1, 0, 0, 0], 33 | [0, 1, 0, 0], 34 | [0, 0, 1, 0], 35 | [0, 0, 0, 1] 36 | ] 37 | 38 | def transpose(matrix): 39 | return zip(*matrix) 40 | #return [[v[i] for v in matrix] for i in xrange(len(matrix[0]))] 41 | 42 | def multmatrix(vector, matrix): 43 | return map(sum, transpose(map(lambda x:[x[0]*p for p in x[1]], zip(vector, transpose(matrix))))) 44 | 45 | def applymatrix(facet, matrix = I): 46 | #return facet 47 | #return [map(lambda x:-1.0*x, multmatrix(facet[0]+[1], matrix)[:3]), map(lambda x:multmatrix(x+[1], matrix)[:3], facet[1])] 48 | return genfacet(map(lambda x:multmatrix(x+[1], matrix)[:3], facet[1])) 49 | 50 | f = [[0, 0, 0],[[-3.022642, 0.642482, -9.510565],[-3.022642, 0.642482, -9.510565],[-3.022642, 0.642482, -9.510565]]] 51 | m = [ 52 | [1, 0, 0, 0], 53 | [0, 1, 0, 0], 54 | [0, 0, 1, 1], 55 | [0, 0, 0, 1] 56 | ] 57 | 58 | def emitstl(filename, facets = [], objname = "stltool_export", binary = 1): 59 | if filename is None: 60 | return 61 | if binary: 62 | f = open(filename, "wb") 63 | buf = "".join(["\0"]*80) 64 | buf+=struct.pack("i): 239 | working.remove(j[1]) 240 | else: 241 | break 242 | for j in (sorted(s.facetsmaxz)): 243 | if(j[0]. 15 | 16 | import wx 17 | import re 18 | 19 | class MacroEditor(wx.Dialog): 20 | """Really simple editor to edit macro definitions""" 21 | 22 | def __init__(self, macro_name, definition, callback, gcode = False): 23 | self.indent_chars = " " 24 | title = " macro %s" 25 | if gcode: 26 | title = " %s" 27 | self.gcode = gcode 28 | wx.Dialog.__init__(self, None, title = title % macro_name, style = wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER) 29 | self.callback = callback 30 | self.panel = wx.Panel(self,-1) 31 | titlesizer = wx.BoxSizer(wx.HORIZONTAL) 32 | titletext = wx.StaticText(self.panel,-1, " _") #title%macro_name) 33 | #title.SetFont(wx.Font(11, wx.NORMAL, wx.NORMAL, wx.BOLD)) 34 | titlesizer.Add(titletext, 1) 35 | self.findb = wx.Button(self.panel, -1, _("Find"), style = wx.BU_EXACTFIT) #New button for "Find" (Jezmy) 36 | self.findb.Bind(wx.EVT_BUTTON, self.find) 37 | self.okb = wx.Button(self.panel, -1, _("Save"), style = wx.BU_EXACTFIT) 38 | self.okb.Bind(wx.EVT_BUTTON, self.save) 39 | self.Bind(wx.EVT_CLOSE, self.close) 40 | titlesizer.Add(self.findb) 41 | titlesizer.Add(self.okb) 42 | self.cancelb = wx.Button(self.panel, -1, _("Cancel"), style = wx.BU_EXACTFIT) 43 | self.cancelb.Bind(wx.EVT_BUTTON, self.close) 44 | titlesizer.Add(self.cancelb) 45 | topsizer = wx.BoxSizer(wx.VERTICAL) 46 | topsizer.Add(titlesizer, 0, wx.EXPAND) 47 | self.e = wx.TextCtrl(self.panel, style = wx.HSCROLL|wx.TE_MULTILINE|wx.TE_RICH2, size = (400, 400)) 48 | if not self.gcode: 49 | self.e.SetValue(self.unindent(definition)) 50 | else: 51 | self.e.SetValue("\n".join(definition)) 52 | topsizer.Add(self.e, 1, wx.ALL+wx.EXPAND) 53 | self.panel.SetSizer(topsizer) 54 | topsizer.Layout() 55 | topsizer.Fit(self) 56 | self.Show() 57 | self.e.SetFocus() 58 | 59 | def find(self, ev): 60 | # Ask user what to look for, find it and point at it ... (Jezmy) 61 | S = self.e.GetStringSelection() 62 | if not S : 63 | S = "Z" 64 | FindValue = wx.GetTextFromUser('Please enter a search string:', caption = "Search", default_value = S, parent = None) 65 | somecode = self.e.GetValue() 66 | numLines = len(somecode) 67 | position = somecode.find(FindValue, self.e.GetInsertionPoint()) 68 | if position == -1 : 69 | # ShowMessage(self,-1, "Not found!") 70 | titletext = wx.TextCtrl(self.panel,-1, "Not Found!") 71 | else: 72 | # self.title.SetValue("Position : "+str(position)) 73 | 74 | titletext = wx.TextCtrl(self.panel,-1, str(position)) 75 | 76 | # ananswer = wx.MessageBox(str(numLines)+" Lines detected in file\n"+str(position), "OK") 77 | self.e.SetFocus() 78 | self.e.SetInsertionPoint(position) 79 | self.e.SetSelection(position, position + len(FindValue)) 80 | self.e.ShowPosition(position) 81 | 82 | def ShowMessage(self, ev , message): 83 | dlg = wxMessageDialog(self, message, 84 | "Info!", wxOK | wxICON_INFORMATION) 85 | dlg.ShowModal() 86 | dlg.Destroy() 87 | 88 | def save(self, ev): 89 | self.Destroy() 90 | if not self.gcode: 91 | self.callback(self.reindent(self.e.GetValue())) 92 | else: 93 | self.callback(self.e.GetValue().split("\n")) 94 | 95 | def close(self, ev): 96 | self.Destroy() 97 | 98 | def unindent(self, text): 99 | self.indent_chars = text[:len(text)-len(text.lstrip())] 100 | if len(self.indent_chars) == 0: 101 | self.indent_chars = " " 102 | unindented = "" 103 | lines = re.split(r"(?:\r\n?|\n)", text) 104 | #print lines 105 | if len(lines) <= 1: 106 | return text 107 | for line in lines: 108 | if line.startswith(self.indent_chars): 109 | unindented += line[len(self.indent_chars):] + "\n" 110 | else: 111 | unindented += line + "\n" 112 | return unindented 113 | def reindent(self, text): 114 | lines = re.split(r"(?:\r\n?|\n)", text) 115 | if len(lines) <= 1: 116 | return text 117 | reindented = "" 118 | for line in lines: 119 | if line.strip() != "": 120 | reindented += self.indent_chars + line + "\n" 121 | return reindented 122 | 123 | class options(wx.Dialog): 124 | """Options editor""" 125 | def __init__(self, pronterface): 126 | wx.Dialog.__init__(self, None, title = _("Edit settings"), style = wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER) 127 | topsizer = wx.BoxSizer(wx.VERTICAL) 128 | vbox = wx.StaticBoxSizer(wx.StaticBox(self, label = _("Defaults")) ,wx.VERTICAL) 129 | topsizer.Add(vbox, 1, wx.ALL+wx.EXPAND) 130 | grid = wx.FlexGridSizer(rows = 0, cols = 2, hgap = 8, vgap = 2) 131 | grid.SetFlexibleDirection( wx.BOTH ) 132 | grid.AddGrowableCol( 1 ) 133 | grid.SetNonFlexibleGrowMode( wx.FLEX_GROWMODE_SPECIFIED ) 134 | vbox.Add(grid, 0, wx.EXPAND) 135 | ctrls = {} 136 | for k, v in sorted(pronterface.settings._all_settings().items()): 137 | ctrls[k, 0] = wx.StaticText(self,-1, k) 138 | ctrls[k, 1] = wx.TextCtrl(self,-1, str(v)) 139 | if k in pronterface.helpdict: 140 | ctrls[k, 0].SetToolTipString(pronterface.helpdict.get(k)) 141 | ctrls[k, 1].SetToolTipString(pronterface.helpdict.get(k)) 142 | grid.Add(ctrls[k, 0], 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL|wx.ALIGN_RIGHT) 143 | grid.Add(ctrls[k, 1], 1, wx.ALIGN_CENTER_VERTICAL|wx.ALL|wx.EXPAND) 144 | topsizer.Add(self.CreateSeparatedButtonSizer(wx.OK+wx.CANCEL), 0, wx.EXPAND) 145 | self.SetSizer(topsizer) 146 | topsizer.Layout() 147 | topsizer.Fit(self) 148 | if self.ShowModal() == wx.ID_OK: 149 | for k, v in pronterface.settings._all_settings().items(): 150 | if ctrls[k, 1].GetValue() != str(v): 151 | pronterface.set(k, str(ctrls[k, 1].GetValue())) 152 | self.Destroy() 153 | 154 | class ButtonEdit(wx.Dialog): 155 | """Custom button edit dialog""" 156 | def __init__(self, pronterface): 157 | wx.Dialog.__init__(self, None, title = _("Custom button"), style = wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER) 158 | self.pronterface = pronterface 159 | topsizer = wx.BoxSizer(wx.VERTICAL) 160 | grid = wx.FlexGridSizer(rows = 0, cols = 2, hgap = 4, vgap = 2) 161 | grid.AddGrowableCol(1, 1) 162 | grid.Add(wx.StaticText(self,-1, _("Button title")), 0, wx.BOTTOM|wx.RIGHT) 163 | self.name = wx.TextCtrl(self,-1, "") 164 | grid.Add(self.name, 1, wx.EXPAND) 165 | grid.Add(wx.StaticText(self, -1, _("Command")), 0, wx.BOTTOM|wx.RIGHT) 166 | self.command = wx.TextCtrl(self,-1, "") 167 | xbox = wx.BoxSizer(wx.HORIZONTAL) 168 | xbox.Add(self.command, 1, wx.EXPAND) 169 | self.command.Bind(wx.EVT_TEXT, self.macrob_enabler) 170 | self.macrob = wx.Button(self,-1, "..", style = wx.BU_EXACTFIT) 171 | self.macrob.Bind(wx.EVT_BUTTON, self.macrob_handler) 172 | xbox.Add(self.macrob, 0) 173 | grid.Add(xbox, 1, wx.EXPAND) 174 | grid.Add(wx.StaticText(self,-1, _("Color")), 0, wx.BOTTOM|wx.RIGHT) 175 | self.color = wx.TextCtrl(self,-1, "") 176 | grid.Add(self.color, 1, wx.EXPAND) 177 | topsizer.Add(grid, 0, wx.EXPAND) 178 | topsizer.Add( (0, 0), 1) 179 | topsizer.Add(self.CreateStdDialogButtonSizer(wx.OK|wx.CANCEL), 0, wx.ALIGN_CENTER) 180 | self.SetSizer(topsizer) 181 | 182 | def macrob_enabler(self, e): 183 | macro = self.command.GetValue() 184 | valid = False 185 | try: 186 | if macro == "": 187 | valid = True 188 | elif self.pronterface.macros.has_key(macro): 189 | valid = True 190 | elif hasattr(self.pronterface.__class__, u"do_"+macro): 191 | valid = False 192 | elif len([c for c in macro if not c.isalnum() and c != "_"]): 193 | valid = False 194 | else: 195 | valid = True 196 | except: 197 | if macro == "": 198 | valid = True 199 | elif self.pronterface.macros.has_key(macro): 200 | valid = True 201 | elif len([c for c in macro if not c.isalnum() and c != "_"]): 202 | valid = False 203 | else: 204 | valid = True 205 | self.macrob.Enable(valid) 206 | 207 | def macrob_handler(self, e): 208 | macro = self.command.GetValue() 209 | macro = self.pronterface.edit_macro(macro) 210 | self.command.SetValue(macro) 211 | if self.name.GetValue()=="": 212 | self.name.SetValue(macro) 213 | 214 | class SpecialButton(object): 215 | 216 | label = None 217 | command = None 218 | background = None 219 | pos = None 220 | span = None 221 | tooltip = None 222 | custom = None 223 | 224 | def __init__(self, label, command, background = None, pos = None, span = None, tooltip = None, custom = False): 225 | self.label = label 226 | self.command = command 227 | self.pos = pos 228 | self.background = background 229 | self.span = span 230 | self.tooltip = tooltip 231 | self.custom = custom 232 | -------------------------------------------------------------------------------- /printrun/svg/css/colour.py: -------------------------------------------------------------------------------- 1 | """ 2 | Parsing for CSS colour values. 3 | Supported formats: 4 | hex literal short: #fff 5 | hex literal long: #fafafa 6 | rgb bytes: rgb(255,100,0) 7 | rgb percent: rgb(100%,100%,0%) 8 | named color: black 9 | """ 10 | import wx 11 | import string 12 | import urlparse 13 | from pyparsing import nums, Literal, Optional, oneOf, Group, StringEnd, Combine, Word, alphas, hexnums 14 | from ..pathdata import number, sign 15 | 16 | number = number.copy() 17 | integerConstant = Word(nums+"+-").setParseAction(lambda t:int(t[0])) 18 | 19 | #rgb format parser 20 | comma = Literal(",").suppress() 21 | def clampColourByte(val): 22 | val = int(val) 23 | return min(max(0,val), 255) 24 | 25 | def clampColourPerc(val): 26 | val = float(val) 27 | return min(max(0,val), 100) 28 | 29 | def parseColorPerc(token): 30 | val = token[0] 31 | val = clampColourPerc(val) 32 | #normalize to bytes 33 | return int(255 * (val / 100.0)) 34 | 35 | 36 | colorByte = Optional(sign) + integerConstant.setParseAction(lambda t: clampColourByte(t[0])) 37 | colorPerc = number.setParseAction(parseColorPerc) + Literal("%").suppress() 38 | 39 | rgb = ( 40 | Literal("rgb(").setParseAction(lambda t: "RGB") + 41 | 42 | ( 43 | #integer constants, ie 255,255,255 44 | Group(colorByte + comma + colorByte + comma + colorByte) ^ 45 | #percentage values, ie 100%, 50% 46 | Group(colorPerc + comma + colorPerc + comma + colorPerc) 47 | ) 48 | + 49 | Literal(")").suppress() + StringEnd() 50 | ) 51 | 52 | def parseShortHex(t): 53 | return tuple(int(x*2, 16) for x in t[0]) 54 | 55 | 56 | doubleHex = Word(hexnums, exact=2).setParseAction(lambda t: int(t[0], 16)) 57 | hexLiteral = (Literal("#").setParseAction(lambda t: "RGB") + 58 | ( 59 | Group(doubleHex + doubleHex + doubleHex) | 60 | Word(hexnums, exact=3).setParseAction(parseShortHex) 61 | ) + StringEnd() 62 | ) 63 | 64 | def parseNamedColour(t): 65 | try: 66 | return ["RGB", NamedColours[t[0].lower()]] 67 | except KeyError: 68 | return ["RGB", (0,0,0)] 69 | 70 | namedColour = Word(alphas).setParseAction(parseNamedColour) 71 | 72 | 73 | colourValue = rgb | hexLiteral | namedColour 74 | 75 | 76 | ##constants 77 | NamedColours = { 78 | #~ #html named colours 79 | #~ "black":(0,0,0), 80 | #~ "silver": (0xc0, 0xc0, 0xc0, 255), 81 | #~ "gray": (0x80, 0x80, 0x80), 82 | #~ "white":(255,255,255), 83 | #~ "maroon":(0x80, 0, 0), 84 | #~ "red":(0xff, 0, 0), 85 | #~ "purple":(0x80, 0, 0x80), 86 | #~ "fuchsia":(0xff, 0, 0xff), 87 | #~ "green": (0, 0x80, 0), 88 | #~ "lime": (0, 0xff, 0), 89 | #~ "olive": (0x80, 0x80, 00), 90 | #~ "yellow":(0xff, 0xff, 00), 91 | #~ "navy": (0, 0, 0x80), 92 | #~ "blue": (0, 0, 0xff), 93 | #~ "teal": (0, 0x80, 0x80), 94 | #~ "aqua": (0, 0xff, 0xff), 95 | #expanded named colors from SVG spc 96 | 'aliceblue' : (240, 248, 255) , 97 | 'antiquewhite' : (250, 235, 215) , 98 | 'aqua' : ( 0, 255, 255) , 99 | 'aquamarine' : (127, 255, 212) , 100 | 'azure' : (240, 255, 255) , 101 | 'beige' : (245, 245, 220) , 102 | 'bisque' : (255, 228, 196) , 103 | 'black' : ( 0, 0, 0) , 104 | 'blanchedalmond' : (255, 235, 205) , 105 | 'blue' : ( 0, 0, 255) , 106 | 'blueviolet' : (138, 43, 226) , 107 | 'brown' : (165, 42, 42) , 108 | 'burlywood' : (222, 184, 135) , 109 | 'cadetblue' : ( 95, 158, 160) , 110 | 'chartreuse' : (127, 255, 0) , 111 | 'chocolate' : (210, 105, 30) , 112 | 'coral' : (255, 127, 80) , 113 | 'cornflowerblue' : (100, 149, 237) , 114 | 'cornsilk' : (255, 248, 220) , 115 | 'crimson' : (220, 20, 60) , 116 | 'cyan' : ( 0, 255, 255) , 117 | 'darkblue' : ( 0, 0, 139) , 118 | 'darkcyan' : ( 0, 139, 139) , 119 | 'darkgoldenrod' : (184, 134, 11) , 120 | 'darkgray' : (169, 169, 169) , 121 | 'darkgreen' : ( 0, 100, 0) , 122 | 'darkgrey' : (169, 169, 169) , 123 | 'darkkhaki' : (189, 183, 107) , 124 | 'darkmagenta' : (139, 0, 139) , 125 | 'darkolivegreen' : ( 85, 107, 47) , 126 | 'darkorange' : (255, 140, 0) , 127 | 'darkorchid' : (153, 50, 204) , 128 | 'darkred' : (139, 0, 0) , 129 | 'darksalmon' : (233, 150, 122) , 130 | 'darkseagreen' : (143, 188, 143) , 131 | 'darkslateblue' : ( 72, 61, 139) , 132 | 'darkslategray' : ( 47, 79, 79) , 133 | 'darkslategrey' : ( 47, 79, 79) , 134 | 'darkturquoise' : ( 0, 206, 209) , 135 | 'darkviolet' : (148, 0, 211) , 136 | 'deeppink' : (255, 20, 147) , 137 | 'deepskyblue' : ( 0, 191, 255) , 138 | 'dimgray' : (105, 105, 105) , 139 | 'dimgrey' : (105, 105, 105) , 140 | 'dodgerblue' : ( 30, 144, 255) , 141 | 'firebrick' : (178, 34, 34) , 142 | 'floralwhite' : (255, 250, 240) , 143 | 'forestgreen' : ( 34, 139, 34) , 144 | 'fuchsia' : (255, 0, 255) , 145 | 'gainsboro' : (220, 220, 220) , 146 | 'ghostwhite' : (248, 248, 255) , 147 | 'gold' : (255, 215, 0) , 148 | 'goldenrod' : (218, 165, 32) , 149 | 'gray' : (128, 128, 128) , 150 | 'grey' : (128, 128, 128) , 151 | 'green' : ( 0, 128, 0) , 152 | 'greenyellow' : (173, 255, 47) , 153 | 'honeydew' : (240, 255, 240) , 154 | 'hotpink' : (255, 105, 180) , 155 | 'indianred' : (205, 92, 92) , 156 | 'indigo' : ( 75, 0, 130) , 157 | 'ivory' : (255, 255, 240) , 158 | 'khaki' : (240, 230, 140) , 159 | 'lavender' : (230, 230, 250) , 160 | 'lavenderblush' : (255, 240, 245) , 161 | 'lawngreen' : (124, 252, 0) , 162 | 'lemonchiffon' : (255, 250, 205) , 163 | 'lightblue' : (173, 216, 230) , 164 | 'lightcoral' : (240, 128, 128) , 165 | 'lightcyan' : (224, 255, 255) , 166 | 'lightgoldenrodyellow' : (250, 250, 210) , 167 | 'lightgray' : (211, 211, 211) , 168 | 'lightgreen' : (144, 238, 144) , 169 | 'lightgrey' : (211, 211, 211) , 170 | 'lightpink' : (255, 182, 193) , 171 | 'lightsalmon' : (255, 160, 122) , 172 | 'lightseagreen' : ( 32, 178, 170) , 173 | 'lightskyblue' : (135, 206, 250) , 174 | 'lightslategray' : (119, 136, 153) , 175 | 'lightslategrey' : (119, 136, 153) , 176 | 'lightsteelblue' : (176, 196, 222) , 177 | 'lightyellow' : (255, 255, 224) , 178 | 'lime' : ( 0, 255, 0) , 179 | 'limegreen' : ( 50, 205, 50) , 180 | 'linen' : (250, 240, 230) , 181 | 'magenta' : (255, 0, 255) , 182 | 'maroon' : (128, 0, 0) , 183 | 'mediumaquamarine' : (102, 205, 170) , 184 | 'mediumblue' : ( 0, 0, 205) , 185 | 'mediumorchid' : (186, 85, 211) , 186 | 'mediumpurple' : (147, 112, 219) , 187 | 'mediumseagreen' : ( 60, 179, 113) , 188 | 'mediumslateblue' : (123, 104, 238) , 189 | 'mediumspringgreen' : ( 0, 250, 154) , 190 | 'mediumturquoise' : ( 72, 209, 204) , 191 | 'mediumvioletred' : (199, 21, 133) , 192 | 'midnightblue' : ( 25, 25, 112) , 193 | 'mintcream' : (245, 255, 250) , 194 | 'mistyrose' : (255, 228, 225) , 195 | 'moccasin' : (255, 228, 181) , 196 | 'navajowhite' : (255, 222, 173) , 197 | 'navy' : ( 0, 0, 128) , 198 | 'oldlace' : (253, 245, 230) , 199 | 'olive' : (128, 128, 0) , 200 | 'olivedrab' : (107, 142, 35) , 201 | 'orange' : (255, 165, 0) , 202 | 'orangered' : (255, 69, 0) , 203 | 'orchid' : (218, 112, 214) , 204 | 'palegoldenrod' : (238, 232, 170) , 205 | 'palegreen' : (152, 251, 152) , 206 | 'paleturquoise' : (175, 238, 238) , 207 | 'palevioletred' : (219, 112, 147) , 208 | 'papayawhip' : (255, 239, 213) , 209 | 'peachpuff' : (255, 218, 185) , 210 | 'peru' : (205, 133, 63) , 211 | 'pink' : (255, 192, 203) , 212 | 'plum' : (221, 160, 221) , 213 | 'powderblue' : (176, 224, 230) , 214 | 'purple' : (128, 0, 128) , 215 | 'red' : (255, 0, 0) , 216 | 'rosybrown' : (188, 143, 143) , 217 | 'royalblue' : ( 65, 105, 225) , 218 | 'saddlebrown' : (139, 69, 19) , 219 | 'salmon' : (250, 128, 114) , 220 | 'sandybrown' : (244, 164, 96) , 221 | 'seagreen' : ( 46, 139, 87) , 222 | 'seashell' : (255, 245, 238) , 223 | 'sienna' : (160, 82, 45) , 224 | 'silver' : (192, 192, 192) , 225 | 'skyblue' : (135, 206, 235) , 226 | 'slateblue' : (106, 90, 205) , 227 | 'slategray' : (112, 128, 144) , 228 | 'slategrey' : (112, 128, 144) , 229 | 'snow' : (255, 250, 250) , 230 | 'springgreen' : ( 0, 255, 127) , 231 | 'steelblue' : ( 70, 130, 180) , 232 | 'tan' : (210, 180, 140) , 233 | 'teal' : ( 0, 128, 128) , 234 | 'thistle' : (216, 191, 216) , 235 | 'tomato' : (255, 99, 71) , 236 | 'turquoise' : ( 64, 224, 208) , 237 | 'violet' : (238, 130, 238) , 238 | 'wheat' : (245, 222, 179) , 239 | 'white' : (255, 255, 255) , 240 | 'whitesmoke' : (245, 245, 245) , 241 | 'yellow' : (255, 255, 0) , 242 | 'yellowgreen' : (154, 205, 50) , 243 | } 244 | 245 | 246 | 247 | def fillCSS2SystemColours(): 248 | #The system colours require a wxApp to be present to retrieve, 249 | #so if you wnat support for them you'll need 250 | #to call this function after your wxApp instance starts 251 | systemColors = { 252 | "ActiveBorder": wx.SYS_COLOUR_ACTIVEBORDER, 253 | "ActiveCaption": wx.SYS_COLOUR_ACTIVECAPTION, 254 | "AppWorkspace": wx.SYS_COLOUR_APPWORKSPACE, 255 | "Background": wx.SYS_COLOUR_BACKGROUND, 256 | "ButtonFace": wx.SYS_COLOUR_BTNFACE, 257 | "ButtonHighlight": wx.SYS_COLOUR_BTNHIGHLIGHT, 258 | "ButtonShadow": wx.SYS_COLOUR_BTNSHADOW, 259 | "ButtonText": wx.SYS_COLOUR_BTNTEXT, 260 | "CaptionText": wx.SYS_COLOUR_CAPTIONTEXT, 261 | "GrayText": wx.SYS_COLOUR_GRAYTEXT, 262 | "Highlight": wx.SYS_COLOUR_HIGHLIGHT, 263 | "HighlightText": wx.SYS_COLOUR_HIGHLIGHTTEXT, 264 | "InactiveBorder": wx.SYS_COLOUR_INACTIVEBORDER, 265 | "InactiveCaption": wx.SYS_COLOUR_INACTIVECAPTION, 266 | "InfoBackground": wx.SYS_COLOUR_INFOBK, 267 | "InfoText": wx.SYS_COLOUR_INFOTEXT, 268 | "Menu": wx.SYS_COLOUR_MENU, 269 | "MenuText": wx.SYS_COLOUR_MENUTEXT, 270 | "Scrollbar": wx.SYS_COLOUR_SCROLLBAR, 271 | "ThreeDDarkShadow": wx.SYS_COLOUR_3DDKSHADOW, 272 | "ThreeDFace": wx.SYS_COLOUR_3DFACE, 273 | "ThreeDHighlight": wx.SYS_COLOUR_3DHIGHLIGHT, 274 | "ThreeDLightShadow": wx.SYS_COLOUR_3DLIGHT, 275 | "ThreeDShadow": wx.SYS_COLOUR_3DSHADOW, 276 | "Window": wx.SYS_COLOUR_WINDOW, 277 | "WindowFrame": wx.SYS_COLOUR_WINDOWFRAME, 278 | "WindowText": wx.SYS_COLOUR_WINDOWTEXT 279 | } 280 | NamedColours.update( 281 | #strip the alpha from the system colors. Is this really what we want to do? 282 | (k.lower(), wx.SystemSettings.GetColour(v)[:3]) for (k,v) in systemColors.iteritems() 283 | ) 284 | -------------------------------------------------------------------------------- /printrun/graph.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # This file is part of the Printrun suite. 4 | # 5 | # Printrun is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Printrun is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Printrun. If not, see . 17 | 18 | import wx, random 19 | 20 | from bufferedcanvas import * 21 | 22 | class Graph(BufferedCanvas): 23 | '''A class to show a Graph with Pronterface.''' 24 | 25 | def __init__(self, parent, id, pos = wx.DefaultPosition, 26 | size = wx.DefaultSize, style = 0): 27 | # Forcing a no full repaint to stop flickering 28 | style = style | wx.NO_FULL_REPAINT_ON_RESIZE 29 | #call super function 30 | #super(Graph, self).__init__(parent, id, pos, size, style) 31 | BufferedCanvas.__init__(self, parent, id) 32 | 33 | self.SetSize(wx.Size(150, 80)) 34 | 35 | self.extruder0temps = [0] 36 | self.extruder0targettemps = [0] 37 | self.extruder1temps = [0] 38 | self.extruder1targettemps = [0] 39 | self.bedtemps = [0] 40 | self.bedtargettemps = [0] 41 | 42 | self.timer = wx.Timer(self) 43 | self.Bind(wx.EVT_TIMER, self.updateTemperatures, self.timer) 44 | 45 | self.maxyvalue = 250 46 | self.ybars = 5 47 | self.xbars = 6 # One bar per 10 second 48 | self.xsteps = 60 # Covering 1 minute in the graph 49 | 50 | self.y_offset = 1 # This is to show the line even when value is 0 and maxyvalue 51 | 52 | self._lastyvalue = 0 53 | 54 | #self.sizer = wx.BoxSizer(wx.HORIZONTAL) 55 | #self.sizer.Add(wx.Button(self, -1, "Button1", (0, 0))) 56 | #self.SetSizer(self.sizer) 57 | 58 | def OnPaint(self, evt): 59 | dc = wx.PaintDC(self) 60 | gc = wx.GraphicsContext.Create(dc) 61 | 62 | def Destroy(self): 63 | #call the super method 64 | super(wx.Panel, self).Destroy() 65 | 66 | def updateTemperatures(self, event): 67 | self.AddBedTemperature(self.bedtemps[-1]) 68 | self.AddBedTargetTemperature(self.bedtargettemps[-1]) 69 | self.AddExtruder0Temperature(self.extruder0temps[-1]) 70 | self.AddExtruder0TargetTemperature(self.extruder0targettemps[-1]) 71 | #self.AddExtruder1Temperature(self.extruder1temps[-1]) 72 | #self.AddExtruder1TargetTemperature(self.extruder1targettemps[-1]) 73 | self.Refresh() 74 | 75 | def drawgrid(self, dc, gc): 76 | #cold, medium, hot = wx.Colour(0, 167, 223), wx.Colour(239, 233, 119), wx.Colour(210, 50.100) 77 | #col1 = wx.Colour(255, 0, 0, 255) 78 | #col2 = wx.Colour(255, 255, 255, 128) 79 | 80 | #b = gc.CreateLinearGradientBrush(0, 0, w, h, col1, col2) 81 | 82 | gc.SetPen(wx.Pen(wx.Colour(255, 0, 0, 0), 4)) 83 | #gc.SetBrush(gc.CreateBrush(wx.Brush(wx.Colour(245, 245, 255, 252)))) 84 | #gc.SetBrush(b) 85 | gc.DrawRectangle(0, 0, self.width, self.height) 86 | 87 | #gc.SetBrush(wx.Brush(wx.Colour(245, 245, 255, 52))) 88 | 89 | #gc.SetBrush(gc.CreateBrush(wx.Brush(wx.Colour(0, 0, 0, 255)))) 90 | #gc.SetPen(wx.Pen(wx.Colour(255, 0, 0, 0), 4)) 91 | 92 | #gc.DrawLines(wx.Point(0, 0), wx.Point(50, 10)) 93 | 94 | #path = gc.CreatePath() 95 | #path.MoveToPoint(0.0, 0.0) 96 | #path.AddLineToPoint(0.0, 100.0) 97 | #path.AddLineToPoint(100.0, 0.0) 98 | #path.AddCircle( 50.0, 50.0, 50.0 ) 99 | #path.CloseSubpath() 100 | #gc.DrawPath(path) 101 | #gc.StrokePath(path) 102 | 103 | font = wx.Font(10, wx.DEFAULT, wx.NORMAL, wx.BOLD) 104 | gc.SetFont(font, wx.Colour(23, 44, 44)) 105 | 106 | dc.SetPen(wx.Pen(wx.Colour(225, 225, 225), 1)) 107 | for x in range(self.xbars): 108 | dc.DrawLine(x*(float(self.width)/self.xbars), 0, x*(float(self.width)/self.xbars), self.height) 109 | 110 | dc.SetPen(wx.Pen(wx.Colour(225, 225, 225), 1)) 111 | for y in range(self.ybars): 112 | y_pos = y*(float(self.height)/self.ybars) 113 | dc.DrawLine(0, y_pos, self.width, y_pos) 114 | gc.DrawText(unicode(int(self.maxyvalue - (y * (self.maxyvalue/self.ybars)))), 1, y_pos - (font.GetPointSize() / 2)) 115 | 116 | if self.timer.IsRunning() == False: 117 | font = wx.Font(14, wx.DEFAULT, wx.NORMAL, wx.BOLD) 118 | gc.SetFont(font, wx.Colour(3, 4, 4)) 119 | gc.DrawText("Graph offline", self.width/2 - (font.GetPointSize() * 3), self.height/2 - (font.GetPointSize() * 1)) 120 | 121 | #dc.DrawCircle(50, 50, 1) 122 | 123 | #gc.SetPen(wx.Pen(wx.Colour(255, 0, 0, 0), 1)) 124 | #gc.DrawLines([[20, 30], [10, 53]]) 125 | #dc.SetPen(wx.Pen(wx.Colour(255, 0, 0, 0), 1)) 126 | 127 | def drawtemperature(self, dc, gc, temperature_list, text, text_xoffset, r, g, b, a): 128 | if self.timer.IsRunning() == False: 129 | dc.SetPen(wx.Pen(wx.Colour(128, 128, 128, 128), 1)) 130 | else: 131 | dc.SetPen(wx.Pen(wx.Colour(r, g, b, a), 1)) 132 | 133 | x_add = float(self.width)/self.xsteps 134 | x_pos = float(0.0) 135 | lastxvalue = float(0.0) 136 | 137 | for temperature in (temperature_list): 138 | y_pos = int((float(self.height-self.y_offset)/self.maxyvalue)*temperature) + self.y_offset 139 | if (x_pos > 0.0): # One need 2 points to draw a line. 140 | dc.DrawLine(lastxvalue, self.height-self._lastyvalue, x_pos, self.height-y_pos) 141 | 142 | lastxvalue = x_pos 143 | x_pos = float(x_pos) + x_add 144 | self._lastyvalue = y_pos 145 | 146 | if len(text) > 0: 147 | font = wx.Font(8, wx.DEFAULT, wx.NORMAL, wx.BOLD) 148 | #font = wx.Font(8, wx.DEFAULT, wx.NORMAL, wx.NORMAL) 149 | if self.timer.IsRunning() == False: 150 | gc.SetFont(font, wx.Colour(128, 128, 128)) 151 | else: 152 | gc.SetFont(font, wx.Colour(r, g, b)) 153 | 154 | #gc.DrawText(text, self.width - (font.GetPointSize() * ((len(text) * text_xoffset + 1))), self.height - self._lastyvalue - (font.GetPointSize() / 2)) 155 | gc.DrawText(text, x_pos - x_add - (font.GetPointSize() * ((len(text) * text_xoffset + 1))), self.height - self._lastyvalue - (font.GetPointSize() / 2)) 156 | #gc.DrawText(text, self.width - (font.GetPixelSize().GetWidth() * ((len(text) * text_xoffset + 1) + 1)), self.height - self._lastyvalue - (font.GetPointSize() / 2)) 157 | 158 | 159 | def drawbedtemp(self, dc, gc): 160 | self.drawtemperature(dc, gc, self.bedtemps, "Bed", 2, 255, 0, 0, 128) 161 | 162 | def drawbedtargettemp(self, dc, gc): 163 | self.drawtemperature(dc, gc, self.bedtargettemps, "Bed Target", 2, 255, 120, 0, 128) 164 | 165 | 166 | def drawextruder0temp(self, dc, gc): 167 | self.drawtemperature(dc, gc, self.extruder0temps, "Ex0", 1, 0, 155, 255, 128) 168 | 169 | def drawextruder0targettemp(self, dc, gc): 170 | self.drawtemperature(dc, gc, self.extruder0targettemps, "Ex0 Target", 2, 0, 5, 255, 128) 171 | 172 | 173 | def drawextruder1temp(self, dc, gc): 174 | self.drawtemperature(dc, gc, self.extruder1temps, "Ex1", 3, 55, 55, 0, 128) 175 | 176 | def drawextruder1targettemp(self, dc, gc): 177 | self.drawtemperature(dc, gc, self.extruder1targettemps, "Ex1 Target", 2, 55, 55, 0, 128) 178 | 179 | 180 | def SetBedTemperature(self, value): 181 | self.bedtemps.pop() 182 | self.bedtemps.append(value) 183 | 184 | def AddBedTemperature(self, value): 185 | self.bedtemps.append(value) 186 | if (len(self.bedtemps)-1) * float(self.width)/self.xsteps > self.width: 187 | self.bedtemps.pop(0) 188 | 189 | def SetBedTargetTemperature(self, value): 190 | self.bedtargettemps.pop() 191 | self.bedtargettemps.append(value) 192 | 193 | def AddBedTargetTemperature(self, value): 194 | self.bedtargettemps.append(value) 195 | if (len(self.bedtargettemps)-1) * float(self.width)/self.xsteps > self.width: 196 | self.bedtargettemps.pop(0) 197 | 198 | def SetExtruder0Temperature(self, value): 199 | self.extruder0temps.pop() 200 | self.extruder0temps.append(value) 201 | 202 | def AddExtruder0Temperature(self, value): 203 | self.extruder0temps.append(value) 204 | if (len(self.extruder0temps)-1) * float(self.width)/self.xsteps > self.width: 205 | self.extruder0temps.pop(0) 206 | 207 | def SetExtruder0TargetTemperature(self, value): 208 | self.extruder0targettemps.pop() 209 | self.extruder0targettemps.append(value) 210 | 211 | def AddExtruder0TargetTemperature(self, value): 212 | self.extruder0targettemps.append(value) 213 | if (len(self.extruder0targettemps)-1) * float(self.width)/self.xsteps > self.width: 214 | self.extruder0targettemps.pop(0) 215 | 216 | def SetExtruder1Temperature(self, value): 217 | self.extruder1temps.pop() 218 | self.extruder1temps.append(value) 219 | 220 | def AddExtruder1Temperature(self, value): 221 | self.extruder1temps.append(value) 222 | if (len(self.extruder1temps)-1) * float(self.width)/self.xsteps > self.width: 223 | self.extruder1temps.pop(0) 224 | 225 | def SetExtruder1TargetTemperature(self, value): 226 | self.extruder1targettemps.pop() 227 | self.extruder1targettemps.append(value) 228 | 229 | def AddExtruder1TargetTemperature(self, value): 230 | self.extruder1targettemps.append(value) 231 | if (len(self.extruder1targettemps)-1) * float(self.width)/self.xsteps > self.width: 232 | self.extruder1targettemps.pop(0) 233 | 234 | def StartPlotting(self, time): 235 | self.Refresh() 236 | self.timer.Start(time) 237 | 238 | def StopPlotting(self): 239 | self.timer.Stop() 240 | self.Refresh() 241 | 242 | def draw(self, dc, w, h): 243 | dc.Clear() 244 | gc = wx.GraphicsContext.Create(dc) 245 | self.width = w 246 | self.height = h 247 | self.drawgrid(dc, gc) 248 | self.drawbedtargettemp(dc, gc) 249 | self.drawbedtemp(dc, gc) 250 | self.drawextruder0targettemp(dc, gc) 251 | self.drawextruder0temp(dc, gc) 252 | self.drawextruder1targettemp(dc, gc) 253 | self.drawextruder1temp(dc, gc) 254 | -------------------------------------------------------------------------------- /printrun/projectlayer.py: -------------------------------------------------------------------------------- 1 | # This file is part of the Printrun suite. 2 | # 3 | # Printrun is free software: you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation, either version 3 of the License, or 6 | # (at your option) any later version. 7 | # 8 | # Printrun is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with Printrun. If not, see . 15 | 16 | import xml.etree.ElementTree 17 | import wx 18 | import os 19 | import zipfile 20 | import tempfile 21 | import shutil 22 | import svg.document as wxpsvgdocument 23 | import imghdr 24 | 25 | class dispframe(wx.Frame): 26 | def __init__(self, parent, title, res = (800, 600), printer = None): 27 | wx.Frame.__init__(self, parent = parent, title = title) 28 | self.p = printer 29 | self.pic = wx.StaticBitmap(self) 30 | self.bitmap = wx.EmptyBitmap(*res) 31 | self.bbitmap = wx.EmptyBitmap(*res) 32 | self.slicer = 'Skeinforge' 33 | dc = wx.MemoryDC() 34 | dc.SelectObject(self.bbitmap) 35 | dc.SetBackground(wx.Brush("black")) 36 | dc.Clear() 37 | dc.SelectObject(wx.NullBitmap) 38 | 39 | self.SetBackgroundColour("black") 40 | self.pic.Hide() 41 | self.pen = wx.Pen("white") 42 | self.brush = wx.Brush("white") 43 | self.SetDoubleBuffered(True) 44 | self.Show() 45 | 46 | def drawlayer(self, image): 47 | try: 48 | dc = wx.MemoryDC() 49 | dc.SelectObject(self.bitmap) 50 | dc.SetBackground(wx.Brush("black")) 51 | dc.Clear() 52 | dc.SetPen(self.pen) 53 | dc.SetBrush(self.brush) 54 | 55 | if self.slicer == 'Skeinforge': 56 | for i in image: 57 | #print i 58 | points = [wx.Point(*map(lambda x:int(round(float(x) * self.scale)), j.strip().split())) for j in i.strip().split("M")[1].split("L")] 59 | dc.DrawPolygon(points, self.size[0] / 2, self.size[1] / 2) 60 | elif self.slicer == 'Slic3r': 61 | gc = wx.GraphicsContext_Create(dc) 62 | gc.Translate(*self.offset) 63 | gc.Scale(self.scale, self.scale) 64 | wxpsvgdocument.SVGDocument(image).render(gc) 65 | elif self.slicer == 'bitmap': 66 | dc.DrawBitmap(image, self.offset[0], -self.offset[1], True) 67 | else: 68 | raise Exception(self.slicer + " is an unknown method.") 69 | self.pic.SetBitmap(self.bitmap) 70 | self.pic.Show() 71 | self.Refresh() 72 | 73 | 74 | except: 75 | raise 76 | pass 77 | 78 | def showimgdelay(self, image): 79 | self.drawlayer(image) 80 | self.pic.Show() 81 | self.Refresh() 82 | 83 | self.Refresh() 84 | if self.p != None and self.p.online: 85 | self.p.send_now("G91") 86 | self.p.send_now("G1 Z%f F300" % (self.thickness,)) 87 | self.p.send_now("G90") 88 | 89 | def nextimg(self, event): 90 | if self.index < len(self.layers): 91 | i = self.index 92 | 93 | print i 94 | wx.CallAfter(self.showimgdelay, self.layers[i]) 95 | wx.FutureCall(1000 * self.interval, self.pic.Hide) 96 | self.index += 1 97 | else: 98 | print "end" 99 | wx.CallAfter(self.pic.Hide) 100 | wx.CallAfter(self.Refresh) 101 | wx.CallAfter(self.ShowFullScreen, 0) 102 | wx.CallAfter(self.timer.Stop) 103 | 104 | def present(self, layers, interval = 0.5, pause = 0.2, thickness = 0.4, scale = 20, size = (800, 600), offset = (0, 0)): 105 | wx.CallAfter(self.pic.Hide) 106 | wx.CallAfter(self.Refresh) 107 | self.layers = layers 108 | self.scale = scale 109 | self.thickness = thickness 110 | self.index = 0 111 | self.size = (size[0] + offset[0], size[1] + offset[1]) 112 | self.interval = interval 113 | self.offset = offset 114 | self.timer = wx.Timer(self, 1) 115 | self.timer.Bind(wx.EVT_TIMER, self.nextimg) 116 | self.Bind(wx.EVT_TIMER, self.nextimg) 117 | self.timer.Start(1000 * interval + 1000 * pause) 118 | 119 | class setframe(wx.Frame): 120 | 121 | def __init__(self, parent, printer = None): 122 | wx.Frame.__init__(self, parent, title = "Projector setup") 123 | self.f = dispframe(None, "", printer = printer) 124 | self.panel = wx.Panel(self) 125 | self.panel.SetBackgroundColour("orange") 126 | self.bload = wx.Button(self.panel, -1, "Load", pos = (0, 0)) 127 | self.bload.Bind(wx.EVT_BUTTON, self.loadfile) 128 | 129 | wx.StaticText(self.panel, -1, "Layer:", pos = (0, 30)) 130 | wx.StaticText(self.panel, -1, "mm", pos = (130, 30)) 131 | self.thickness = wx.TextCtrl(self.panel, -1, "0.5", pos = (50, 30)) 132 | 133 | wx.StaticText(self.panel, -1, "Exposure:", pos = (0, 60)) 134 | wx.StaticText(self.panel, -1, "s", pos = (130, 60)) 135 | self.interval = wx.TextCtrl(self.panel, -1, "0.5", pos = (50, 60)) 136 | 137 | wx.StaticText(self.panel, -1, "Blank:", pos = (0, 90)) 138 | wx.StaticText(self.panel, -1, "s", pos = (130, 90)) 139 | self.delay = wx.TextCtrl(self.panel, -1, "0.5", pos = (50, 90)) 140 | 141 | wx.StaticText(self.panel, -1, "Scale:", pos = (0, 120)) 142 | wx.StaticText(self.panel, -1, "x", pos = (130, 120)) 143 | self.scale = wx.TextCtrl(self.panel, -1, "5", pos = (50, 120)) 144 | 145 | wx.StaticText(self.panel, -1, "X:", pos = (160, 30)) 146 | self.X = wx.TextCtrl(self.panel, -1, "1024", pos = (210, 30)) 147 | 148 | wx.StaticText(self.panel, -1, "Y:", pos = (160, 60)) 149 | self.Y = wx.TextCtrl(self.panel, -1, "768", pos = (210, 60)) 150 | 151 | wx.StaticText(self.panel, -1, "OffsetX:", pos = (160, 90)) 152 | self.offsetX = wx.TextCtrl(self.panel, -1, "50", pos = (210, 90)) 153 | 154 | wx.StaticText(self.panel, -1, "OffsetY:", pos = (160, 120)) 155 | self.offsetY = wx.TextCtrl(self.panel, -1, "50", pos = (210, 120)) 156 | 157 | self.bload = wx.Button(self.panel, -1, "Present", pos = (0, 150)) 158 | self.bload.Bind(wx.EVT_BUTTON, self.startdisplay) 159 | 160 | wx.StaticText(self.panel, -1, "Fullscreen:", pos = (160, 150)) 161 | self.fullscreen = wx.CheckBox(self.panel, -1, pos = (220, 150)) 162 | self.fullscreen.SetValue(True) 163 | 164 | self.Show() 165 | 166 | def __del__(self): 167 | if hasattr(self, 'image_dir') and self.image_dir != '': 168 | shutil.rmtree(self.image_dir) 169 | 170 | def parsesvg(self, name): 171 | et = xml.etree.ElementTree.ElementTree(file = name) 172 | #xml.etree.ElementTree.dump(et) 173 | slicer = 'Slic3r' if et.getroot().find('{http://www.w3.org/2000/svg}metadata') == None else 'Skeinforge' 174 | zlast = 0 175 | zdiff = 0 176 | ol = [] 177 | if (slicer == 'Slic3r'): 178 | height = et.getroot().get('height') 179 | width = et.getroot().get('width') 180 | 181 | for i in et.findall("{http://www.w3.org/2000/svg}g"): 182 | z = float(i.get('{http://slic3r.org/namespaces/slic3r}z')) 183 | zdiff = z - zlast 184 | zlast = z 185 | 186 | svgSnippet = xml.etree.ElementTree.Element('{http://www.w3.org/2000/svg}svg') 187 | svgSnippet.set('height', height + 'mm') 188 | svgSnippet.set('width', width + 'mm') 189 | svgSnippet.set('viewBox', '0 0 ' + height + ' ' + width) 190 | svgSnippet.append(i) 191 | 192 | ol += [svgSnippet] 193 | else : 194 | for i in et.findall("{http://www.w3.org/2000/svg}g")[0].findall("{http://www.w3.org/2000/svg}g"): 195 | z = float(i.get('id').split("z:")[-1]) 196 | zdiff = z - zlast 197 | zlast = z 198 | path = i.find('{http://www.w3.org/2000/svg}path') 199 | ol += [(path.get("d").split("z"))[:-1]] 200 | return ol, zdiff, slicer 201 | 202 | def parse3DLPzip(self, name): 203 | if not zipfile.is_zipfile(name): 204 | raise Exception(name + " is not a zip file!") 205 | acceptedImageTypes = ['gif','tiff','jpg','jpeg','bmp','png'] 206 | zipFile = zipfile.ZipFile(name, 'r') 207 | self.image_dir = tempfile.mkdtemp() 208 | zipFile.extractall(self.image_dir) 209 | ol = [] 210 | for f in os.listdir(self.image_dir): 211 | path = os.path.join(self.image_dir, f) 212 | if os.path.isfile(path) and imghdr.what(path) in acceptedImageTypes: 213 | ol.append(wx.Bitmap(path)) 214 | return ol, -1, "bitmap" 215 | 216 | def loadfile(self, event): 217 | dlg = wx.FileDialog(self, ("Open file to print"), style = wx.FD_OPEN | wx.FD_FILE_MUST_EXIST) 218 | dlg.SetWildcard(("Slic3r or Skeinforge svg files (;*.svg;*.SVG;);3DLP Zip (;*.3dlp.zip;)")) 219 | if(dlg.ShowModal() == wx.ID_OK): 220 | name = dlg.GetPath() 221 | if not(os.path.exists(name)): 222 | self.status.SetStatusText(("File not found!")) 223 | return 224 | if name.endswith(".3dlp.zip"): 225 | layers = self.parse3DLPzip(name) 226 | layerHeight = float(self.thickness.GetValue()) 227 | else: 228 | layers = self.parsesvg(name) 229 | layerHeight = layers[1] 230 | self.thickness.SetValue(str(layers[1])) 231 | print "Layer thickness detected:", layerHeight, "mm" 232 | print len(layers[0]), "layers found, total height", layerHeight * len(layers[0]), "mm" 233 | self.layers = layers 234 | self.f.slicer = layers[2] 235 | dlg.Destroy() 236 | 237 | def startdisplay(self, event): 238 | self.f.Raise() 239 | if (self.fullscreen.GetValue()): 240 | self.f.ShowFullScreen(1) 241 | l = self.layers[0][:] 242 | self.f.present(l, 243 | thickness = float(self.thickness.GetValue()), 244 | interval = float(self.interval.GetValue()), 245 | scale = float(self.scale.GetValue()), 246 | pause = float(self.delay.GetValue()), 247 | size = (float(self.X.GetValue()), float(self.Y.GetValue())), 248 | offset = (float(self.offsetX.GetValue()), float(self.offsetY.GetValue()))) 249 | 250 | if __name__ == "__main__": 251 | a = wx.App() 252 | setframe(None).Show() 253 | a.MainLoop() 254 | -------------------------------------------------------------------------------- /locale/pronterface.pot: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR ORGANIZATION 3 | # FIRST AUTHOR , YEAR. 4 | # 5 | msgid "" 6 | msgstr "" 7 | "Project-Id-Version: PACKAGE VERSION\n" 8 | "POT-Creation-Date: 2012-08-08 10:09+CEST\n" 9 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 10 | "Last-Translator: FULL NAME \n" 11 | "Language-Team: LANGUAGE \n" 12 | "MIME-Version: 1.0\n" 13 | "Content-Type: text/plain; charset=CHARSET\n" 14 | "Content-Transfer-Encoding: ENCODING\n" 15 | "Generated-By: pygettext.py 1.5\n" 16 | 17 | 18 | #: printrun/pronterface_widgets.py:34 19 | msgid "Find" 20 | msgstr "" 21 | 22 | #: printrun/pronterface_widgets.py:36 23 | msgid "Save" 24 | msgstr "" 25 | 26 | #: printrun/pronterface_widgets.py:41 pronterface.py:477 pronterface.py:1535 27 | msgid "Cancel" 28 | msgstr "" 29 | 30 | #: printrun/pronterface_widgets.py:125 31 | msgid "Edit settings" 32 | msgstr "" 33 | 34 | #: printrun/pronterface_widgets.py:127 35 | msgid "Defaults" 36 | msgstr "" 37 | 38 | #: printrun/pronterface_widgets.py:156 39 | msgid "Custom button" 40 | msgstr "" 41 | 42 | #: printrun/pronterface_widgets.py:161 43 | msgid "Button title" 44 | msgstr "" 45 | 46 | #: printrun/pronterface_widgets.py:164 47 | msgid "Command" 48 | msgstr "" 49 | 50 | #: printrun/pronterface_widgets.py:173 51 | msgid "Color" 52 | msgstr "" 53 | 54 | #: pronterface.py:26 55 | msgid "WX is not installed. This program requires WX to run." 56 | msgstr "" 57 | 58 | #: pronterface.py:93 59 | msgid "" 60 | "Dimensions of Build Platform\n" 61 | " & optional offset of origin\n" 62 | "\n" 63 | "Examples:\n" 64 | " XXXxYYY\n" 65 | " XXX,YYY,ZZZ\n" 66 | " XXXxYYYxZZZ+OffX+OffY+OffZ" 67 | msgstr "" 68 | 69 | #: pronterface.py:94 70 | msgid "Last Set Temperature for the Heated Print Bed" 71 | msgstr "" 72 | 73 | #: pronterface.py:95 74 | msgid "Folder of last opened file" 75 | msgstr "" 76 | 77 | #: pronterface.py:96 78 | msgid "Last Temperature of the Hot End" 79 | msgstr "" 80 | 81 | #: pronterface.py:97 82 | msgid "Width of Extrusion in Preview (default: 0.5)" 83 | msgstr "" 84 | 85 | #: pronterface.py:98 86 | msgid "Fine Grid Spacing (default: 10)" 87 | msgstr "" 88 | 89 | #: pronterface.py:99 90 | msgid "Coarse Grid Spacing (default: 50)" 91 | msgstr "" 92 | 93 | #: pronterface.py:100 94 | msgid "Pronterface background color (default: #FFFFFF)" 95 | msgstr "" 96 | 97 | #: pronterface.py:103 98 | msgid "Printer Interface" 99 | msgstr "" 100 | 101 | #: pronterface.py:122 102 | msgid "Motors off" 103 | msgstr "" 104 | 105 | #: pronterface.py:122 106 | msgid "Switch all motors off" 107 | msgstr "" 108 | 109 | #: pronterface.py:123 110 | msgid "Check current hotend temperature" 111 | msgstr "" 112 | 113 | #: pronterface.py:123 114 | msgid "Check temp" 115 | msgstr "" 116 | 117 | #: pronterface.py:124 118 | msgid "Advance extruder by set length" 119 | msgstr "" 120 | 121 | #: pronterface.py:124 122 | msgid "Extrude" 123 | msgstr "" 124 | 125 | #: pronterface.py:125 126 | msgid "Reverse" 127 | msgstr "" 128 | 129 | #: pronterface.py:125 130 | msgid "Reverse extruder by set length" 131 | msgstr "" 132 | 133 | #: pronterface.py:143 134 | msgid "" 135 | "# I moved all your custom buttons into .pronsolerc.\n" 136 | "# Please don't add them here any more.\n" 137 | "# Backup of your old buttons is in custombtn.old\n" 138 | msgstr "" 139 | 140 | #: pronterface.py:148 141 | msgid "Note!!! You have specified custom buttons in both custombtn.txt and .pronsolerc" 142 | msgstr "" 143 | 144 | #: pronterface.py:149 145 | msgid "Ignoring custombtn.txt. Remove all current buttons to revert to custombtn.txt" 146 | msgstr "" 147 | 148 | #: pronterface.py:181 149 | msgid "Failed to start web interface" 150 | msgstr "" 151 | 152 | #: pronterface.py:185 153 | msgid "CherryPy is not installed. Web Interface Disabled." 154 | msgstr "" 155 | 156 | #: pronterface.py:197 pronterface.py:603 pronterface.py:1525 157 | #: pronterface.py:1578 pronterface.py:1705 pronterface.py:1765 158 | #: pronterface.py:1778 159 | msgid "Print" 160 | msgstr "" 161 | 162 | #: pronterface.py:207 163 | msgid "Printer is now online." 164 | msgstr "" 165 | 166 | #: pronterface.py:208 167 | msgid "Disconnect" 168 | msgstr "" 169 | 170 | #: pronterface.py:331 171 | msgid "Setting hotend temperature to %f degrees Celsius." 172 | msgstr "" 173 | 174 | #: pronterface.py:334 pronterface.py:356 pronterface.py:428 175 | msgid "Printer is not online." 176 | msgstr "" 177 | 178 | #: pronterface.py:336 179 | msgid "You cannot set negative temperatures. To turn the hotend off entirely, set its temperature to 0." 180 | msgstr "" 181 | 182 | #: pronterface.py:338 pronterface.py:364 183 | msgid "You must enter a temperature. (%s)" 184 | msgstr "" 185 | 186 | #: pronterface.py:353 187 | msgid "Setting bed temperature to %f degrees Celsius." 188 | msgstr "" 189 | 190 | #: pronterface.py:360 191 | msgid "You cannot set negative temperatures. To turn the bed off entirely, set its temperature to 0." 192 | msgstr "" 193 | 194 | #: pronterface.py:381 195 | msgid "Do you want to erase the macro?" 196 | msgstr "" 197 | 198 | #: pronterface.py:385 199 | msgid "Cancelled." 200 | msgstr "" 201 | 202 | #: pronterface.py:436 203 | msgid " Opens file" 204 | msgstr "" 205 | 206 | #: pronterface.py:436 207 | msgid "&Open..." 208 | msgstr "" 209 | 210 | #: pronterface.py:437 211 | msgid " Edit open file" 212 | msgstr "" 213 | 214 | #: pronterface.py:437 215 | msgid "&Edit..." 216 | msgstr "" 217 | 218 | #: pronterface.py:438 219 | msgid " Clear output console" 220 | msgstr "" 221 | 222 | #: pronterface.py:438 223 | msgid "Clear console" 224 | msgstr "" 225 | 226 | #: pronterface.py:439 227 | msgid " Project slices" 228 | msgstr "" 229 | 230 | #: pronterface.py:439 231 | msgid "Projector" 232 | msgstr "" 233 | 234 | #: pronterface.py:440 235 | msgid " Closes the Window" 236 | msgstr "" 237 | 238 | #: pronterface.py:440 239 | msgid "E&xit" 240 | msgstr "" 241 | 242 | #: pronterface.py:441 243 | msgid "&File" 244 | msgstr "" 245 | 246 | #: pronterface.py:446 247 | msgid "&Macros" 248 | msgstr "" 249 | 250 | #: pronterface.py:447 251 | msgid "<&New...>" 252 | msgstr "" 253 | 254 | #: pronterface.py:448 255 | msgid " Options dialog" 256 | msgstr "" 257 | 258 | #: pronterface.py:448 259 | msgid "&Options" 260 | msgstr "" 261 | 262 | #: pronterface.py:450 263 | msgid " Adjust slicing settings" 264 | msgstr "" 265 | 266 | #: pronterface.py:450 267 | msgid "Slicing Settings" 268 | msgstr "" 269 | 270 | #: pronterface.py:452 271 | msgid "&Settings" 272 | msgstr "" 273 | 274 | #: pronterface.py:467 275 | msgid "Enter macro name" 276 | msgstr "" 277 | 278 | #: pronterface.py:470 279 | msgid "Macro name:" 280 | msgstr "" 281 | 282 | #: pronterface.py:473 283 | msgid "Ok" 284 | msgstr "" 285 | 286 | #: pronterface.py:495 287 | msgid "Macro name may contain only ASCII alphanumeric symbols and underscores" 288 | msgstr "" 289 | 290 | #: pronterface.py:500 291 | msgid "Name '%s' is being used by built-in command" 292 | msgstr "" 293 | 294 | #: pronterface.py:548 295 | msgid "Port" 296 | msgstr "" 297 | 298 | #: pronterface.py:570 pronterface.py:1747 299 | msgid "Connect" 300 | msgstr "" 301 | 302 | #: pronterface.py:574 303 | msgid "Reset" 304 | msgstr "" 305 | 306 | #: pronterface.py:589 307 | msgid "Load file" 308 | msgstr "" 309 | 310 | #: pronterface.py:593 311 | msgid "Compose" 312 | msgstr "" 313 | 314 | #: pronterface.py:598 315 | msgid "SD" 316 | msgstr "" 317 | 318 | #: pronterface.py:608 pronterface.py:1579 pronterface.py:1631 319 | #: pronterface.py:1681 pronterface.py:1704 pronterface.py:1764 320 | #: pronterface.py:1781 321 | msgid "Pause" 322 | msgstr "" 323 | 324 | #: pronterface.py:612 325 | msgid "Recover" 326 | msgstr "" 327 | 328 | #: pronterface.py:630 329 | msgid "Send" 330 | msgstr "" 331 | 332 | #: pronterface.py:671 333 | msgid "XY:" 334 | msgstr "" 335 | 336 | #: pronterface.py:673 337 | msgid "mm/min Z:" 338 | msgstr "" 339 | 340 | #: pronterface.py:678 341 | msgid "Watch" 342 | msgstr "" 343 | 344 | #: pronterface.py:683 345 | msgid "Heat:" 346 | msgstr "" 347 | 348 | #: pronterface.py:686 pronterface.py:709 349 | msgid "Off" 350 | msgstr "" 351 | 352 | #: pronterface.py:700 pronterface.py:723 353 | msgid "Set" 354 | msgstr "" 355 | 356 | #: pronterface.py:706 357 | msgid "Bed:" 358 | msgstr "" 359 | 360 | #: pronterface.py:756 361 | msgid "mm" 362 | msgstr "" 363 | 364 | #: pronterface.py:764 365 | msgid "" 366 | "mm/\n" 367 | "min" 368 | msgstr "" 369 | 370 | #: pronterface.py:821 pronterface.py:1387 pronterface.py:1625 371 | #: pronterface.py:1723 372 | msgid "Not connected to printer." 373 | msgstr "" 374 | 375 | #: pronterface.py:869 376 | msgid "SD Upload" 377 | msgstr "" 378 | 379 | #: pronterface.py:873 380 | msgid "SD Print" 381 | msgstr "" 382 | 383 | #: pronterface.py:914 384 | msgid "Mini mode" 385 | msgstr "" 386 | 387 | #: pronterface.py:921 388 | msgid "Full mode" 389 | msgstr "" 390 | 391 | #: pronterface.py:946 392 | msgid "Execute command: " 393 | msgstr "" 394 | 395 | #: pronterface.py:957 396 | msgid "click to add new custom button" 397 | msgstr "" 398 | 399 | #: pronterface.py:979 400 | msgid "Defines custom button. Usage: button \"title\" [/c \"colour\"] command" 401 | msgstr "" 402 | 403 | #: pronterface.py:1003 404 | msgid "Custom button number should be between 0 and 63" 405 | msgstr "" 406 | 407 | #: pronterface.py:1096 408 | msgid "Edit custom button '%s'" 409 | msgstr "" 410 | 411 | #: pronterface.py:1098 412 | msgid "Move left <<" 413 | msgstr "" 414 | 415 | #: pronterface.py:1101 416 | msgid "Move right >>" 417 | msgstr "" 418 | 419 | #: pronterface.py:1105 420 | msgid "Remove custom button '%s'" 421 | msgstr "" 422 | 423 | #: pronterface.py:1108 424 | msgid "Add custom button" 425 | msgstr "" 426 | 427 | #: pronterface.py:1270 428 | msgid "event object missing" 429 | msgstr "" 430 | 431 | #: pronterface.py:1306 432 | msgid "Invalid period given." 433 | msgstr "" 434 | 435 | #: pronterface.py:1311 436 | msgid "Monitoring printer." 437 | msgstr "" 438 | 439 | #: pronterface.py:1315 440 | msgid "Done monitoring." 441 | msgstr "" 442 | 443 | #: pronterface.py:1355 444 | msgid " SD printing:%04.2f %%" 445 | msgstr "" 446 | 447 | #: pronterface.py:1358 448 | msgid " Printing: %04.2f%% |" 449 | msgstr "" 450 | 451 | #: pronterface.py:1359 452 | msgid " Line# %d of %d lines |" 453 | msgstr "" 454 | 455 | #: pronterface.py:1364 456 | msgid " Est: %s of %s remaining | " 457 | msgstr "" 458 | 459 | #: pronterface.py:1366 460 | msgid " Z: %0.2f mm" 461 | msgstr "" 462 | 463 | #: pronterface.py:1439 464 | msgid "Opening file failed." 465 | msgstr "" 466 | 467 | #: pronterface.py:1445 468 | msgid "Starting print" 469 | msgstr "" 470 | 471 | #: pronterface.py:1466 472 | msgid "Pick SD file" 473 | msgstr "" 474 | 475 | #: pronterface.py:1466 476 | msgid "Select the file to print" 477 | msgstr "" 478 | 479 | #: pronterface.py:1501 480 | msgid "Failed to execute slicing software: " 481 | msgstr "" 482 | 483 | #: pronterface.py:1510 484 | msgid "Slicing..." 485 | msgstr "" 486 | 487 | #: pronterface.py:1523 488 | msgid ", %d lines" 489 | msgstr "" 490 | 491 | #: pronterface.py:1523 492 | msgid "Loaded " 493 | msgstr "" 494 | 495 | #: pronterface.py:1530 496 | msgid "Load File" 497 | msgstr "" 498 | 499 | #: pronterface.py:1536 500 | msgid "Slicing " 501 | msgstr "" 502 | 503 | #: pronterface.py:1555 504 | msgid "Open file to print" 505 | msgstr "" 506 | 507 | #: pronterface.py:1556 508 | msgid "OBJ, STL, and GCODE files (*.gcode;*.gco;*.g;*.stl;*.STL;*.obj;*.OBJ)|*.gcode;*.gco;*.g;*.stl;*.STL;*.obj;*.OBJ|All Files (*.*)|*.*" 509 | msgstr "" 510 | 511 | #: pronterface.py:1563 512 | msgid "File not found!" 513 | msgstr "" 514 | 515 | #: pronterface.py:1577 516 | msgid "Loaded %s, %d lines" 517 | msgstr "" 518 | 519 | #: pronterface.py:1588 520 | msgid "" 521 | "mm of filament used in this print\n" 522 | msgstr "" 523 | 524 | #: pronterface.py:1589 pronterface.py:1591 525 | msgid "" 526 | "the print goes from %f mm to %f mm in X\n" 527 | "and is %f mm wide\n" 528 | msgstr "" 529 | 530 | #: pronterface.py:1592 531 | msgid "" 532 | "the print goes from %f mm to %f mm in Y\n" 533 | "and is %f mm wide\n" 534 | msgstr "" 535 | 536 | #: pronterface.py:1593 537 | msgid "" 538 | "the print goes from %f mm to %f mm in Z\n" 539 | "and is %f mm high\n" 540 | msgstr "" 541 | 542 | #: pronterface.py:1595 543 | msgid "Estimated duration (pessimistic): " 544 | msgstr "" 545 | 546 | #: pronterface.py:1622 547 | msgid "No file loaded. Please use load first." 548 | msgstr "" 549 | 550 | #: pronterface.py:1633 551 | msgid "Restart" 552 | msgstr "" 553 | 554 | #: pronterface.py:1637 555 | msgid "File upload complete" 556 | msgstr "" 557 | 558 | #: pronterface.py:1656 559 | msgid "Pick SD filename" 560 | msgstr "" 561 | 562 | #: pronterface.py:1663 563 | msgid "Paused." 564 | msgstr "" 565 | 566 | #: pronterface.py:1674 567 | msgid "Resume" 568 | msgstr "" 569 | 570 | #: pronterface.py:1688 571 | msgid "Connecting..." 572 | msgstr "" 573 | 574 | #: pronterface.py:1738 575 | msgid "Disconnected." 576 | msgstr "" 577 | 578 | #: pronterface.py:1771 579 | msgid "Reset." 580 | msgstr "" 581 | 582 | #: pronterface.py:1772 583 | msgid "Are you sure you want to reset the printer?" 584 | msgstr "" 585 | 586 | #: pronterface.py:1772 587 | msgid "Reset?" 588 | msgstr "" 589 | 590 | -------------------------------------------------------------------------------- /printrun/xybuttons.py: -------------------------------------------------------------------------------- 1 | # This file is part of the Printrun suite. 2 | # 3 | # Printrun is free software: you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation, either version 3 of the License, or 6 | # (at your option) any later version. 7 | # 8 | # Printrun is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with Printrun. If not, see . 15 | 16 | import wx, os, math 17 | from bufferedcanvas import * 18 | from printrun_utils import * 19 | 20 | def sign(n): 21 | if n < 0: return -1 22 | elif n > 0: return 1 23 | else: return 0 24 | 25 | class XYButtons(BufferedCanvas): 26 | keypad_positions = { 27 | 0: (105, 102), 28 | 1: (86, 83), 29 | 2: (68, 65), 30 | 3: (53, 50) 31 | } 32 | corner_size = (49, 49) 33 | corner_inset = (8, 6) 34 | label_overlay_positions = { 35 | 0: (142, 105, 11), 36 | 1: (160, 85, 13), 37 | 2: (179, 65, 15), 38 | 3: (201, 42, 16) 39 | } 40 | concentric_circle_radii = [11, 45, 69, 94, 115] 41 | center = (124, 121) 42 | spacer = 7 43 | 44 | def __init__(self, parent, moveCallback = None, cornerCallback = None, spacebarCallback = None, bgcolor = "#FFFFFF", ID=-1): 45 | self.bg_bmp = wx.Image(imagefile("control_xy.png"), wx.BITMAP_TYPE_PNG).ConvertToBitmap() 46 | self.keypad_bmp = wx.Image(imagefile("arrow_keys.png"), wx.BITMAP_TYPE_PNG).ConvertToBitmap() 47 | self.keypad_idx = -1 48 | self.quadrant = None 49 | self.concentric = None 50 | self.corner = None 51 | self.moveCallback = moveCallback 52 | self.cornerCallback = cornerCallback 53 | self.spacebarCallback = spacebarCallback 54 | self.enabled = False 55 | # Remember the last clicked buttons, so we can repeat when spacebar pressed 56 | self.lastMove = None 57 | self.lastCorner = None 58 | 59 | self.bgcolor = wx.Colour() 60 | self.bgcolor.SetFromName(bgcolor) 61 | self.bgcolormask = wx.Colour(self.bgcolor.Red(), self.bgcolor.Green(), self.bgcolor.Blue(), 128) 62 | 63 | BufferedCanvas.__init__(self, parent, ID) 64 | self.SetSize(self.bg_bmp.GetSize()) 65 | 66 | # Set up mouse and keyboard event capture 67 | self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown) 68 | self.Bind(wx.EVT_LEFT_DCLICK, self.OnLeftDown) 69 | self.Bind(wx.EVT_MOTION, self.OnMotion) 70 | self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeaveWindow) 71 | self.Bind(wx.EVT_KEY_UP, self.OnKey) 72 | wx.GetTopLevelParent(self).Bind(wx.EVT_CHAR_HOOK, self.OnTopLevelKey) 73 | 74 | def disable(self): 75 | self.enabled = False 76 | self.update() 77 | 78 | def enable(self): 79 | self.enabled = True 80 | self.update() 81 | 82 | def repeatLast(self): 83 | if self.lastMove: 84 | self.moveCallback(*self.lastMove) 85 | if self.lastCorner: 86 | self.cornerCallback(self.lastCorner) 87 | 88 | def clearRepeat(self): 89 | self.lastMove = None 90 | self.lastCorner = None 91 | 92 | def distanceToLine(self, pos, x1, y1, x2, y2): 93 | xlen = x2 - x1 94 | ylen = y2 - y1 95 | pxlen = x1 - pos.x 96 | pylen = y1 - pos.y 97 | return abs(xlen*pylen-ylen*pxlen)/math.sqrt(xlen**2+ylen**2) 98 | 99 | def distanceToPoint(self, x1, y1, x2, y2): 100 | return math.sqrt((x1-x2)**2 + (y1-y2)**2) 101 | 102 | def cycleKeypadIndex(self): 103 | idx = self.keypad_idx + 1 104 | if idx > 2: idx = 0 105 | return idx 106 | 107 | def setKeypadIndex(self, idx): 108 | self.keypad_idx = idx 109 | self.update() 110 | 111 | def getMovement(self): 112 | xdir = [1, 0, -1, 0][self.quadrant] 113 | ydir = [0, 1, 0, -1][self.quadrant] 114 | magnitude = math.pow(10, self.concentric-1) 115 | return (magnitude * xdir, magnitude * ydir) 116 | 117 | def lookupConcentric(self, radius): 118 | idx = 0 119 | for r in XYButtons.concentric_circle_radii[1:]: 120 | if radius < r: 121 | return idx 122 | idx += 1 123 | return len(XYButtons.concentric_circle_radii) 124 | 125 | def getQuadrantConcentricFromPosition(self, pos): 126 | rel_x = pos[0] - XYButtons.center[0] 127 | rel_y = pos[1] - XYButtons.center[1] 128 | radius = math.sqrt(rel_x**2 + rel_y**2) 129 | if rel_x > rel_y and rel_x > -rel_y: 130 | quadrant = 0 # Right 131 | elif rel_x <= rel_y and rel_x > -rel_y: 132 | quadrant = 3 # Down 133 | elif rel_x > rel_y and rel_x < -rel_y: 134 | quadrant = 1 # Up 135 | else: 136 | quadrant = 2 # Left 137 | 138 | idx = self.lookupConcentric(radius) 139 | return (quadrant, idx) 140 | 141 | def mouseOverKeypad(self, mpos): 142 | for idx, kpos in XYButtons.keypad_positions.items(): 143 | radius = self.distanceToPoint(mpos[0], mpos[1], kpos[0], kpos[1]) 144 | if radius < 9: 145 | return idx 146 | return None 147 | 148 | def drawPartialPie(self, gc, center, r1, r2, angle1, angle2): 149 | p1 = wx.Point(center.x + r1*math.cos(angle1), center.y + r1*math.sin(angle1)) 150 | 151 | path = gc.CreatePath() 152 | path.MoveToPoint(p1.x, p1.y) 153 | path.AddArc(center.x, center.y, r1, angle1, angle2, True) 154 | path.AddArc(center.x, center.y, r2, angle2, angle1, False) 155 | path.AddLineToPoint(p1.x, p1.y) 156 | gc.DrawPath(path) 157 | 158 | def highlightQuadrant(self, gc, quadrant, concentric): 159 | assert(quadrant >= 0 and quadrant <= 3) 160 | assert(concentric >= 0 and concentric <= 3) 161 | 162 | inner_ring_radius = XYButtons.concentric_circle_radii[0] 163 | # fudge = math.pi*0.002 164 | fudge = -0.02 165 | center = wx.Point(XYButtons.center[0], XYButtons.center[1]) 166 | if quadrant == 0: 167 | a1, a2 = (-math.pi*0.25, math.pi*0.25) 168 | center.x += inner_ring_radius 169 | elif quadrant == 1: 170 | a1, a2 = (math.pi*1.25, math.pi*1.75) 171 | center.y -= inner_ring_radius 172 | elif quadrant == 2: 173 | a1, a2 = (math.pi*0.75, math.pi*1.25) 174 | center.x -= inner_ring_radius 175 | elif quadrant == 3: 176 | a1, a2 = (math.pi*0.25, math.pi*0.75) 177 | center.y += inner_ring_radius 178 | 179 | r1 = XYButtons.concentric_circle_radii[concentric] 180 | r2 = XYButtons.concentric_circle_radii[concentric+1] 181 | 182 | self.drawPartialPie(gc, center, r1-inner_ring_radius, r2-inner_ring_radius, a1+fudge, a2-fudge) 183 | 184 | def drawCorner(self, gc, x, y, angle = 0.0): 185 | w, h = XYButtons.corner_size 186 | 187 | gc.PushState() 188 | gc.Translate(x, y) 189 | gc.Rotate(angle) 190 | path = gc.CreatePath() 191 | path.MoveToPoint(-w/2, -h/2) 192 | path.AddLineToPoint(w/2, -h/2) 193 | path.AddLineToPoint(w/2, -h/2+h/3) 194 | path.AddLineToPoint(-w/2+w/3, h/2) 195 | path.AddLineToPoint(-w/2, h/2) 196 | path.AddLineToPoint(-w/2, -h/2) 197 | gc.DrawPath(path) 198 | gc.PopState() 199 | 200 | def highlightCorner(self, gc, corner = 0): 201 | w, h = XYButtons.corner_size 202 | cx, cy = XYButtons.center 203 | ww, wh = self.GetSizeTuple() 204 | 205 | inset = 10 206 | if corner == 0: 207 | x, y = (cx - ww/2 + inset, cy - wh/2 + inset) 208 | self.drawCorner(gc, x+w/2, y+h/2, 0) 209 | elif corner == 1: 210 | x, y = (cx + ww/2 - inset, cy - wh/2 + inset) 211 | self.drawCorner(gc, x-w/2, y+h/2, math.pi/2) 212 | elif corner == 2: 213 | x, y = (cx + ww/2 - inset, cy + wh/2 - inset) 214 | self.drawCorner(gc, x-w/2, y-h/2, math.pi) 215 | elif corner == 3: 216 | x, y = (cx - ww/2 + inset, cy + wh/2 - inset) 217 | self.drawCorner(gc, x+w/2, y-h/2, math.pi*3/2) 218 | 219 | 220 | def draw(self, dc, w, h): 221 | dc.SetBackground(wx.Brush(self.bgcolor)) 222 | dc.Clear() 223 | gc = wx.GraphicsContext.Create(dc) 224 | 225 | center = wx.Point(XYButtons.center[0], XYButtons.center[1]) 226 | if self.bg_bmp: 227 | w, h = (self.bg_bmp.GetWidth(), self.bg_bmp.GetHeight()) 228 | gc.DrawBitmap(self.bg_bmp, 0, 0, w, h) 229 | 230 | if self.enabled: 231 | # Brush and pen for grey overlay when mouse hovers over 232 | gc.SetPen(wx.Pen(wx.Colour(100, 100, 100, 172), 4)) 233 | gc.SetBrush(wx.Brush(wx.Colour(0, 0, 0, 128))) 234 | 235 | if self.concentric != None: 236 | if self.concentric < len(XYButtons.concentric_circle_radii): 237 | if self.quadrant != None: 238 | self.highlightQuadrant(gc, self.quadrant, self.concentric) 239 | elif self.corner != None: 240 | self.highlightCorner(gc, self.corner) 241 | 242 | if self.keypad_idx >= 0: 243 | padw, padh = (self.keypad_bmp.GetWidth(), self.keypad_bmp.GetHeight()) 244 | pos = XYButtons.keypad_positions[self.keypad_idx] 245 | pos = (pos[0] - padw/2 - 3, pos[1] - padh/2 - 3) 246 | gc.DrawBitmap(self.keypad_bmp, pos[0], pos[1], padw, padh) 247 | 248 | # Draw label overlays 249 | gc.SetPen(wx.Pen(wx.Colour(255, 255, 255, 128), 1)) 250 | gc.SetBrush(wx.Brush(wx.Colour(255, 255, 255, 128+64))) 251 | for idx, kpos in XYButtons.label_overlay_positions.items(): 252 | if idx != self.concentric: 253 | r = kpos[2] 254 | gc.DrawEllipse(kpos[0]-r, kpos[1]-r, r*2, r*2) 255 | else: 256 | gc.SetPen(wx.Pen(self.bgcolor, 0)) 257 | gc.SetBrush(wx.Brush(self.bgcolormask)) 258 | gc.DrawRectangle(0, 0, w, h) 259 | 260 | 261 | # Used to check exact position of keypad dots, should we ever resize the bg image 262 | # for idx, kpos in XYButtons.label_overlay_positions.items(): 263 | # dc.DrawCircle(kpos[0], kpos[1], kpos[2]) 264 | 265 | ## ------ ## 266 | ## Events ## 267 | ## ------ ## 268 | 269 | def OnTopLevelKey(self, evt): 270 | # Let user press escape on any control, and return focus here 271 | if evt.GetKeyCode() == wx.WXK_ESCAPE: 272 | self.SetFocus() 273 | evt.Skip() 274 | 275 | def OnKey(self, evt): 276 | if not self.enabled: 277 | return 278 | if self.keypad_idx >= 0: 279 | if evt.GetKeyCode() == wx.WXK_TAB: 280 | self.setKeypadIndex(self.cycleKeypadIndex()) 281 | elif evt.GetKeyCode() == wx.WXK_UP: 282 | self.quadrant = 1 283 | elif evt.GetKeyCode() == wx.WXK_DOWN: 284 | self.quadrant = 3 285 | elif evt.GetKeyCode() == wx.WXK_LEFT: 286 | self.quadrant = 2 287 | elif evt.GetKeyCode() == wx.WXK_RIGHT: 288 | self.quadrant = 0 289 | else: 290 | evt.Skip() 291 | return 292 | 293 | if self.moveCallback: 294 | self.concentric = self.keypad_idx 295 | x, y = self.getMovement() 296 | self.moveCallback(x, y) 297 | elif evt.GetKeyCode() == wx.WXK_SPACE: 298 | self.spacebarCallback() 299 | 300 | 301 | def OnMotion(self, event): 302 | if not self.enabled: 303 | return 304 | 305 | oldcorner = self.corner 306 | oldq, oldc = self.quadrant, self.concentric 307 | 308 | mpos = event.GetPosition() 309 | idx = self.mouseOverKeypad(mpos) 310 | self.quadrant = None 311 | self.concentric = None 312 | if idx == None: 313 | center = wx.Point(XYButtons.center[0], XYButtons.center[1]) 314 | riseDist = self.distanceToLine(mpos, center.x-1, center.y-1, center.x+1, center.y+1) 315 | fallDist = self.distanceToLine(mpos, center.x-1, center.y+1, center.x+1, center.y-1) 316 | self.quadrant, self.concentric = self.getQuadrantConcentricFromPosition(mpos) 317 | 318 | # If mouse hovers in space between quadrants, don't commit to a quadrant 319 | if riseDist <= XYButtons.spacer or fallDist <= XYButtons.spacer: 320 | self.quadrant = None 321 | 322 | cx, cy = XYButtons.center 323 | if mpos.x < cx and mpos.y < cy: 324 | self.corner = 0 325 | if mpos.x >= cx and mpos.y < cy: 326 | self.corner = 1 327 | if mpos.x >= cx and mpos.y >= cy: 328 | self.corner = 2 329 | if mpos.x < cx and mpos.y >= cy: 330 | self.corner = 3 331 | 332 | if oldq != self.quadrant or oldc != self.concentric or oldcorner != self.corner: 333 | self.update() 334 | 335 | def OnLeftDown(self, event): 336 | if not self.enabled: 337 | return 338 | 339 | # Take focus when clicked so that arrow keys can control movement 340 | self.SetFocus() 341 | 342 | mpos = event.GetPosition() 343 | 344 | idx = self.mouseOverKeypad(mpos) 345 | if idx == None: 346 | self.quadrant, self.concentric = self.getQuadrantConcentricFromPosition(mpos) 347 | if self.concentric != None: 348 | if self.concentric < len(XYButtons.concentric_circle_radii): 349 | if self.quadrant != None: 350 | x, y = self.getMovement() 351 | if self.moveCallback: 352 | self.lastMove = (x, y) 353 | self.lastCorner = None 354 | self.moveCallback(x, y) 355 | elif self.corner != None: 356 | if self.cornerCallback: 357 | self.lastCorner = self.corner 358 | self.lastMove = None 359 | self.cornerCallback(self.corner) 360 | else: 361 | if self.keypad_idx == idx: 362 | self.setKeypadIndex(-1) 363 | else: 364 | self.setKeypadIndex(idx) 365 | 366 | def OnLeaveWindow(self, evt): 367 | self.quadrant = None 368 | self.concentric = None 369 | self.update() 370 | -------------------------------------------------------------------------------- /printrun/gui.py: -------------------------------------------------------------------------------- 1 | # This file is part of the Printrun suite. 2 | # 3 | # Printrun is free software: you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation, either version 3 of the License, or 6 | # (at your option) any later version. 7 | # 8 | # Printrun is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with Printrun. If not, see . 15 | 16 | try: 17 | import wx 18 | except: 19 | print _("WX is not installed. This program requires WX to run.") 20 | raise 21 | 22 | global buttonSize 23 | buttonSize = (70, 25) # Define sizes for the buttons on top rows 24 | 25 | from printrun import gviz 26 | from printrun.xybuttons import XYButtons 27 | from printrun.zbuttons import ZButtons 28 | from printrun.graph import Graph 29 | 30 | def make_button(parent, label, callback, tooltip, container = None, size = wx.DefaultSize, style = 0): 31 | button = wx.Button(parent, -1, label, style = style, size = size) 32 | button.Bind(wx.EVT_BUTTON, callback) 33 | button.SetToolTip(wx.ToolTip(tooltip)) 34 | if container: 35 | container.Add(button) 36 | return button 37 | 38 | def make_sized_button(*args): 39 | return make_button(*args, size = buttonSize) 40 | 41 | def make_autosize_button(*args): 42 | return make_button(*args, size = (-1, buttonSize[1]), style = wx.BU_EXACTFIT) 43 | 44 | class XYZControlsSizer(wx.GridBagSizer): 45 | 46 | def __init__(self, root): 47 | super(XYZControlsSizer, self).__init__() 48 | root.xyb = XYButtons(root.panel, root.moveXY, root.homeButtonClicked, root.spacebarAction, root.settings.bgcolor) 49 | self.Add(root.xyb, pos = (0, 1), flag = wx.ALIGN_CENTER) 50 | root.zb = ZButtons(root.panel, root.moveZ, root.settings.bgcolor) 51 | self.Add(root.zb, pos = (0, 2), flag = wx.ALIGN_CENTER) 52 | wx.CallAfter(root.xyb.SetFocus) 53 | 54 | class LeftPane(wx.GridBagSizer): 55 | 56 | def __init__(self, root): 57 | super(LeftPane, self).__init__() 58 | llts = wx.BoxSizer(wx.HORIZONTAL) 59 | self.Add(llts, pos = (0, 0), span = (1, 9)) 60 | self.xyzsizer = XYZControlsSizer(root) 61 | self.Add(self.xyzsizer, pos = (1, 0), span = (1, 8), flag = wx.ALIGN_CENTER) 62 | 63 | for i in root.cpbuttons: 64 | btn = make_button(root.panel, i.label, root.procbutton, i.tooltip, style = wx.BU_EXACTFIT) 65 | btn.SetBackgroundColour(i.background) 66 | btn.SetForegroundColour("black") 67 | btn.properties = i 68 | root.btndict[i.command] = btn 69 | root.printerControls.append(btn) 70 | if i.pos == None: 71 | if i.span == 0: 72 | llts.Add(btn) 73 | else: 74 | self.Add(btn, pos = i.pos, span = i.span) 75 | 76 | root.xyfeedc = wx.SpinCtrl(root.panel,-1, str(root.settings.xy_feedrate), min = 0, max = 50000, size = (70,-1)) 77 | root.xyfeedc.SetToolTip(wx.ToolTip("Set Maximum Speed for X & Y axes (mm/min)")) 78 | llts.Add(wx.StaticText(root.panel,-1, _("XY:")), flag = wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL) 79 | llts.Add(root.xyfeedc) 80 | llts.Add(wx.StaticText(root.panel,-1, _("mm/min Z:")), flag = wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL) 81 | root.zfeedc = wx.SpinCtrl(root.panel,-1, str(root.settings.z_feedrate), min = 0, max = 50000, size = (70,-1)) 82 | root.zfeedc.SetToolTip(wx.ToolTip("Set Maximum Speed for Z axis (mm/min)")) 83 | llts.Add(root.zfeedc,) 84 | 85 | root.monitorbox = wx.CheckBox(root.panel,-1, _("Watch")) 86 | root.monitorbox.SetToolTip(wx.ToolTip("Monitor Temperatures in Graph")) 87 | self.Add(root.monitorbox, pos = (2, 6)) 88 | root.monitorbox.Bind(wx.EVT_CHECKBOX, root.setmonitor) 89 | 90 | self.Add(wx.StaticText(root.panel,-1, _("Heat:")), pos = (2, 0), span = (1, 1), flag = wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) 91 | htemp_choices = [root.temps[i]+" ("+i+")" for i in sorted(root.temps.keys(), key = lambda x:root.temps[x])] 92 | 93 | root.settoff = make_button(root.panel, _("Off"), lambda e: root.do_settemp("off"), _("Switch Hotend Off"), size = (36,-1), style = wx.BU_EXACTFIT) 94 | root.printerControls.append(root.settoff) 95 | self.Add(root.settoff, pos = (2, 1), span = (1, 1)) 96 | 97 | if root.settings.last_temperature not in map(float, root.temps.values()): 98 | htemp_choices = [str(root.settings.last_temperature)] + htemp_choices 99 | root.htemp = wx.ComboBox(root.panel, -1, 100 | choices = htemp_choices, style = wx.CB_DROPDOWN, size = (70,-1)) 101 | root.htemp.SetToolTip(wx.ToolTip("Select Temperature for Hotend")) 102 | root.htemp.Bind(wx.EVT_COMBOBOX, root.htemp_change) 103 | 104 | self.Add(root.htemp, pos = (2, 2), span = (1, 2)) 105 | root.settbtn = make_button(root.panel, _("Set"), root.do_settemp, _("Switch Hotend On"), size = (38, -1), style = wx.BU_EXACTFIT) 106 | root.printerControls.append(root.settbtn) 107 | self.Add(root.settbtn, pos = (2, 4), span = (1, 1)) 108 | 109 | self.Add(wx.StaticText(root.panel,-1, _("Bed:")), pos = (3, 0), span = (1, 1), flag = wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) 110 | btemp_choices = [root.bedtemps[i]+" ("+i+")" for i in sorted(root.bedtemps.keys(), key = lambda x:root.temps[x])] 111 | 112 | root.setboff = make_button(root.panel, _("Off"), lambda e:root.do_bedtemp("off"), _("Switch Heated Bed Off"), size = (36,-1), style = wx.BU_EXACTFIT) 113 | root.printerControls.append(root.setboff) 114 | self.Add(root.setboff, pos = (3, 1), span = (1, 1)) 115 | 116 | if root.settings.last_bed_temperature not in map(float, root.bedtemps.values()): 117 | btemp_choices = [str(root.settings.last_bed_temperature)] + btemp_choices 118 | root.btemp = wx.ComboBox(root.panel, -1, 119 | choices = btemp_choices, style = wx.CB_DROPDOWN, size = (70,-1)) 120 | root.btemp.SetToolTip(wx.ToolTip("Select Temperature for Heated Bed")) 121 | root.btemp.Bind(wx.EVT_COMBOBOX, root.btemp_change) 122 | self.Add(root.btemp, pos = (3, 2), span = (1, 2)) 123 | 124 | root.setbbtn = make_button(root.panel, _("Set"), root.do_bedtemp, ("Switch Heated Bed On"), size = (38, -1), style = wx.BU_EXACTFIT) 125 | root.printerControls.append(root.setbbtn) 126 | self.Add(root.setbbtn, pos = (3, 4), span = (1, 1)) 127 | 128 | root.btemp.SetValue(str(root.settings.last_bed_temperature)) 129 | root.htemp.SetValue(str(root.settings.last_temperature)) 130 | 131 | ## added for an error where only the bed would get (pla) or (abs). 132 | #This ensures, if last temp is a default pla or abs, it will be marked so. 133 | # if it is not, then a (user) remark is added. This denotes a manual entry 134 | 135 | for i in btemp_choices: 136 | if i.split()[0] == str(root.settings.last_bed_temperature).split('.')[0] or i.split()[0] == str(root.settings.last_bed_temperature): 137 | root.btemp.SetValue(i) 138 | for i in htemp_choices: 139 | if i.split()[0] == str(root.settings.last_temperature).split('.')[0] or i.split()[0] == str(root.settings.last_temperature) : 140 | root.htemp.SetValue(i) 141 | 142 | if( '(' not in root.btemp.Value): 143 | root.btemp.SetValue(root.btemp.Value + ' (user)') 144 | if( '(' not in root.htemp.Value): 145 | root.htemp.SetValue(root.htemp.Value + ' (user)') 146 | 147 | root.tempdisp = wx.StaticText(root.panel,-1, "") 148 | 149 | root.edist = wx.SpinCtrl(root.panel,-1, "5", min = 0, max = 1000, size = (60,-1)) 150 | root.edist.SetBackgroundColour((225, 200, 200)) 151 | root.edist.SetForegroundColour("black") 152 | self.Add(root.edist, pos = (4, 2), span = (1, 2)) 153 | self.Add(wx.StaticText(root.panel,-1, _("mm")), pos = (4, 4), span = (1, 1)) 154 | root.edist.SetToolTip(wx.ToolTip("Amount to Extrude or Retract (mm)")) 155 | root.efeedc = wx.SpinCtrl(root.panel,-1, str(root.settings.e_feedrate), min = 0, max = 50000, size = (60,-1)) 156 | root.efeedc.SetToolTip(wx.ToolTip("Extrude / Retract speed (mm/min)")) 157 | root.efeedc.SetBackgroundColour((225, 200, 200)) 158 | root.efeedc.SetForegroundColour("black") 159 | root.efeedc.Bind(wx.EVT_SPINCTRL, root.setfeeds) 160 | self.Add(root.efeedc, pos = (5, 2), span = (1, 2)) 161 | self.Add(wx.StaticText(root.panel,-1, _("mm/\nmin")), pos = (5, 4), span = (1, 1)) 162 | root.xyfeedc.Bind(wx.EVT_SPINCTRL, root.setfeeds) 163 | root.zfeedc.Bind(wx.EVT_SPINCTRL, root.setfeeds) 164 | root.zfeedc.SetBackgroundColour((180, 255, 180)) 165 | root.zfeedc.SetForegroundColour("black") 166 | 167 | root.graph = Graph(root.panel, wx.ID_ANY) 168 | self.Add(root.graph, pos = (3, 5), span = (3, 3)) 169 | self.Add(root.tempdisp, pos = (6, 0), span = (1, 9)) 170 | 171 | class VizPane(wx.BoxSizer): 172 | 173 | def __init__(self, root): 174 | super(VizPane, self).__init__(wx.VERTICAL) 175 | root.gviz = gviz.gviz(root.panel, (300, 300), 176 | build_dimensions = root.build_dimensions_list, 177 | grid = (root.settings.preview_grid_step1, root.settings.preview_grid_step2), 178 | extrusion_width = root.settings.preview_extrusion_width) 179 | root.gviz.SetToolTip(wx.ToolTip("Click to examine / edit\n layers of loaded file")) 180 | root.gviz.showall = 1 181 | try: 182 | raise "" 183 | import printrun.stlview 184 | root.gwindow = printrun.stlview.GCFrame(None, wx.ID_ANY, 'Gcode view, shift to move view, mousewheel to set layer', size = (600, 600)) 185 | except: 186 | root.gwindow = gviz.window([], 187 | build_dimensions = root.build_dimensions_list, 188 | grid = (root.settings.preview_grid_step1, root.settings.preview_grid_step2), 189 | extrusion_width = root.settings.preview_extrusion_width) 190 | root.gviz.Bind(wx.EVT_LEFT_DOWN, root.showwin) 191 | root.gwindow.Bind(wx.EVT_CLOSE, lambda x:root.gwindow.Hide()) 192 | self.Add(root.gviz, 1, flag = wx.SHAPED) 193 | cs = root.centersizer = wx.GridBagSizer() 194 | self.Add(cs, 0, flag = wx.EXPAND) 195 | 196 | class LogPane(wx.BoxSizer): 197 | 198 | def __init__(self, root): 199 | super(LogPane, self).__init__(wx.VERTICAL) 200 | root.lowerrsizer = self 201 | root.logbox = wx.TextCtrl(root.panel, style = wx.TE_MULTILINE, size = (350,-1)) 202 | root.logbox.SetEditable(0) 203 | self.Add(root.logbox, 1, wx.EXPAND) 204 | lbrs = wx.BoxSizer(wx.HORIZONTAL) 205 | root.commandbox = wx.TextCtrl(root.panel, style = wx.TE_PROCESS_ENTER) 206 | root.commandbox.SetToolTip(wx.ToolTip("Send commands to printer\n(Type 'help' for simple\nhelp function)")) 207 | root.commandbox.Bind(wx.EVT_TEXT_ENTER, root.sendline) 208 | root.commandbox.Bind(wx.EVT_CHAR, root.cbkey) 209 | root.commandbox.history = [u""] 210 | root.commandbox.histindex = 1 211 | #root.printerControls.append(root.commandbox) 212 | lbrs.Add(root.commandbox, 1) 213 | root.sendbtn = make_button(root.panel, _("Send"), root.sendline, _("Send Command to Printer"), style = wx.BU_EXACTFIT, container = lbrs) 214 | #root.printerControls.append(root.sendbtn) 215 | self.Add(lbrs, 0, wx.EXPAND) 216 | 217 | class MainToolbar(wx.BoxSizer): 218 | 219 | def __init__(self, root): 220 | super(MainToolbar, self).__init__(wx.HORIZONTAL) 221 | root.rescanbtn = make_sized_button(root.panel, _("Port"), root.rescanports, _("Communication Settings\nClick to rescan ports")) 222 | self.Add(root.rescanbtn, 0, wx.TOP|wx.LEFT, 0) 223 | 224 | root.serialport = wx.ComboBox(root.panel, -1, 225 | choices = root.scanserial(), 226 | style = wx.CB_DROPDOWN, size = (100, 25)) 227 | root.serialport.SetToolTip(wx.ToolTip("Select Port Printer is connected to")) 228 | root.rescanports() 229 | self.Add(root.serialport) 230 | 231 | self.Add(wx.StaticText(root.panel,-1, "@"), 0, wx.RIGHT|wx.ALIGN_CENTER, 0) 232 | root.baud = wx.ComboBox(root.panel, -1, 233 | choices = ["2400", "9600", "19200", "38400", "57600", "115200", "250000"], 234 | style = wx.CB_DROPDOWN, size = (100, 25)) 235 | root.baud.SetToolTip(wx.ToolTip("Select Baud rate for printer communication")) 236 | try: 237 | root.baud.SetValue("115200") 238 | root.baud.SetValue(str(root.settings.baudrate)) 239 | except: 240 | pass 241 | self.Add(root.baud) 242 | root.connectbtn = make_sized_button(root.panel, _("Connect"), root.connect, _("Connect to the printer"), self) 243 | 244 | root.resetbtn = make_autosize_button(root.panel, _("Reset"), root.reset, _("Reset the printer"), self) 245 | root.loadbtn = make_autosize_button(root.panel, _("Load file"), root.loadfile, _("Load a 3D model file"), self) 246 | root.platebtn = make_autosize_button(root.panel, _("Compose"), root.plate, _("Simple Plater System"), self) 247 | root.sdbtn = make_autosize_button(root.panel, _("SD"), root.sdmenu, _("SD Card Printing"), self) 248 | root.printerControls.append(root.sdbtn) 249 | root.printbtn = make_sized_button(root.panel, _("Print"), root.printfile, _("Start Printing Loaded File"), self) 250 | root.printbtn.Disable() 251 | root.pausebtn = make_sized_button(root.panel, _("Pause"), root.pause, _("Pause Current Print"), self) 252 | root.recoverbtn = make_sized_button(root.panel, _("Recover"), root.recover, _("Recover previous Print"), self) 253 | 254 | class MainWindow(wx.Frame): 255 | 256 | def __init__(self, *args, **kwargs): 257 | super(MainWindow, self).__init__(*args, **kwargs) 258 | # this list will contain all controls that should be only enabled 259 | # when we're connected to a printer 260 | self.printerControls = [] 261 | 262 | def createGui(self): 263 | self.mainsizer = wx.BoxSizer(wx.VERTICAL) 264 | self.uppersizer = MainToolbar(self) 265 | self.lowersizer = wx.BoxSizer(wx.HORIZONTAL) 266 | self.lowersizer.Add(LeftPane(self)) 267 | self.lowersizer.Add(VizPane(self), 1, wx.EXPAND|wx.ALIGN_CENTER_HORIZONTAL) 268 | self.lowersizer.Add(LogPane(self), 0, wx.EXPAND) 269 | self.mainsizer.Add(self.uppersizer) 270 | self.mainsizer.Add(self.lowersizer, 1, wx.EXPAND) 271 | self.panel.SetSizer(self.mainsizer) 272 | self.status = self.CreateStatusBar() 273 | self.status.SetStatusText(_("Not connected to printer.")) 274 | self.panel.Bind(wx.EVT_MOUSE_EVENTS, self.editbutton) 275 | self.Bind(wx.EVT_CLOSE, self.kill) 276 | 277 | self.mainsizer.Layout() 278 | self.mainsizer.Fit(self) 279 | 280 | # disable all printer controls until we connect to a printer 281 | self.pausebtn.Disable() 282 | self.recoverbtn.Disable() 283 | for i in self.printerControls: 284 | i.Disable() 285 | 286 | #self.panel.Fit() 287 | self.cbuttons_reload() 288 | -------------------------------------------------------------------------------- /printcore.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # This file is part of the Printrun suite. 4 | # 5 | # Printrun is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Printrun is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Printrun. If not, see . 17 | 18 | from serial import Serial, SerialException 19 | from threading import Thread 20 | from select import error as SelectError 21 | import time, getopt, sys 22 | import platform, os 23 | 24 | def control_ttyhup(port, disable_hup): 25 | """Controls the HUPCL""" 26 | if platform.system() == "Linux": 27 | if disable_hup: 28 | os.system("stty -F %s -hup" % port) 29 | else: 30 | os.system("stty -F %s hup" % port) 31 | 32 | def enable_hup(port): 33 | control_ttyhup(port, False) 34 | 35 | def disable_hup(port): 36 | control_ttyhup(port, True) 37 | 38 | class printcore(): 39 | def __init__(self, port = None, baud = None): 40 | """Initializes a printcore instance. Pass the port and baud rate to connect immediately 41 | """ 42 | self.baud = None 43 | self.port = None 44 | self.printer = None #Serial instance connected to the printer, None when disconnected 45 | self.clear = 0 #clear to send, enabled after responses 46 | self.online = False #The printer has responded to the initial command and is active 47 | self.printing = False #is a print currently running, true if printing, false if paused 48 | self.mainqueue = [] 49 | self.priqueue = [] 50 | self.queueindex = 0 51 | self.lineno = 0 52 | self.resendfrom = -1 53 | self.paused = False 54 | self.sentlines = {} 55 | self.log = [] 56 | self.sent = [] 57 | self.tempcb = None #impl (wholeline) 58 | self.recvcb = None #impl (wholeline) 59 | self.sendcb = None #impl (wholeline) 60 | self.errorcb = None #impl (wholeline) 61 | self.startcb = None #impl () 62 | self.endcb = None #impl () 63 | self.onlinecb = None #impl () 64 | self.loud = False #emit sent and received lines to terminal 65 | self.greetings = ['start','Grbl '] 66 | self.wait = 0 # default wait period for send(), send_now() 67 | self.read_thread = None 68 | self.stop_read_thread = False 69 | self.print_thread = None 70 | if port is not None and baud is not None: 71 | self.connect(port, baud) 72 | 73 | def disconnect(self): 74 | """Disconnects from printer and pauses the print 75 | """ 76 | if self.printer: 77 | if self.read_thread: 78 | self.stop_read_thread = True 79 | self.read_thread.join() 80 | self.read_thread = None 81 | self.printer.close() 82 | self.printer = None 83 | self.online = False 84 | self.printing = False 85 | 86 | def connect(self, port = None, baud = None): 87 | """Set port and baudrate if given, then connect to printer 88 | """ 89 | if self.printer: 90 | self.disconnect() 91 | if port is not None: 92 | self.port = port 93 | if baud is not None: 94 | self.baud = baud 95 | if self.port is not None and self.baud is not None: 96 | disable_hup(self.port) 97 | self.printer = Serial(port = self.port, baudrate = self.baud, timeout = 0.25) 98 | self.stop_read_thread = False 99 | self.read_thread = Thread(target = self._listen) 100 | self.read_thread.start() 101 | 102 | def reset(self): 103 | """Reset the printer 104 | """ 105 | if self.printer: 106 | self.printer.setDTR(1) 107 | time.sleep(0.2) 108 | self.printer.setDTR(0) 109 | 110 | def _readline(self): 111 | try: 112 | line = self.printer.readline() 113 | if len(line) > 1: 114 | self.log.append(line) 115 | if self.recvcb: 116 | try: self.recvcb(line) 117 | except: pass 118 | if self.loud: print "RECV: ", line.rstrip() 119 | return line 120 | except SelectError, e: 121 | if 'Bad file descriptor' in e.args[1]: 122 | print "Can't read from printer (disconnected?)." 123 | return None 124 | else: 125 | raise 126 | except SerialException, e: 127 | print "Can't read from printer (disconnected?)." 128 | return None 129 | except OSError, e: 130 | print "Can't read from printer (disconnected?)." 131 | return None 132 | 133 | def _listen_can_continue(self): 134 | return not self.stop_read_thread and self.printer and self.printer.isOpen() 135 | 136 | def _listen_until_online(self): 137 | while not self.online and self._listen_can_continue(): 138 | self._send("M105") 139 | empty_lines = 0 140 | while self._listen_can_continue(): 141 | line = self._readline() 142 | if line == None: break # connection problem 143 | # workaround cases where M105 was sent before printer Serial 144 | # was online an empty line means read timeout was reached, 145 | # meaning no data was received thus we count those empty lines, 146 | # and once we have seen 5 in a row, we just break and send a 147 | # new M105 148 | if not line: empty_lines += 1 149 | else: empty_lines = 0 150 | if empty_lines == 5: break 151 | if line.startswith(tuple(self.greetings)) or line.startswith('ok'): 152 | if self.onlinecb: 153 | try: self.onlinecb() 154 | except: pass 155 | self.online = True 156 | return 157 | time.sleep(0.25) 158 | 159 | def _listen(self): 160 | """This function acts on messages from the firmware 161 | """ 162 | self.clear = True 163 | if not self.printing: 164 | self._listen_until_online() 165 | while self._listen_can_continue(): 166 | line = self._readline() 167 | if line == None: 168 | break 169 | if line.startswith('DEBUG_'): 170 | continue 171 | if line.startswith(tuple(self.greetings)) or line.startswith('ok'): 172 | self.clear = True 173 | if line.startswith('ok') and "T:" in line and self.tempcb: 174 | #callback for temp, status, whatever 175 | try: self.tempcb(line) 176 | except: pass 177 | elif line.startswith('Error'): 178 | if self.errorcb: 179 | #callback for errors 180 | try: self.errorcb(line) 181 | except: pass 182 | # Teststrings for resend parsing # Firmware exp. result 183 | # line="rs N2 Expected checksum 67" # Teacup 2 184 | if line.lower().startswith("resend") or line.startswith("rs"): 185 | line = line.replace("N:"," ").replace("N"," ").replace(":"," ") 186 | linewords = line.split() 187 | while len(linewords) != 0: 188 | try: 189 | toresend = int(linewords.pop(0)) 190 | self.resendfrom = toresend 191 | #print str(toresend) 192 | break 193 | except: 194 | pass 195 | self.clear = True 196 | self.clear = True 197 | 198 | def _checksum(self, command): 199 | return reduce(lambda x, y:x^y, map(ord, command)) 200 | 201 | def startprint(self, data, startindex = 0): 202 | """Start a print, data is an array of gcode commands. 203 | returns True on success, False if already printing. 204 | The print queue will be replaced with the contents of the data array, the next line will be set to 0 and the firmware notified. 205 | Printing will then start in a parallel thread. 206 | """ 207 | if self.printing or not self.online or not self.printer: 208 | return False 209 | self.printing = True 210 | self.mainqueue = [] + data 211 | self.lineno = 0 212 | self.queueindex = startindex 213 | self.resendfrom = -1 214 | self._send("M110", -1, True) 215 | if len(data) == 0: 216 | return True 217 | self.clear = False 218 | self.print_thread = Thread(target = self._print) 219 | self.print_thread.start() 220 | return True 221 | 222 | def pause(self): 223 | """Pauses the print, saving the current position. 224 | """ 225 | self.paused = True 226 | self.printing = False 227 | self.print_thread.join() 228 | self.print_thread = None 229 | 230 | def resume(self): 231 | """Resumes a paused print. 232 | """ 233 | self.paused = False 234 | self.printing = True 235 | self.print_thread = Thread(target = self._print) 236 | self.print_thread.start() 237 | 238 | def send(self, command, wait = 0): 239 | """Adds a command to the checksummed main command queue if printing, or sends the command immediately if not printing 240 | """ 241 | 242 | if self.online: 243 | if self.printing: 244 | self.mainqueue.append(command) 245 | else: 246 | while self.printer and self.printing and not self.clear: 247 | time.sleep(0.001) 248 | if wait == 0 and self.wait > 0: 249 | wait = self.wait 250 | if wait > 0: 251 | self.clear = False 252 | self._send(command, self.lineno, True) 253 | self.lineno += 1 254 | while wait > 0 and self.printer and self.printing and not self.clear: 255 | time.sleep(0.001) 256 | wait -= 1 257 | else: 258 | print "Not connected to printer." 259 | 260 | def send_now(self, command, wait = 0): 261 | """Sends a command to the printer ahead of the command queue, without a checksum 262 | """ 263 | if self.online or force: 264 | if self.printing: 265 | self.priqueue.append(command) 266 | else: 267 | while self.printer and self.printing and not self.clear: 268 | time.sleep(0.001) 269 | if wait == 0 and self.wait > 0: 270 | wait = self.wait 271 | if wait > 0: 272 | self.clear = False 273 | self._send(command) 274 | while (wait > 0) and self.printer and self.printing and not self.clear: 275 | time.sleep(0.001) 276 | wait -= 1 277 | else: 278 | print "Not connected to printer." 279 | 280 | def _print(self): 281 | if self.startcb: 282 | #callback for printing started 283 | try: self.startcb() 284 | except: pass 285 | while self.printing and self.printer and self.online: 286 | self._sendnext() 287 | self.sentlines = {} 288 | self.log = [] 289 | self.sent = [] 290 | if self.endcb: 291 | #callback for printing done 292 | try: self.endcb() 293 | except: pass 294 | 295 | def _sendnext(self): 296 | if not self.printer: 297 | return 298 | while self.printer and self.printing and not self.clear: 299 | time.sleep(0.001) 300 | self.clear = False 301 | if not (self.printing and self.printer and self.online): 302 | self.clear = True 303 | return 304 | if self.resendfrom < self.lineno and self.resendfrom > -1: 305 | self._send(self.sentlines[self.resendfrom], self.resendfrom, False) 306 | self.resendfrom += 1 307 | return 308 | self.resendfrom = -1 309 | for i in self.priqueue[:]: 310 | self._send(i) 311 | del self.priqueue[0] 312 | return 313 | if self.printing and self.queueindex < len(self.mainqueue): 314 | tline = self.mainqueue[self.queueindex] 315 | tline = tline.split(";")[0] 316 | if len(tline) > 0: 317 | self._send(tline, self.lineno, True) 318 | self.lineno += 1 319 | else: 320 | self.clear = True 321 | self.queueindex += 1 322 | else: 323 | self.printing = False 324 | self.clear = True 325 | if not self.paused: 326 | self.queueindex = 0 327 | self.lineno = 0 328 | self._send("M110", -1, True) 329 | 330 | def _send(self, command, lineno = 0, calcchecksum = False): 331 | if calcchecksum: 332 | prefix = "N" + str(lineno) + " " + command 333 | command = prefix + "*" + str(self._checksum(prefix)) 334 | if "M110" not in command: 335 | self.sentlines[lineno] = command 336 | if self.printer: 337 | self.sent.append(command) 338 | if self.loud: 339 | print "SENT: ", command 340 | if self.sendcb: 341 | try: self.sendcb(command) 342 | except: pass 343 | try: 344 | self.printer.write(str(command+"\n")) 345 | except SerialException, e: 346 | print "Can't write to printer (disconnected?)." 347 | 348 | if __name__ == '__main__': 349 | baud = 115200 350 | loud = False 351 | statusreport = False 352 | try: 353 | opts, args = getopt.getopt(sys.argv[1:], "h,b:,v,s", 354 | ["help", "baud", "verbose", "statusreport"]) 355 | except getopt.GetoptError, err: 356 | print str(err) 357 | sys.exit(2) 358 | for o, a in opts: 359 | if o in ('-h', '--help'): 360 | # FIXME: Fix help 361 | print "Opts are: --help , -b --baud = baudrate, -v --verbose, -s --statusreport" 362 | sys.exit(1) 363 | if o in ('-b', '--baud'): 364 | baud = int(a) 365 | if o in ('-v','--verbose'): 366 | loud = True 367 | elif o in ('-s','--statusreport'): 368 | statusreport = True 369 | 370 | if len (args) > 1: 371 | port = args[-2] 372 | filename = args[-1] 373 | print "Printing: %s on %s with baudrate %d" % (filename, port, baud) 374 | else: 375 | print "Usage: python [-h|-b|-v|-s] printcore.py /dev/tty[USB|ACM]x filename.gcode" 376 | sys.exit(2) 377 | p = printcore(port, baud) 378 | p.loud = loud 379 | time.sleep(2) 380 | gcode = [i.replace("\n", "") for i in open(filename)] 381 | p.startprint(gcode) 382 | 383 | try: 384 | if statusreport: 385 | p.loud = False 386 | sys.stdout.write("Progress: 00.0%") 387 | sys.stdout.flush() 388 | while p.printing: 389 | time.sleep(1) 390 | if statusreport: 391 | sys.stdout.write("\b\b\b\b%02.1f%%" % (100 * float(p.queueindex) / len(p.mainqueue),) ) 392 | sys.stdout.flush() 393 | p.disconnect() 394 | sys.exit(0) 395 | except: 396 | p.disconnect() 397 | -------------------------------------------------------------------------------- /locale/fr/LC_MESSAGES/pronterface.po: -------------------------------------------------------------------------------- 1 | # Pronterface Message Catalog Template 2 | # Copyright (C) 2011 Jonathan Marsden 3 | # Jonathan Marsden , 2011. 4 | # 5 | msgid "" 6 | msgstr "" 7 | "Project-Id-Version: Pronterface jm1\n" 8 | "POT-Creation-Date: 2012-08-08 10:09+CEST\n" 9 | "PO-Revision-Date: 2012-03-16 03:50+0100\n" 10 | "Last-Translator: Guillaume Seguin \n" 11 | "Language-Team: FR \n" 12 | "Language: \n" 13 | "MIME-Version: 1.0\n" 14 | "Content-Type: text/plain; charset=UTF-8\n" 15 | "Content-Transfer-Encoding: 8bit\n" 16 | "Generated-By: pygettext.py 1.5\n" 17 | 18 | #: printrun/pronterface_widgets.py:34 19 | msgid "Find" 20 | msgstr "Trouver" 21 | 22 | #: printrun/pronterface_widgets.py:36 23 | msgid "Save" 24 | msgstr "Enregistrer" 25 | 26 | #: printrun/pronterface_widgets.py:41 pronterface.py:477 pronterface.py:1535 27 | msgid "Cancel" 28 | msgstr "Annuler" 29 | 30 | #: printrun/pronterface_widgets.py:125 31 | msgid "Edit settings" 32 | msgstr "Modifier les paramètres" 33 | 34 | #: printrun/pronterface_widgets.py:127 35 | msgid "Defaults" 36 | msgstr "Paramètres par défaut" 37 | 38 | #: printrun/pronterface_widgets.py:156 39 | msgid "Custom button" 40 | msgstr "Commande personnalisée" 41 | 42 | #: printrun/pronterface_widgets.py:161 43 | msgid "Button title" 44 | msgstr "Titre du bouton" 45 | 46 | #: printrun/pronterface_widgets.py:164 47 | msgid "Command" 48 | msgstr "Commande" 49 | 50 | #: printrun/pronterface_widgets.py:173 51 | msgid "Color" 52 | msgstr "Couleur" 53 | 54 | #: pronterface.py:26 55 | msgid "WX is not installed. This program requires WX to run." 56 | msgstr "" 57 | "wxWidgets n'est pas installé. Ce programme nécessite la librairie wxWidgets " 58 | "pour fonctionner." 59 | 60 | #: pronterface.py:93 61 | msgid "" 62 | "Dimensions of Build Platform\n" 63 | " & optional offset of origin\n" 64 | "\n" 65 | "Examples:\n" 66 | " XXXxYYY\n" 67 | " XXX,YYY,ZZZ\n" 68 | " XXXxYYYxZZZ+OffX+OffY+OffZ" 69 | msgstr "" 70 | 71 | #: pronterface.py:94 72 | msgid "Last Set Temperature for the Heated Print Bed" 73 | msgstr "Dernière température du plateau chauffant définie" 74 | 75 | #: pronterface.py:95 76 | msgid "Folder of last opened file" 77 | msgstr "Dossier du dernier fichier ouvert" 78 | 79 | #: pronterface.py:96 80 | msgid "Last Temperature of the Hot End" 81 | msgstr "Dernière température de la buse définie" 82 | 83 | #: pronterface.py:97 84 | msgid "Width of Extrusion in Preview (default: 0.5)" 85 | msgstr "Largeur de l'extrusion dans la prévisualisation (défaut : 0.5)" 86 | 87 | #: pronterface.py:98 88 | msgid "Fine Grid Spacing (default: 10)" 89 | msgstr "Espacement fin de la grille (défaut : 10)" 90 | 91 | #: pronterface.py:99 92 | msgid "Coarse Grid Spacing (default: 50)" 93 | msgstr "Espacement large de la grille (défaut : 50)" 94 | 95 | #: pronterface.py:100 96 | msgid "Pronterface background color (default: #FFFFFF)" 97 | msgstr "Couleur de fond de la Pronterface (défaut : #FFFFFF)" 98 | 99 | #: pronterface.py:103 100 | msgid "Printer Interface" 101 | msgstr "Interface de l'imprimante" 102 | 103 | #: pronterface.py:122 104 | msgid "Motors off" 105 | msgstr "Arrêter les moteurs" 106 | 107 | #: pronterface.py:122 108 | msgid "Switch all motors off" 109 | msgstr "Arrêter tous les moteurs" 110 | 111 | #: pronterface.py:123 112 | msgid "Check current hotend temperature" 113 | msgstr "Vérifier la température actuelle de la buse" 114 | 115 | #: pronterface.py:123 116 | msgid "Check temp" 117 | msgstr "Lire les températures" 118 | 119 | #: pronterface.py:124 120 | msgid "Advance extruder by set length" 121 | msgstr "Extruder sur la longueur donnée" 122 | 123 | #: pronterface.py:124 124 | msgid "Extrude" 125 | msgstr "Extruder" 126 | 127 | #: pronterface.py:125 128 | msgid "Reverse" 129 | msgstr "Inverser" 130 | 131 | #: pronterface.py:125 132 | msgid "Reverse extruder by set length" 133 | msgstr "Inverser l'extrudeur sur la longueur donnée" 134 | 135 | #: pronterface.py:143 136 | msgid "" 137 | "# I moved all your custom buttons into .pronsolerc.\n" 138 | "# Please don't add them here any more.\n" 139 | "# Backup of your old buttons is in custombtn.old\n" 140 | msgstr "" 141 | "# Tous vos boutons personalisés ont été déplacés dans le fichier ." 142 | "pronsolerc.\n" 143 | "# Veuillez ne plus en ajouter ici.\n" 144 | "# Une sauvegarde de vos anciens boutons est dans le fichier custombtn.old\n" 145 | 146 | #: pronterface.py:148 147 | msgid "" 148 | "Note!!! You have specified custom buttons in both custombtn.txt and ." 149 | "pronsolerc" 150 | msgstr "" 151 | "Remarque! Vous avez spécifié des boutons personnalisés dans custombtn.txt et " 152 | "aussi dans .pronsolerc" 153 | 154 | #: pronterface.py:149 155 | msgid "" 156 | "Ignoring custombtn.txt. Remove all current buttons to revert to custombtn.txt" 157 | msgstr "" 158 | "custombtn.txt ignoré. Retirez tous les boutons en cours pour revenir à " 159 | "custombtn.txt" 160 | 161 | #: pronterface.py:181 162 | msgid "Failed to start web interface" 163 | msgstr "Échec du lancement de l'interface web" 164 | 165 | #: pronterface.py:185 166 | msgid "CherryPy is not installed. Web Interface Disabled." 167 | msgstr "CherryPy n'est pas installé. L'interface web est désactivée." 168 | 169 | #: pronterface.py:197 pronterface.py:603 pronterface.py:1525 170 | #: pronterface.py:1578 pronterface.py:1705 pronterface.py:1765 171 | #: pronterface.py:1778 172 | msgid "Print" 173 | msgstr "Imprimer" 174 | 175 | #: pronterface.py:207 176 | msgid "Printer is now online." 177 | msgstr "Imprimante connectée." 178 | 179 | #: pronterface.py:208 180 | msgid "Disconnect" 181 | msgstr "Déconnecter" 182 | 183 | #: pronterface.py:331 184 | msgid "Setting hotend temperature to %f degrees Celsius." 185 | msgstr "Réglage de la température de la buse à %f degrés Celsius." 186 | 187 | #: pronterface.py:334 pronterface.py:356 pronterface.py:428 188 | msgid "Printer is not online." 189 | msgstr "Imprimante déconnectée." 190 | 191 | #: pronterface.py:336 192 | msgid "" 193 | "You cannot set negative temperatures. To turn the hotend off entirely, set " 194 | "its temperature to 0." 195 | msgstr "" 196 | "Vous ne pouvez pas régler une température négative. Pour éteindre la buse, " 197 | "réglez sa température à 0°C." 198 | 199 | #: pronterface.py:338 pronterface.py:364 200 | msgid "You must enter a temperature. (%s)" 201 | msgstr "Vous devez saisir une température. (%s)" 202 | 203 | #: pronterface.py:353 204 | msgid "Setting bed temperature to %f degrees Celsius." 205 | msgstr "Réglage de la température du plateau à %f degrés Celsius." 206 | 207 | #: pronterface.py:360 208 | msgid "" 209 | "You cannot set negative temperatures. To turn the bed off entirely, set its " 210 | "temperature to 0." 211 | msgstr "" 212 | "Vous ne pouvez pas régler une température négative. Pour désactiver votre " 213 | "plateau chauffant, réglez sa température à 0°C." 214 | 215 | #: pronterface.py:381 216 | msgid "Do you want to erase the macro?" 217 | msgstr "Voulez-vous effacer la macro ?" 218 | 219 | #: pronterface.py:385 220 | msgid "Cancelled." 221 | msgstr "Annulé" 222 | 223 | #: pronterface.py:436 224 | msgid " Opens file" 225 | msgstr " Ouvrir un fichier" 226 | 227 | #: pronterface.py:436 228 | msgid "&Open..." 229 | msgstr "&Ouvrir..." 230 | 231 | #: pronterface.py:437 232 | msgid " Edit open file" 233 | msgstr " Éditer le fichier ouvert" 234 | 235 | #: pronterface.py:437 236 | msgid "&Edit..." 237 | msgstr "&Éditer..." 238 | 239 | #: pronterface.py:438 240 | msgid " Clear output console" 241 | msgstr " Effacer le contenu de la console de sortie" 242 | 243 | #: pronterface.py:438 244 | msgid "Clear console" 245 | msgstr "Effacer la console" 246 | 247 | #: pronterface.py:439 248 | msgid " Project slices" 249 | msgstr " Projeter les couches" 250 | 251 | #: pronterface.py:439 252 | msgid "Projector" 253 | msgstr "Projecteur" 254 | 255 | #: pronterface.py:440 256 | msgid " Closes the Window" 257 | msgstr " Quitter le programme" 258 | 259 | #: pronterface.py:440 260 | msgid "E&xit" 261 | msgstr "&Quitter" 262 | 263 | #: pronterface.py:441 264 | msgid "&File" 265 | msgstr "&Fichier" 266 | 267 | #: pronterface.py:446 268 | msgid "&Macros" 269 | msgstr "&Macros" 270 | 271 | #: pronterface.py:447 272 | msgid "<&New...>" 273 | msgstr "<&Nouvelle...>" 274 | 275 | #: pronterface.py:448 276 | msgid " Options dialog" 277 | msgstr " Fenêtre des options" 278 | 279 | #: pronterface.py:448 280 | msgid "&Options" 281 | msgstr "&Options" 282 | 283 | #: pronterface.py:450 284 | msgid " Adjust slicing settings" 285 | msgstr " Régler les paramètres de slicing" 286 | 287 | #: pronterface.py:450 288 | msgid "Slicing Settings" 289 | msgstr "Paramètres de slicing" 290 | 291 | #: pronterface.py:452 292 | msgid "&Settings" 293 | msgstr "&Paramètres" 294 | 295 | #: pronterface.py:467 296 | msgid "Enter macro name" 297 | msgstr "Saisissez le nom de la macro" 298 | 299 | #: pronterface.py:470 300 | msgid "Macro name:" 301 | msgstr "Nom :" 302 | 303 | #: pronterface.py:473 304 | msgid "Ok" 305 | msgstr "Valider" 306 | 307 | #: pronterface.py:495 308 | msgid "Macro name may contain only ASCII alphanumeric symbols and underscores" 309 | msgstr "" 310 | "Un nom de macro ne peut contenir que des caractères alphanumérique ASCII et " 311 | "des underscore (_)" 312 | 313 | #: pronterface.py:500 314 | msgid "Name '%s' is being used by built-in command" 315 | msgstr "Le nom '%s' est utilisé par une commande interne" 316 | 317 | #: pronterface.py:548 318 | msgid "Port" 319 | msgstr "Port" 320 | 321 | #: pronterface.py:570 pronterface.py:1747 322 | msgid "Connect" 323 | msgstr "Connecter" 324 | 325 | #: pronterface.py:574 326 | msgid "Reset" 327 | msgstr "Réinitialiser" 328 | 329 | #: pronterface.py:589 330 | msgid "Load file" 331 | msgstr "Charger un fichier" 332 | 333 | #: pronterface.py:593 334 | msgid "Compose" 335 | msgstr "Composer" 336 | 337 | #: pronterface.py:598 338 | msgid "SD" 339 | msgstr "SD" 340 | 341 | #: pronterface.py:608 pronterface.py:1579 pronterface.py:1631 342 | #: pronterface.py:1681 pronterface.py:1704 pronterface.py:1764 343 | #: pronterface.py:1781 344 | msgid "Pause" 345 | msgstr "Pause" 346 | 347 | #: pronterface.py:612 348 | msgid "Recover" 349 | msgstr "Récupérer" 350 | 351 | #: pronterface.py:630 352 | msgid "Send" 353 | msgstr "Envoyer" 354 | 355 | #: pronterface.py:671 356 | msgid "XY:" 357 | msgstr "XY:" 358 | 359 | #: pronterface.py:673 360 | msgid "mm/min Z:" 361 | msgstr "mm/min Z:" 362 | 363 | #: pronterface.py:678 364 | msgid "Watch" 365 | msgstr "Surveiller" 366 | 367 | #: pronterface.py:683 368 | msgid "Heat:" 369 | msgstr "Buse:" 370 | 371 | #: pronterface.py:686 pronterface.py:709 372 | msgid "Off" 373 | msgstr "Off" 374 | 375 | #: pronterface.py:700 pronterface.py:723 376 | msgid "Set" 377 | msgstr "Régler" 378 | 379 | #: pronterface.py:706 380 | msgid "Bed:" 381 | msgstr "Plateau :" 382 | 383 | #: pronterface.py:756 384 | msgid "mm" 385 | msgstr "mm" 386 | 387 | #: pronterface.py:764 388 | msgid "" 389 | "mm/\n" 390 | "min" 391 | msgstr "" 392 | "mm/\n" 393 | "min" 394 | 395 | #: pronterface.py:821 pronterface.py:1387 pronterface.py:1625 396 | #: pronterface.py:1723 397 | msgid "Not connected to printer." 398 | msgstr "Imprimante non connectée." 399 | 400 | #: pronterface.py:869 401 | msgid "SD Upload" 402 | msgstr "Copier sur SD" 403 | 404 | #: pronterface.py:873 405 | msgid "SD Print" 406 | msgstr "Imprimer depuis SD" 407 | 408 | #: pronterface.py:914 409 | msgid "Mini mode" 410 | msgstr "Mode réduit" 411 | 412 | #: pronterface.py:921 413 | msgid "Full mode" 414 | msgstr "Mode complet" 415 | 416 | #: pronterface.py:946 417 | msgid "Execute command: " 418 | msgstr "Exécuter la commande :" 419 | 420 | #: pronterface.py:957 421 | msgid "click to add new custom button" 422 | msgstr "Ajouter un bouton personnalisé" 423 | 424 | #: pronterface.py:979 425 | msgid "" 426 | "Defines custom button. Usage: button \"title\" [/c \"colour\"] command" 427 | msgstr "" 428 | "Définit des boutons personnalidés. Utilisation : \"Libelle\" [/c " 429 | "\"couleur\"] commande" 430 | 431 | #: pronterface.py:1003 432 | msgid "Custom button number should be between 0 and 63" 433 | msgstr "" 434 | "Les numéros des boutons personnalisés doivent être compris entre 0 et 63." 435 | 436 | #: pronterface.py:1096 437 | msgid "Edit custom button '%s'" 438 | msgstr "Editer le bouton personnalisé '%s'" 439 | 440 | #: pronterface.py:1098 441 | msgid "Move left <<" 442 | msgstr "Déplacer vers la gauche <<" 443 | 444 | #: pronterface.py:1101 445 | msgid "Move right >>" 446 | msgstr "Déplacer vers la droite >>" 447 | 448 | #: pronterface.py:1105 449 | msgid "Remove custom button '%s'" 450 | msgstr "Supprimer le bouton personnalisé '%s'" 451 | 452 | #: pronterface.py:1108 453 | msgid "Add custom button" 454 | msgstr "Ajouter un bouton personnalisé" 455 | 456 | #: pronterface.py:1270 457 | msgid "event object missing" 458 | msgstr "événement d'objet manquant" 459 | 460 | #: pronterface.py:1306 461 | msgid "Invalid period given." 462 | msgstr "La période donnée est invalide" 463 | 464 | #: pronterface.py:1311 465 | msgid "Monitoring printer." 466 | msgstr "Imprimante sous surveillance." 467 | 468 | #: pronterface.py:1315 469 | msgid "Done monitoring." 470 | msgstr "Surveillance de l'imprimante effectuée." 471 | 472 | #: pronterface.py:1355 473 | msgid " SD printing:%04.2f %%" 474 | msgstr " Impression SD : %04.2f %%" 475 | 476 | #: pronterface.py:1358 477 | msgid " Printing: %04.2f%% |" 478 | msgstr " Impression : %04.2f%% |" 479 | 480 | #: pronterface.py:1359 481 | msgid " Line# %d of %d lines |" 482 | msgstr " Ligne# %d sur %d lignes |" 483 | 484 | #: pronterface.py:1364 485 | msgid " Est: %s of %s remaining | " 486 | msgstr " ETA: %s restant sur %s | " 487 | 488 | #: pronterface.py:1366 489 | msgid " Z: %0.2f mm" 490 | msgstr " Z: %0.2f mm" 491 | 492 | #: pronterface.py:1439 493 | msgid "Opening file failed." 494 | msgstr "L'ouverture du fichier a échoué" 495 | 496 | #: pronterface.py:1445 497 | msgid "Starting print" 498 | msgstr "Début de l'impression..." 499 | 500 | #: pronterface.py:1466 501 | msgid "Pick SD file" 502 | msgstr "Choisir un fichier sur la carte SD" 503 | 504 | #: pronterface.py:1466 505 | msgid "Select the file to print" 506 | msgstr "Sélectionnez le fichier à imprimer :" 507 | 508 | #: pronterface.py:1501 509 | msgid "Failed to execute slicing software: " 510 | msgstr "Une erreur s'est produite lors du slicing : " 511 | 512 | #: pronterface.py:1510 513 | msgid "Slicing..." 514 | msgstr "Slicing..." 515 | 516 | #: pronterface.py:1523 517 | msgid ", %d lines" 518 | msgstr ", %d lignes" 519 | 520 | #: pronterface.py:1523 521 | msgid "Loaded " 522 | msgstr "Chargé " 523 | 524 | #: pronterface.py:1530 525 | msgid "Load File" 526 | msgstr "Charger un fichier" 527 | 528 | #: pronterface.py:1536 529 | msgid "Slicing " 530 | msgstr "Slicing " 531 | 532 | #: pronterface.py:1555 533 | msgid "Open file to print" 534 | msgstr "Ouvrir un fichier à imprimer" 535 | 536 | #: pronterface.py:1556 537 | msgid "" 538 | "OBJ, STL, and GCODE files (*.gcode;*.gco;*.g;*.stl;*.STL;*.obj;*.OBJ)|*." 539 | "gcode;*.gco;*.g;*.stl;*.STL;*.obj;*.OBJ|All Files (*.*)|*.*" 540 | msgstr "" 541 | "Fichiers OBJ, STL et GCODE (;*.gcode;*.gco;*.g;*.stl;*.STL;*.obj;*.OBJ;)|*." 542 | "gcode;*.gco;*.g;*.stl;*.STL;*.obj;*.OBJ|Tous les fichiers (*.*)|*.*" 543 | 544 | #: pronterface.py:1563 545 | msgid "File not found!" 546 | msgstr "Fichier non trouvé" 547 | 548 | #: pronterface.py:1577 549 | msgid "Loaded %s, %d lines" 550 | msgstr "%s chargé, %d lignes" 551 | 552 | #: pronterface.py:1588 553 | msgid "mm of filament used in this print\n" 554 | msgstr "mm de filament utilisés pour cette impression\n" 555 | 556 | #: pronterface.py:1589 pronterface.py:1591 557 | msgid "" 558 | "the print goes from %f mm to %f mm in X\n" 559 | "and is %f mm wide\n" 560 | msgstr "" 561 | "L'impression va de %f mm à %f mm en X\n" 562 | "et mesure %f mm de large\n" 563 | 564 | #: pronterface.py:1592 565 | msgid "" 566 | "the print goes from %f mm to %f mm in Y\n" 567 | "and is %f mm wide\n" 568 | msgstr "" 569 | "L'impression va de %f mm à %f mm en Y\n" 570 | "et mesure %f mm de large\n" 571 | 572 | #: pronterface.py:1593 573 | msgid "" 574 | "the print goes from %f mm to %f mm in Z\n" 575 | "and is %f mm high\n" 576 | msgstr "" 577 | "L'impression va de %f mm à %f mm en Y\n" 578 | "et mesure %f mm de haut\n" 579 | 580 | #: pronterface.py:1595 581 | msgid "Estimated duration (pessimistic): " 582 | msgstr "Durée estimée (pessimiste) : " 583 | 584 | #: pronterface.py:1622 585 | msgid "No file loaded. Please use load first." 586 | msgstr "Aucun fichier chargé. Veuillez charger un fichier avant." 587 | 588 | #: pronterface.py:1633 589 | msgid "Restart" 590 | msgstr "Recommencer" 591 | 592 | #: pronterface.py:1637 593 | msgid "File upload complete" 594 | msgstr "Envoi du fichier terminé" 595 | 596 | #: pronterface.py:1656 597 | msgid "Pick SD filename" 598 | msgstr "Lister les fichiers sur la carte SD" 599 | 600 | #: pronterface.py:1663 601 | msgid "Paused." 602 | msgstr "En pause." 603 | 604 | #: pronterface.py:1674 605 | msgid "Resume" 606 | msgstr "Reprendre" 607 | 608 | #: pronterface.py:1688 609 | msgid "Connecting..." 610 | msgstr "Connexion en cours..." 611 | 612 | #: pronterface.py:1738 613 | msgid "Disconnected." 614 | msgstr "Déconnecté." 615 | 616 | #: pronterface.py:1771 617 | msgid "Reset." 618 | msgstr "Réinitialisée." 619 | 620 | #: pronterface.py:1772 621 | msgid "Are you sure you want to reset the printer?" 622 | msgstr "Etes-vous sûr de vouloir réinitialiser l'imprimante?" 623 | 624 | #: pronterface.py:1772 625 | msgid "Reset?" 626 | msgstr "Réinitialiser ?" 627 | --------------------------------------------------------------------------------