├── 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 |
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 |
--------------------------------------------------------------------------------