Commit 2d830491 authored by Guillaume Seguin's avatar Guillaume Seguin

Merge branch 'experimental' of git://github.com/kliment/Printrun into experimental

parents c8138998 9632556c
......@@ -113,6 +113,7 @@ To use pronterface, you need:
* python (ideally 2.6.x or 2.7.x),
* pyserial (or python-serial on ubuntu/debian)
* pyglet
* numpy (for 3D view)
* pyreadline (not needed on Linux) and
* argparse (installed by default with python >= 2.7)
* wxPython
......
......@@ -16,12 +16,12 @@
# along with Printrun. If not, see <http://www.gnu.org/licenses/>.
from serial import Serial, SerialException
from select import error as SelectError
from threading import Thread
from select import error as SelectError, select
import time, getopt, sys
import platform, os
import socket # Network
import re # Regex
import platform, os, traceback
import socket
import re
from collections import deque
from printrun.GCodeAnalyzer import GCodeAnalyzer
from printrun import gcoder
......@@ -40,9 +40,6 @@ def enable_hup(port):
def disable_hup(port):
control_ttyhup(port, True)
def is_socket(printer):
return (type(printer) == socket._socketobject)
class printcore():
def __init__(self, port = None, baud = None):
"""Initializes a printcore instance. Pass the port and baud rate to connect immediately
......@@ -108,14 +105,40 @@ class printcore():
self.baud = baud
if self.port is not None and self.baud is not None:
# Connect to socket if "port" is an IP, device if not
p = re.compile("^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$")
if p.match(port):
self.printer = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
host_regexp = re.compile("^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$|^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$")
is_serial = True
if ":" in port:
bits = port.split(":")
if len(bits) == 2:
hostname = bits[0]
try:
port = int(bits[1])
if host_regexp.match(hostname) and 1 <= port <= 65535:
is_serial = False
except:
pass
if not is_serial:
self.printer_tcp = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.timeout = 0.25
self.printer.connect((port, baud))
try:
self.printer_tcp.connect((hostname, port))
self.printer = self.printer_tcp.makefile()
except socket.error:
print _("Could not connect to %s:%s:") % (hostname, port)
self.printer = None
self.printer_tcp = None
traceback.print_exc()
return
else:
disable_hup(self.port)
self.printer_tcp = None
try:
self.printer = Serial(port = self.port, baudrate = self.baud, timeout = 0.25)
except SerialException:
print _("Could not connect to %s at baudrate %s:") % (self.port, self.baud)
self.printer = None
traceback.print_exc()
return
self.stop_read_thread = False
self.read_thread = Thread(target = self._listen)
self.read_thread.start()
......@@ -123,26 +146,19 @@ class printcore():
def reset(self):
"""Reset the printer
"""
if self.printer and not is_socket(self.printer):
if self.printer and not self.printer_tcp:
self.printer.setDTR(1)
time.sleep(0.2)
self.printer.setDTR(0)
def _readline(self):
try:
# Read line if socket
if is_socket(self.printer):
line = ''
ready = select([self.printer], [], [], self.timeout)
if ready[0]:
while not "\n" in line:
chunk = self.printer.recv(1)
if chunk == '':
raise RuntimeError("socket connection broken")
line = line + chunk
# Read if tty
else:
try:
line = self.printer.readline()
if self.printer_tcp and not line:
raise OSError("Read EOF from socket")
except socket.timeout:
return ""
if len(line) > 1:
self.log.append(line)
......@@ -151,21 +167,22 @@ class printcore():
except: pass
if self.loud: print "RECV: ", line.rstrip()
return line
except SelectError, e:
except SelectError as e:
if 'Bad file descriptor' in e.args[1]:
print "Can't read from printer (disconnected?)."
print "Can't read from printer (disconnected?) (SelectError {0}): {1}".format(e.errno, e.strerror)
return None
else:
print "SelectError ({0}): {1}".format(e.errno, e.strerror)
raise
except SerialException, e:
print "Can't read from printer (disconnected?)."
except SerialException as e:
print "Can't read from printer (disconnected?) (SerialException {0}): {1}".format(e.errno, e.strerror)
return None
except OSError, e:
print "Can't read from printer (disconnected?)."
except OSError as e:
print "Can't read from printer (disconnected?) (OS Error {0}): {1}".format(e.errno, e.strerror)
return None
def _listen_can_continue(self):
if is_socket(self.printer):
if self.printer_tcp:
return not self.stop_read_thread and self.printer
return not self.stop_read_thread and self.printer and self.printer.isOpen()
......@@ -348,7 +365,7 @@ class printcore():
def send_now(self, command, wait = 0):
"""Sends a command to the printer ahead of the command queue, without a checksum
"""
if self.online or force:
if self.online:
if self.printing:
self.priqueue.append(command)
else:
......@@ -457,21 +474,12 @@ class printcore():
try: self.sendcb(command)
except: pass
try:
# If the printer is connected via Ethernet, use send
if is_socket(self.printer):
msg = str(command+"\n")
totalsent = 0
while totalsent < len(msg):
sent = self.printer.send(msg[totalsent:])
if sent == 0:
raise RuntimeError("socket connection broken")
totalsent = totalsent + sent
else:
self.printer.write(str(command+"\n"))
self.printer.write(str(command + "\n"))
self.printer.flush()
except SerialException, e:
print "Can't write to printer (disconnected?)."
print "Can't write to printer (disconnected?) ({0}): {1}".format(e.errno, e.strerror)
except RuntimeError, e:
print "Socket connection broken, disconnected."
print "Socket connection broken, disconnected. ({0}): {1}".format(e.errno, e.strerror)
if __name__ == '__main__':
baud = 115200
......
# -*- coding: utf-8 -*-
# This file is part of CairoSVG
# Copyright © 2010-2012 Kozea
#
# This library is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option) any
# later version.
#
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with CairoSVG. If not, see <http://www.gnu.org/licenses/>.
"""
CairoSVG - A simple SVG converter for Cairo.
"""
import os
import sys
import optparse
from . import surface
VERSION = '0.4.4'
SURFACES = {
'SVG': surface.SVGSurface, # Tell us if you actually use this one!
'PNG': surface.PNGSurface,
'PDF': surface.PDFSurface,
'PS': surface.PSSurface}
# Generate the svg2* functions from SURFACES
for _output_format, _surface_type in SURFACES.items():
_function = (
# Two lambdas needed for the closure
lambda surface_type: lambda *args, **kwargs: # pylint: disable=W0108
surface_type.convert(*args, **kwargs))(_surface_type)
_name = 'svg2%s' % _output_format.lower()
_function.__name__ = _name
_function.__doc__ = surface.Surface.convert.__doc__.replace(
'the format for this class', _output_format)
setattr(sys.modules[__name__], _name, _function)
def main():
"""Entry-point of the executable."""
# Get command-line options
option_parser = optparse.OptionParser(
usage = "usage: %prog filename [options]", version = VERSION)
option_parser.add_option(
"-f", "--format", help = "output format")
option_parser.add_option(
"-d", "--dpi", help = "svg resolution", default = 96)
option_parser.add_option(
"-o", "--output",
default = "", help = "output filename")
options, args = option_parser.parse_args()
# Print help if no argument is given
if not args:
option_parser.print_help()
sys.exit()
kwargs = {'dpi': float(options.dpi)}
if not options.output or options.output == '-':
# Python 2/3 hack
bytes_stdout = getattr(sys.stdout, "buffer", sys.stdout)
kwargs['write_to'] = bytes_stdout
else:
kwargs['write_to'] = options.output
url = args[0]
if url == "-":
# Python 2/3 hack
bytes_stdin = getattr(sys.stdin, "buffer", sys.stdin)
kwargs['file_obj'] = bytes_stdin
else:
kwargs['url'] = url
output_format = (
options.format or
os.path.splitext(options.output)[1].lstrip(".") or
"pdf")
SURFACES[output_format.upper()].convert(**kwargs)
# -*- coding: utf-8 -*-
# This file is part of CairoSVG
# Copyright © 2010-2012 Kozea
#
# This library is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option) any
# later version.
#
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with CairoSVG. If not, see <http://www.gnu.org/licenses/>.
"""
Optionally handle CSS stylesheets.
"""
from .parser import HAS_LXML
# Detect optional depedencies
# pylint: disable=W0611
try:
import tinycss
import cssselect
CSS_CAPABLE = HAS_LXML
except ImportError:
CSS_CAPABLE = False
# pylint: enable=W0611
# Python 2/3 compat
iteritems = getattr(dict, "iteritems", dict.items) # pylint: disable=C0103
def find_stylesheets(tree):
"""Find the stylesheets included in ``tree``."""
# TODO: support contentStyleType on <svg>
default_type = "text/css"
for element in tree.iter():
# http://www.w3.org/TR/SVG/styling.html#StyleElement
if (element.tag == "style" and
element.get("type", default_type) == "text/css"):
# TODO: pass href for relative URLs
# TODO: support media types
# TODO: what if <style> has children elements?
yield tinycss.make_parser().parse_stylesheet(element.text)
# TODO: support <?xml-stylesheet ... ?>
def find_style_rules(tree):
"""Find the style rules in ``tree``."""
for stylesheet in find_stylesheets(tree):
# TODO: warn for each stylesheet.errors
for rule in stylesheet.rules:
# TODO: support @import and @media
if not rule.at_keyword:
yield rule
def get_declarations(rule):
"""Get the declarations in ``rule``."""
for declaration in rule.declarations:
if declaration.name.startswith("-"):
# Ignore properties prefixed by "-"
continue
# TODO: filter out invalid values
yield (
declaration.name,
declaration.value.as_css(),
bool(declaration.priority))
def match_selector(rule, tree):
"""Yield the ``(element, specificity)`` in ``tree`` matching ``rule``."""
selector_list = cssselect.parse(rule.selector.as_css())
translator = cssselect.GenericTranslator()
for selector in selector_list:
if not selector.pseudo_element:
specificity = selector.specificity()
for element in tree.xpath(translator.selector_to_xpath(selector)):
yield element, specificity
def apply_stylesheets(tree):
"""Apply the stylesheet in ``tree`` to ``tree``."""
if not CSS_CAPABLE:
# TODO: warn?
return
style_by_element = {}
for rule in find_style_rules(tree):
declarations = list(get_declarations(rule))
for element, specificity in match_selector(rule, tree):
style = style_by_element.setdefault(element, {})
for name, value, important in declarations:
weight = important, specificity
if name in style:
_old_value, old_weight = style[name]
if old_weight > weight:
continue
style[name] = value, weight
for element, style in iteritems(style_by_element):
values = ["%s: %s" % (name, value)
for name, (value, weight) in iteritems(style)]
values.append(element.get("style", ""))
element.set("style", ";".join(values))
# -*- coding: utf-8 -*-
# This file is part of CairoSVG
# Copyright © 2010-2012 Kozea
#
# This library is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option) any
# later version.
#
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with CairoSVG. If not, see <http://www.gnu.org/licenses/>.
"""
SVG Parser.
"""
# Fallbacks for Python 2/3 and lxml/ElementTree
# pylint: disable=E0611,F0401,W0611
try:
import lxml.etree as ElementTree
from lxml.etree import XMLSyntaxError as ParseError
HAS_LXML = True
except ImportError:
from xml.etree import ElementTree
from xml.parsers import expat
# ElementTree's API changed between 2.6 and 2.7
# pylint: disable=C0103
ParseError = getattr(ElementTree, 'ParseError', expat.ExpatError)
# pylint: enable=C0103
HAS_LXML = False
try:
from urllib import urlopen
import urlparse
except ImportError:
from urllib.request import urlopen
from urllib import parse as urlparse # Python 3
# pylint: enable=E0611,F0401,W0611
import gzip
import os.path
from .css import apply_stylesheets
# Python 2/3 compat
# pylint: disable=C0103,W0622
try:
basestring
except NameError:
basestring = str
# pylint: enable=C0103,W0622
def remove_svg_namespace(tree):
"""Remove the SVG namespace from ``tree`` tags.
``lxml.cssselect`` does not support empty/default namespaces, so remove any
SVG namespace.
"""
prefix = "{http://www.w3.org/2000/svg}"
prefix_len = len(prefix)
iterator = (
tree.iter() if hasattr(tree, 'iter')
else tree.getiterator())
for element in iterator:
tag = element.tag
if hasattr(tag, "startswith") and tag.startswith(prefix):
element.tag = tag[prefix_len:]
class Node(dict):
"""SVG node with dict-like properties and children."""
def __init__(self, node, parent = None):
"""Create the Node from ElementTree ``node``, with ``parent`` Node."""
super(Node, self).__init__()
self.children = ()
self.root = False
self.tag = node.tag
self.text = node.text
# Inherits from parent properties
# TODO: drop other attributes that should not be inherited
if parent is not None:
items = parent.copy()
not_inherited = (
"transform", "opacity", "style", "viewBox", "stop-color",
"stop-opacity")
if self.tag in ("tspan", "pattern"):
not_inherited += ("x", "y")
for attribute in not_inherited:
if attribute in items:
del items[attribute]
self.update(items)
self.url = parent.url
self.xml_tree = parent.xml_tree
self.parent = parent
self.update(dict(node.attrib.items()))
# Handle the CSS
style = self.pop("style", "")
for declaration in style.split(";"):
if ":" in declaration:
name, value = declaration.split(":", 1)
self[name.strip()] = value.strip()
# Replace currentColor by a real color value
color_attributes = (
"fill", "stroke", "stop-color", "flood-color",
"lighting-color")
for attribute in color_attributes:
if self.get(attribute) == "currentColor":
self[attribute] = self.get("color", "black")
# Replace inherit by the parent value
for attribute, value in dict(self).items():
if value == "inherit":
if parent is not None and attribute in parent:
self[attribute] = parent.get(attribute)
else:
del self[attribute]
# Manage text by creating children
if self.tag == "text" or self.tag == "textPath":
self.children = self.text_children(node)
if not self.children:
self.children = tuple(
Node(child, self) for child in node
if isinstance(child.tag, basestring))
def text_children(self, node):
"""Create children and return them."""
children = []
for child in node:
children.append(Node(child, parent = self))
if child.tail:
anonymous = ElementTree.Element('tspan')
anonymous.text = child.tail
children.append(Node(anonymous, parent = self))
return list(children)
class Tree(Node):
"""SVG tree."""
def __init__(self, **kwargs):
"""Create the Tree from SVG ``text``."""
# Make the parameters keyword-only:
bytestring = kwargs.pop('bytestring', None)
file_obj = kwargs.pop('file_obj', None)
url = kwargs.pop('url', None)
parent = kwargs.pop('parent', None)
if bytestring is not None:
tree = ElementTree.fromstring(bytestring)
self.url = url
elif file_obj is not None:
tree = ElementTree.parse(file_obj).getroot()
if url:
self.url = url
else:
self.url = getattr(file_obj, 'name', None)
elif url is not None:
if "#" in url:
url, element_id = url.split("#", 1)
else:
element_id = None
if parent and parent.url:
if url:
url = urlparse.urljoin(parent.url, url)
elif element_id:
url = parent.url
self.url = url
if url:
if urlparse.urlparse(url).scheme:
input_ = urlopen(url)
else:
input_ = url # filename
if os.path.splitext(url)[1].lower() == "svgz":
input_ = gzip.open(url)
tree = ElementTree.parse(input_).getroot()
else:
tree = parent.xml_tree
if element_id:
iterator = (
tree.iter() if hasattr(tree, 'iter')
else tree.getiterator())
for element in iterator:
if element.get("id") == element_id:
tree = element
break
else:
raise TypeError(
'No tag with id="%s" found.' % element_id)
else:
raise TypeError(
'No input. Use one of bytestring, file_obj or url.')
remove_svg_namespace(tree)
apply_stylesheets(tree)
self.xml_tree = tree
super(Tree, self).__init__(tree, parent)
self.root = True
This diff is collapsed.
# -*- coding: utf-8 -*-
# This file is part of CairoSVG
# Copyright © 2010-2012 Kozea
#
# This library is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option) any
# later version.
#
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with CairoSVG. If not, see <http://www.gnu.org/licenses/>.
"""
SVG colors.
"""
COLORS = {
"aliceblue": "rgb(240, 248, 255)",
"antiquewhite": "rgb(250, 235, 215)",
"aqua": "rgb(0, 255, 255)",
"aquamarine": "rgb(127, 255, 212)",
"azure": "rgb(240, 255, 255)",
"beige": "rgb(245, 245, 220)",
"bisque": "rgb(255, 228, 196)",
"black": "rgb(0, 0, 0)",
"blanchedalmond": "rgb(255, 235, 205)",
"blue": "rgb(0, 0, 255)",
"blueviolet": "rgb(138, 43, 226)",
"brown": "rgb(165, 42, 42)",
"burlywood": "rgb(222, 184, 135)",
"cadetblue": "rgb(95, 158, 160)",
"chartreuse": "rgb(127, 255, 0)",
"chocolate": "rgb(210, 105, 30)",
"coral": "rgb(255, 127, 80)",
"cornflowerblue": "rgb(100, 149, 237)",
"cornsilk": "rgb(255, 248, 220)",
"crimson": "rgb(220, 20, 60)",
"cyan": "rgb(0, 255, 255)",
"darkblue": "rgb(0, 0, 139)",
"darkcyan": "rgb(0, 139, 139)",
"darkgoldenrod": "rgb(184, 134, 11)",
"darkgray": "rgb(169, 169, 169)",
"darkgreen": "rgb(0, 100, 0)",
"darkgrey": "rgb(169, 169, 169)",
"darkkhaki": "rgb(189, 183, 107)",
"darkmagenta": "rgb(139, 0, 139)",
"darkolivegreen": "rgb(85, 107, 47)",
"darkorange": "rgb(255, 140, 0)",
"darkorchid": "rgb(153, 50, 204)",
"darkred": "rgb(139, 0, 0)",
"darksalmon": "rgb(233, 150, 122)",
"darkseagreen": "rgb(143, 188, 143)",
"darkslateblue": "rgb(72, 61, 139)",
"darkslategray": "rgb(47, 79, 79)",
"darkslategrey": "rgb(47, 79, 79)",
"darkturquoise": "rgb(0, 206, 209)",
"darkviolet": "rgb(148, 0, 211)",
"deeppink": "rgb(255, 20, 147)",
"deepskyblue": "rgb(0, 191, 255)",
"dimgray": "rgb(105, 105, 105)",
"dimgrey": "rgb(105, 105, 105)",
"dodgerblue": "rgb(30, 144, 255)",
"firebrick": "rgb(178, 34, 34)",
"floralwhite": "rgb(255, 250, 240)",
"forestgreen": "rgb(34, 139, 34)",
"fuchsia": "rgb(255, 0, 255)",
"gainsboro": "rgb(220, 220, 220)",
"ghostwhite": "rgb(248, 248, 255)",
"gold": "rgb(255, 215, 0)",
"goldenrod": "rgb(218, 165, 32)",
"gray": "rgb(128, 128, 128)",
"grey": "rgb(128, 128, 128)",
"green": "rgb(0, 128, 0)",
"greenyellow": "rgb(173, 255, 47)",
"honeydew": "rgb(240, 255, 240)",
"hotpink": "rgb(255, 105, 180)",
"indianred": "rgb(205, 92, 92)",
"indigo": "rgb(75, 0, 130)",
"ivory": "rgb(255, 255, 240)",
"khaki": "rgb(240, 230, 140)",
"lavender": "rgb(230, 230, 250)",
"lavenderblush": "rgb(255, 240, 245)",
"lawngreen": "rgb(124, 252, 0)",
"lemonchiffon": "rgb(255, 250, 205)",
"lightblue": "rgb(173, 216, 230)",
"lightcoral": "rgb(240, 128, 128)",
"lightcyan": "rgb(224, 255, 255)",
"lightgoldenrodyellow": "rgb(250, 250, 210)",
"lightgray": "rgb(211, 211, 211)",
"lightgreen": "rgb(144, 238, 144)",
"lightgrey": "rgb(211, 211, 211)",
"lightpink": "rgb(255, 182, 193)",
"lightsalmon": "rgb(255, 160, 122)",
"lightseagreen": "rgb(32, 178, 170)",
"lightskyblue": "rgb(135, 206, 250)",
"lightslategray": "rgb(119, 136, 153)",
"lightslategrey": "rgb(119, 136, 153)",
"lightsteelblue": "rgb(176, 196, 222)",
"lightyellow": "rgb(255, 255, 224)",
"lime": "rgb(0, 255, 0)",
"limegreen": "rgb(50, 205, 50)",
"linen": "rgb(250, 240, 230)",
"magenta": "rgb(255, 0, 255)",
"maroon": "rgb(128, 0, 0)",
"mediumaquamarine": "rgb(102, 205, 170)",
"mediumblue": "rgb(0, 0, 205)",
"mediumorchid": "rgb(186, 85, 211)",
"mediumpurple": "rgb(147, 112, 219)",
"mediumseagreen": "rgb(60, 179, 113)",
"mediumslateblue": "rgb(123, 104, 238)",
"mediumspringgreen": "rgb(0, 250, 154)",
"mediumturquoise": "rgb(72, 209, 204)",
"mediumvioletred": "rgb(199, 21, 133)",
"midnightblue": "rgb(25, 25, 112)",
"mintcream": "rgb(245, 255, 250)",
"mistyrose": "rgb(255, 228, 225)",
"moccasin": "rgb(255, 228, 181)",
"navajowhite": "rgb(255, 222, 173)",
"navy": "rgb(0, 0, 128)",
"oldlace": "rgb(253, 245, 230)",
"olive": "rgb(128, 128, 0)",
"olivedrab": "rgb(107, 142, 35)",
"orange": "rgb(255, 165, 0)",
"orangered": "rgb(255, 69, 0)",
"orchid": "rgb(218, 112, 214)",
"palegoldenrod": "rgb(238, 232, 170)",
"palegreen": "rgb(152, 251, 152)",
"paleturquoise": "rgb(175, 238, 238)",
"palevioletred": "rgb(219, 112, 147)",
"papayawhip": "rgb(255, 239, 213)",
"peachpuff": "rgb(255, 218, 185)",
"peru": "rgb(205, 133, 63)",
"pink": "rgb(255, 192, 203)",
"plum": "rgb(221, 160, 221)",
"powderblue": "rgb(176, 224, 230)",
"purple": "rgb(128, 0, 128)",
"red": "rgb(255, 0, 0)",
"rosybrown": "rgb(188, 143, 143)",
"royalblue": "rgb(65, 105, 225)",
"saddlebrown": "rgb(139, 69, 19)",
"salmon": "rgb(250, 128, 114)",
"sandybrown": "rgb(244, 164, 96)",
"seagreen": "rgb(46, 139, 87)",
"seashell": "rgb(255, 245, 238)",
"sienna": "rgb(160, 82, 45)",
"silver": "rgb(192, 192, 192)",
"skyblue": "rgb(135, 206, 235)",
"slateblue": "rgb(106, 90, 205)",
"slategray": "rgb(112, 128, 144)",
"slategrey": "rgb(112, 128, 144)",
"snow": "rgb(255, 250, 250)",
"springgreen": "rgb(0, 255, 127)",
"steelblue": "rgb(70, 130, 180)",
"tan": "rgb(210, 180, 140)",
"teal": "rgb(0, 128, 128)",
"thistle": "rgb(216, 191, 216)",
"tomato": "rgb(255, 99, 71)",
"turquoise": "rgb(64, 224, 208)",
"violet": "rgb(238, 130, 238)",
"wheat": "rgb(245, 222, 179)",
"white": "rgb(255, 255, 255)",
"whitesmoke": "rgb(245, 245, 245)",
"yellow": "rgb(255, 255, 0)",
"yellowgreen": "rgb(154, 205, 50)",
"activeborder": "#0000ff",
"activecaption": "#0000ff",
"appworkspace": "#ffffff",
"background": "#ffffff",
"buttonface": "#000000",
"buttonhighlight": "#cccccc",
"buttonshadow": "#333333",
"buttontext": "#000000",
"captiontext": "#000000",
"graytext": "#333333",
"highlight": "#0000ff",
"highlighttext": "#cccccc",
"inactiveborder": "#333333",
"inactivecaption": "#cccccc",
"inactivecaptiontext": "#333333",
"infobackground": "#cccccc",
"infotext": "#000000",
"menu": "#cccccc",
"menutext": "#333333",
"scrollbar": "#cccccc",
"threeddarkshadow": "#333333",
"threedface": "#cccccc",
"threedhighlight": "#ffffff",
"threedlightshadow": "#333333",
"threedshadow": "#333333",
"window": "#cccccc",
"windowframe": "#cccccc",
"windowtext": "#000000"}
def color(string = None, opacity = 1):
"""Replace ``string`` representing a color by a RGBA tuple."""
if not string or string in ("none", "transparent"):
return (0, 0, 0, 0)
string = string.strip().lower()
if string in COLORS:
string = COLORS[string]
if string.startswith("rgba"):
r, g, b, a = tuple(
float(i.strip(" %")) * 2.55 if "%" in i else float(i)
for i in string.strip(" rgba()").split(","))
return r / 255, g / 255, b / 255, a * opacity
elif string.startswith("rgb"):
r, g, b = tuple(
float(i.strip(" %")) / 100 if "%" in i else float(i) / 255
for i in string.strip(" rgb()").split(","))
return r, g, b, opacity
if len(string) in (4, 5):
string = "#" + "".join(2 * char for char in string[1:])
if len(string) == 9:
opacity *= int(string[7:9], 16) / 255
try:
plain_color = tuple(
int(value, 16) / 255. for value in (
string[1:3], string[3:5], string[5:7]))
except ValueError:
# Unknown color, return black
return (0, 0, 0, 1)
else:
return plain_color + (opacity,)
# -*- coding: utf-8 -*-
# This file is part of CairoSVG
# Copyright © 2010-2012 Kozea
#
# This library is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option) any
# later version.
#
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with CairoSVG. If not, see <http://www.gnu.org/licenses/>.
"""
Externally defined elements managers.
This module handles gradients and patterns.
"""
import cairo
from math import radians
from copy import deepcopy
from .colors import color
from .helpers import node_format, preserve_ratio, urls, transform
from .units import size
from ..parser import Tree
def parse_def(surface, node):
"""Parse the SVG definitions."""
for def_type in ("marker", "gradient", "pattern", "path"):
if def_type in node.tag.lower():
def_list = getattr(surface, def_type + "s")
name = node["id"]
href = node.get("{http://www.w3.org/1999/xlink}href")
if href and href[0] == "#" and href[1:] in def_list:
new_node = deepcopy(def_list[href[1:]])
new_node.update(node)
node = new_node
def_list[name] = node
def gradient_or_pattern(surface, node, name):
"""Gradient or pattern color."""
if name in surface.gradients:
return draw_gradient(surface, node, name)
elif name in surface.patterns:
return draw_pattern(surface, name)
def marker(surface, node):
"""Store a marker definition."""
parse_def(surface, node)
def linear_gradient(surface, node):
"""Store a linear gradient definition."""
parse_def(surface, node)
def radial_gradient(surface, node):
"""Store a radial gradient definition."""
parse_def(surface, node)
def pattern(surface, node):
"""Store a pattern definition."""
parse_def(surface, node)
def draw_gradient(surface, node, name):
"""Gradients colors."""
gradient_node = surface.gradients[name]
transform(surface, gradient_node.get("gradientTransform"))
if gradient_node.get("gradientUnits") == "userSpaceOnUse":
width_ref, height_ref = "x", "y"
diagonal_ref = "xy"
else:
x = float(size(surface, node.get("x"), "x"))
y = float(size(surface, node.get("y"), "y"))
width = float(size(surface, node.get("width"), "x"))
height = float(size(surface, node.get("height"), "y"))
width_ref = height_ref = diagonal_ref = 1
if gradient_node.tag == "linearGradient":
x1 = float(size(surface, gradient_node.get("x1", "0%"), width_ref))
x2 = float(size(surface, gradient_node.get("x2", "100%"), width_ref))
y1 = float(size(surface, gradient_node.get("y1", "0%"), height_ref))
y2 = float(size(surface, gradient_node.get("y2", "0%"), height_ref))
gradient_pattern = cairo.LinearGradient(x1, y1, x2, y2)
elif gradient_node.tag == "radialGradient":
r = float(size(surface, gradient_node.get("r", "50%"), diagonal_ref))
cx = float(size(surface, gradient_node.get("cx", "50%"), width_ref))
cy = float(size(surface, gradient_node.get("cy", "50%"), height_ref))
fx = float(size(surface, gradient_node.get("fx", str(cx)), width_ref))
fy = float(size(surface, gradient_node.get("fy", str(cy)), height_ref))
gradient_pattern = cairo.RadialGradient(fx, fy, 0, cx, cy, r)
if gradient_node.get("gradientUnits") != "userSpaceOnUse":
gradient_pattern.set_matrix(cairo.Matrix(
1 / width, 0, 0, 1 / height, -x / width, -y / height))
gradient_pattern.set_extend(getattr(
cairo, "EXTEND_%s" % node.get("spreadMethod", "pad").upper()))
offset = 0
for child in gradient_node.children:
offset = max(offset, size(surface, child.get("offset"), 1))
stop_color = color(
child.get("stop-color", "black"),
float(child.get("stop-opacity", 1)))
gradient_pattern.add_color_stop_rgba(offset, *stop_color)
gradient_pattern.set_extend(getattr(
cairo, "EXTEND_%s" % gradient_node.get("spreadMethod", "pad").upper()))
surface.context.set_source(gradient_pattern)
def draw_pattern(surface, name):
"""Draw a pattern image."""
pattern_node = surface.patterns[name]
pattern_node.tag = "g"
transform(surface, "translate(%s %s)" % (
pattern_node.get("x"), pattern_node.get("y")))
transform(surface, pattern_node.get("patternTransform"))
from . import SVGSurface # circular import
pattern_surface = SVGSurface(pattern_node, None, surface.dpi)
pattern_pattern = cairo.SurfacePattern(pattern_surface.cairo)
pattern_pattern.set_extend(cairo.EXTEND_REPEAT)
surface.context.set_source(pattern_pattern)
def draw_marker(surface, node, position = "mid"):
"""Draw a marker."""
# TODO: manage markers for other tags than path
if position == "start":
node.markers = {
"start": list(urls(node.get("marker-start", ""))),
"mid": list(urls(node.get("marker-mid", ""))),
"end": list(urls(node.get("marker-end", "")))}
all_markers = list(urls(node.get("marker", "")))
for markers_list in node.markers.values():
markers_list.extend(all_markers)
pending_marker = (
surface.context.get_current_point(), node.markers[position])
if position == "start":
node.pending_markers.append(pending_marker)
return
elif position == "end":
node.pending_markers.append(pending_marker)
while node.pending_markers:
next_point, markers = node.pending_markers.pop(0)
angle1 = node.tangents.pop(0)
angle2 = node.tangents.pop(0)
if angle1 is None:
angle1 = angle2
for active_marker in markers:
if not active_marker.startswith("#"):
continue
active_marker = active_marker[1:]
if active_marker in surface.markers:
marker_node = surface.markers[active_marker]
angle = marker_node.get("orient", "0")
if angle == "auto":
angle = float(angle1 + angle2) / 2
else:
angle = radians(float(angle))
temp_path = surface.context.copy_path()
current_x, current_y = next_point
if node.get("markerUnits") == "userSpaceOnUse":
base_scale = 1
else:
base_scale = size(
surface, surface.parent_node.get("stroke-width"))
# Returns 4 values
scale_x, scale_y, translate_x, translate_y = \
preserve_ratio(surface, marker_node)
viewbox = node_format(surface, marker_node)[-1]
viewbox_width = viewbox[2] - viewbox[0]
viewbox_height = viewbox[3] - viewbox[1]
surface.context.new_path()
for child in marker_node.children:
surface.context.save()
surface.context.translate(current_x, current_y)
surface.context.rotate(angle)
surface.context.scale(
base_scale / viewbox_width * float(scale_x),
base_scale / viewbox_height * float(scale_y))
surface.context.translate(translate_x, translate_y)
surface.draw(child)
surface.context.restore()
surface.context.append_path(temp_path)
if position == "mid":
node.pending_markers.append(pending_marker)
def use(surface, node):
"""Draw the content of another SVG file."""
surface.context.save()
surface.context.translate(
size(surface, node.get("x"), "x"), size(surface, node.get("y"), "y"))
if "x" in node:
del node["x"]
if "y" in node:
del node["y"]
if "viewBox" in node:
del node["viewBox"]
href = node.get("{http://www.w3.org/1999/xlink}href")
url = list(urls(href))[0]
tree = Tree(url = url, parent = node)
surface.set_context_size(*node_format(surface, tree))
surface.draw(tree)
surface.context.restore()
# Restore twice, because draw does not restore at the end of svg tags
surface.context.restore()
# -*- coding: utf-8 -*-
# This file is part of CairoSVG
# Copyright © 2010-2012 Kozea
#
# This library is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option) any
# later version.
#
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with CairoSVG. If not, see <http://www.gnu.org/licenses/>.
"""
Surface helpers.
"""
import cairo
from math import cos, sin, tan, atan2, radians
from .units import size
# Python 2/3 management
# pylint: disable=C0103
try:
Error = cairo.Error
except AttributeError:
Error = SystemError
# pylint: enable=C0103
class PointError(Exception):
"""Exception raised when parsing a point fails."""
def distance(x1, y1, x2, y2):
"""Get the distance between two points."""
return ((x2 - x1) ** 2 + (y2 - y1) ** 2) ** 0.5
def filter_fill_or_stroke(value):
"""Remove unnecessary characters from fill or stroke value."""
if not value:
return
content = list(urls(value))[0]
if "url" in value:
if not content.startswith("#"):
return
content = content[1:]
return content
def node_format(surface, node):
"""Return ``(width, height, viewbox)`` of ``node``."""
width = size(surface, node.get("width"), "x")
height = size(surface, node.get("height"), "y")
viewbox = node.get("viewBox")
if viewbox:
viewbox = tuple(float(position) for position in viewbox.split())
width = width or viewbox[2]
height = height or viewbox[3]
return width, height, viewbox
def normalize(string = None):
"""Normalize a string corresponding to an array of various values."""
string = string.replace("-", " -")
string = string.replace(",", " ")
while " " in string:
string = string.replace(" ", " ")
string = string.replace("e -", "e-")
values = string.split(" ")
string = ""
for value in values:
if value.count(".") > 1:
numbers = value.split(".")
string += "%s.%s " % (numbers.pop(0), numbers.pop(0))
string += ".%s " % " .".join(numbers)
else:
string += value + " "
return string.strip()
def point(surface, string = None):
"""Return ``(x, y, trailing_text)`` from ``string``."""
if not string:
return (0, 0, "")
try:
x, y, string = (string.strip() + " ").split(" ", 2)
except ValueError:
raise PointError("The point cannot be found in string %s" % string)
return size(surface, x, "x"), size(surface, y, "y"), string
def point_angle(cx, cy, px, py):
"""Return angle between x axis and point knowing given center."""
return atan2(py - cy, px - cx)
def preserve_ratio(surface, node):
"""Manage the ratio preservation."""
if node.tag == "marker":
scale_x = size(surface, node.get("markerWidth", "3"), "x")
scale_y = size(surface, node.get("markerHeight", "3"), "y")
translate_x = -size(surface, node.get("refX"))
translate_y = -size(surface, node.get("refY"))
elif node.tag in ("svg", "image"):
width, height, _ = node_format(surface, node)
scale_x = width / node.image_width
scale_y = height / node.image_height
align = node.get("preserveAspectRatio", "xMidYMid").split(" ")[0]
if align == "none":
return scale_x, scale_y, 0, 0
else:
mos_properties = node.get("preserveAspectRatio", "").split()
meet_or_slice = (
mos_properties[1] if len(mos_properties) > 1 else None)
if meet_or_slice == "slice":
scale_value = max(scale_x, scale_y)
else:
scale_value = min(scale_x, scale_y)
scale_x = scale_y = scale_value
x_position = align[1:4].lower()
y_position = align[5:].lower()
if x_position == "min":
translate_x = 0
if y_position == "min":
translate_y = 0
if x_position == "mid":
translate_x = (width / scale_x - node.image_width) / 2.
if y_position == "mid":
translate_y = (height / scale_y - node.image_height) / 2.
if x_position == "max":
translate_x = width / scale_x - node.image_width
if y_position == "max":
translate_y = height / scale_y - node.image_height
return scale_x, scale_y, translate_x, translate_y
def quadratic_points(x1, y1, x2, y2, x3, y3):
"""Return the quadratic points to create quadratic curves."""
xq1 = x2 * 2 / 3 + x1 / 3
yq1 = y2 * 2 / 3 + y1 / 3
xq2 = x2 * 2 / 3 + x3 / 3
yq2 = y2 * 2 / 3 + y3 / 3
return xq1, yq1, xq2, yq2, x3, y3
def rotate(x, y, angle):
"""Rotate a point of an angle around the origin point."""
return x * cos(angle) - y * sin(angle), y * cos(angle) + x * sin(angle)
def transform(surface, string):
"""Update ``surface`` matrix according to transformation ``string``."""
if not string:
return
transformations = string.split(")")
matrix = cairo.Matrix()
for transformation in transformations:
for ttype in (
"scale", "translate", "matrix", "rotate", "skewX",
"skewY"):
if ttype in transformation:
transformation = transformation.replace(ttype, "")
transformation = transformation.replace("(", "")
transformation = normalize(transformation).strip() + " "
values = []
while transformation:
value, transformation = \
transformation.split(" ", 1)
# TODO: manage the x/y sizes here
values.append(size(surface, value))
if ttype == "matrix":
matrix = cairo.Matrix(*values).multiply(matrix)
elif ttype == "rotate":
angle = radians(float(values.pop(0)))
x, y = values or (0, 0)
matrix.translate(x, y)
matrix.rotate(angle)
matrix.translate(-x, -y)
elif ttype == "skewX":
tangent = tan(radians(float(values[0])))
matrix = \
cairo.Matrix(1, 0, tangent, 1, 0, 0).multiply(matrix)
elif ttype == "skewY":
tangent = tan(radians(float(values[0])))
matrix = \
cairo.Matrix(1, tangent, 0, 1, 0, 0).multiply(matrix)
elif ttype == "translate":
if len(values) == 1:
values += (0,)
matrix.translate(*values)
elif ttype == "scale":
if len(values) == 1:
values = 2 * values
matrix.scale(*values)
apply_matrix_transform(surface, matrix)
def apply_matrix_transform(surface, matrix):
try:
matrix.invert()
except Error:
# Matrix not invertible, clip the surface to an empty path
active_path = surface.context.copy_path()
surface.context.new_path()
surface.context.clip()
surface.context.append_path(active_path)
else:
matrix.invert()
surface.context.transform(matrix)
def urls(string):
"""Parse a comma-separated list of url() strings."""
for link in string.split(","):
link = link.strip()
if link.startswith("url"):
link = link[3:]
yield link.strip("() ")
# -*- coding: utf-8 -*-
# This file is part of CairoSVG
# Copyright © 2010-2012 Kozea
#
# This library is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option) any
# later version.
#
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with CairoSVG. If not, see <http://www.gnu.org/licenses/>.
"""
Images manager.
"""
import base64
import cairo
from io import BytesIO
try:
from urllib import urlopen, unquote
import urlparse
unquote_to_bytes = lambda data: unquote(
data.encode('ascii') if isinstance(data, unicode) else data)
except ImportError:
from urllib.request import urlopen
from urllib import parse as urlparse # Python 3
from urllib.parse import unquote_to_bytes
from .helpers import node_format, size, preserve_ratio
from ..parser import Tree
def open_data_url(url):
"""Decode URLs with the 'data' scheme. urllib can handle them
in Python 2, but that is broken in Python 3.
Inspired from Python 2.7.2’s urllib.py.
"""
# syntax of data URLs:
# dataurl := "data:" [ mediatype ] [ ";base64" ] "," data
# mediatype := [ type "/" subtype ] *( ";" parameter )
# data := *urlchar
# parameter := attribute "=" value
try:
header, data = url.split(",", 1)
except ValueError:
raise IOError("bad data URL")
header = header[5:] # len("data:") == 5
if header:
semi = header.rfind(";")
if semi >= 0 and "=" not in header[semi:]:
encoding = header[semi + 1:]
else:
encoding = ""
else:
encoding = ""
data = unquote_to_bytes(data)
if encoding == "base64":
missing_padding = 4 - len(data) % 4
if missing_padding:
data += b"=" * missing_padding
return base64.decodestring(data)
return data
def image(surface, node):
"""Draw an image ``node``."""
url = node.get("{http://www.w3.org/1999/xlink}href")
if not url:
return
if url.startswith("data:"):
image_bytes = open_data_url(url)
else:
base_url = node.get("{http://www.w3.org/XML/1998/namespace}base")
if base_url:
url = urlparse.urljoin(base_url, url)
if node.url:
url = urlparse.urljoin(node.url, url)
if urlparse.urlparse(url).scheme:
input_ = urlopen(url)
else:
input_ = open(url, 'rb') # filename
image_bytes = input_.read()
if len(image_bytes) < 5:
return
x, y = size(surface, node.get("x"), "x"), size(surface, node.get("y"), "y")
width = size(surface, node.get("width"), "x")
height = size(surface, node.get("height"), "y")
surface.context.rectangle(x, y, width, height)
surface.context.clip()
if image_bytes[:4] == b"\x89PNG":
png_bytes = image_bytes
elif image_bytes[:5] == b"\x3csvg ":
surface.context.save()
surface.context.translate(x, y)
if "x" in node:
del node["x"]
if "y" in node:
del node["y"]
if "viewBox" in node:
del node["viewBox"]
tree = Tree(bytestring = image_bytes)
tree_width, tree_height, viewbox = node_format(surface, tree)
if not tree_width or not tree_height:
tree_width = tree["width"] = width
tree_height = tree["height"] = height
node.image_width = tree_width or width
node.image_height = tree_height or height
scale_x, scale_y, translate_x, translate_y = \
preserve_ratio(surface, node)
surface.set_context_size(*node_format(surface, tree))
surface.context.translate(*surface.context.get_current_point())
surface.context.scale(scale_x, scale_y)
surface.context.translate(translate_x, translate_y)
surface.draw(tree)
surface.context.restore()
# Restore twice, because draw does not restore at the end of svg tags
surface.context.restore()
return
else:
try:
from pystacia import read_blob
png_bytes = read_blob(image_bytes).get_blob('png')
except:
# No way to handle the image
return
image_surface = cairo.ImageSurface.create_from_png(BytesIO(png_bytes))
node.image_width = image_surface.get_width()
node.image_height = image_surface.get_height()
scale_x, scale_y, translate_x, translate_y = preserve_ratio(surface, node)
surface.context.rectangle(x, y, width, height)
pattern_pattern = cairo.SurfacePattern(image_surface)
surface.context.save()
surface.context.translate(*surface.context.get_current_point())
surface.context.scale(scale_x, scale_y)
surface.context.translate(translate_x, translate_y)
surface.context.set_source(pattern_pattern)
surface.context.fill()
surface.context.restore()
This diff is collapsed.
# -*- coding: utf-8 -*-
# This file is part of CairoSVG
# Copyright © 2010-2012 Kozea
#
# This library is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option) any
# later version.
#
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with CairoSVG. If not, see <http://www.gnu.org/licenses/>.
"""
Shapes drawers.
"""
from math import pi
from .helpers import normalize, point, size
def circle(surface, node):
"""Draw a circle ``node`` on ``surface``."""
r = size(surface, node.get("r"))
if not r:
return
surface.context.new_sub_path()
surface.context.arc(
size(surface, node.get("x"), "x") + size(surface, node.get("cx"), "x"),
size(surface, node.get("y"), "y") + size(surface, node.get("cy"), "y"),
r, 0, 2 * pi)
def ellipse(surface, node):
"""Draw an ellipse ``node`` on ``surface``."""
rx = size(surface, node.get("rx"), "x")
ry = size(surface, node.get("ry"), "y")
if not rx or not ry:
return
ratio = ry / rx
surface.context.new_sub_path()
surface.context.save()
surface.context.scale(1, ratio)
surface.context.arc(
size(surface, node.get("x"), "x") + size(surface, node.get("cx"), "x"),
(size(surface, node.get("y"), "y") +
size(surface, node.get("cy"), "y")) / ratio,
size(surface, node.get("rx"), "x"), 0, 2 * pi)
surface.context.restore()
def line(surface, node):
"""Draw a line ``node``."""
x1, y1, x2, y2 = tuple(
size(surface, node.get(position), position[0])
for position in ("x1", "y1", "x2", "y2"))
surface.context.move_to(x1, y1)
surface.context.line_to(x2, y2)
def polygon(surface, node):
"""Draw a polygon ``node`` on ``surface``."""
polyline(surface, node)
surface.context.close_path()
def polyline(surface, node):
"""Draw a polyline ``node``."""
points = normalize(node.get("points"))
if points:
x, y, points = point(surface, points)
surface.context.move_to(x, y)
while points:
x, y, points = point(surface, points)
surface.context.line_to(x, y)
def rect(surface, node):
"""Draw a rect ``node`` on ``surface``."""
# TODO: handle ry
x, y = size(surface, node.get("x"), "x"), size(surface, node.get("y"), "y")
width = size(surface, node.get("width"), "x")
height = size(surface, node.get("height"), "y")
if size(surface, node.get("rx"), "x") == 0:
surface.context.rectangle(x, y, width, height)
else:
r = size(surface, node.get("rx"), "x")
a, b, c, d = x, width + x, y, height + y
if r > width - r:
r = width / 2
surface.context.move_to(x, y + height / 2)
surface.context.arc(a + r, c + r, r, 2 * pi / 2, 3 * pi / 2)
surface.context.arc(b - r, c + r, r, 3 * pi / 2, 0 * pi / 2)
surface.context.arc(b - r, d - r, r, 0 * pi / 2, 1 * pi / 2)
surface.context.arc(a + r, d - r, r, 1 * pi / 2, 2 * pi / 2)
surface.context.close_path()
# -*- coding: utf-8 -*-
# This file is part of CairoSVG
# Copyright © 2010-2012 Kozea
#
# This library is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option) any
# later version.
#
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with CairoSVG. If not, see <http://www.gnu.org/licenses/>.
"""
Root tag drawer.
"""
from .helpers import preserve_ratio, node_format
def svg(surface, node):
"""Draw a svg ``node``."""
if node.get("preserveAspectRatio", "none") != "none":
width, height, viewbox = node_format(surface, node)
node.image_width, node.image_height = viewbox[2:]
scale_x, scale_y, translate_x, translate_y = \
preserve_ratio(surface, node)
surface.context.rectangle(0, 0, width, height)
surface.context.clip()
surface.context.translate(*surface.context.get_current_point())
surface.context.scale(scale_x, scale_y)
surface.context.translate(translate_x, translate_y)
# -*- coding: utf-8 -*-
# This file is part of CairoSVG
# Copyright © 2010-2012 Kozea
#
# This library is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option) any
# later version.
#
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with CairoSVG. If not, see <http://www.gnu.org/licenses/>.
"""
SVG tags functions.
"""
from .defs import linear_gradient, marker, pattern, radial_gradient, use
from .image import image
from .path import path
from .shapes import circle, ellipse, line, polygon, polyline, rect
from .svg import svg
from .text import text, text_path, tspan
TAGS = {
"a": tspan,
"circle": circle,
"ellipse": ellipse,
"image": image,
"line": line,
"linearGradient": linear_gradient,
"marker": marker,
"path": path,
"pattern": pattern,
"polyline": polyline,
"polygon": polygon,
"radialGradient": radial_gradient,
"rect": rect,
"svg": svg,
"text": text,
"textPath": text_path,
"tref": use,
"tspan": tspan,
"use": use}
# -*- coding: utf-8 -*-
# This file is part of CairoSVG
# Copyright © 2010-2012 Kozea
#
# This library is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option) any
# later version.
#
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with CairoSVG. If not, see <http://www.gnu.org/licenses/>.
"""
Text drawers.
"""
import cairo
from math import cos, sin
# Python 2/3 management
# pylint: disable=E0611
try:
from itertools import zip_longest
except ImportError:
from itertools import izip_longest as zip_longest
# pylint: enable=E0611
from .colors import color
from .helpers import distance, normalize, point_angle
from .units import size
def path_length(path):
"""Get the length of ``path``."""
total_length = 0
for item in path:
if item[0] == cairo.PATH_MOVE_TO:
old_point = item[1]
elif item[0] == cairo.PATH_LINE_TO:
new_point = item[1]
length = distance(
old_point[0], old_point[1], new_point[0], new_point[1])
total_length += length
old_point = new_point
return total_length
def point_following_path(path, width):
"""Get the point at ``width`` distance on ``path``."""
total_length = 0
for item in path:
if item[0] == cairo.PATH_MOVE_TO:
old_point = item[1]
elif item[0] == cairo.PATH_LINE_TO:
new_point = item[1]
length = distance(
old_point[0], old_point[1], new_point[0], new_point[1])
total_length += length
if total_length < width:
old_point = new_point
else:
length -= total_length - width
angle = point_angle(
old_point[0], old_point[1], new_point[0], new_point[1])
x = cos(angle) * length + old_point[0]
y = sin(angle) * length + old_point[1]
return x, y
def text(surface, node):
"""Draw a text ``node``."""
# Set black as default text color
if not node.get("fill"):
node["fill"] = "#000000"
# TODO: find a better way to manage white spaces in text nodes
node.text = (node.text or "").lstrip()
node.text = node.text.rstrip() + " "
# TODO: manage font variant
font_size = size(surface, node.get("font-size", "12pt"))
font_family = (node.get("font-family") or "sans-serif").split(",")[0]
font_style = getattr(
cairo, ("font_slant_%s" % node.get("font-style")).upper(),
cairo.FONT_SLANT_NORMAL)
font_weight = getattr(
cairo, ("font_weight_%s" % node.get("font-weight")).upper(),
cairo.FONT_WEIGHT_NORMAL)
surface.context.select_font_face(font_family, font_style, font_weight)
surface.context.set_font_size(font_size)
text_extents = surface.context.text_extents(node.text)
x_bearing = text_extents[0]
width = text_extents[2]
x, y = size(surface, node.get("x"), "x"), size(surface, node.get("y"), "y")
text_anchor = node.get("text-anchor")
if text_anchor == "middle":
x -= width / 2. + x_bearing
elif text_anchor == "end":
x -= width + x_bearing
surface.context.move_to(x, y)
surface.context.text_path(node.text)
# Remember the absolute cursor position
surface.cursor_position = surface.context.get_current_point()
def text_path(surface, node):
"""Draw text on a path."""
surface.context.save()
if "url(#" not in (node.get("fill") or ""):
surface.context.set_source_rgba(*color(node.get("fill")))
id_path = node.get("{http://www.w3.org/1999/xlink}href", "")
if not id_path.startswith("#"):
return
id_path = id_path[1:]
if id_path in surface.paths:
path = surface.paths.get(id_path)
else:
return
surface.draw(path, False)
cairo_path = surface.context.copy_path_flat()
surface.context.new_path()
start_offset = size(
surface, node.get("startOffset", 0), path_length(cairo_path))
surface.total_width += start_offset
x, y = point_following_path(cairo_path, surface.total_width)
string = (node.text or "").strip(" \n")
letter_spacing = size(surface, node.get("letter-spacing"))
for letter in string:
surface.total_width += (
surface.context.text_extents(letter)[4] + letter_spacing)
point_on_path = point_following_path(cairo_path, surface.total_width)
if point_on_path:
x2, y2 = point_on_path
else:
continue
angle = point_angle(x, y, x2, y2)
surface.context.save()
surface.context.translate(x, y)
surface.context.rotate(angle)
surface.context.translate(0, size(surface, node.get("y"), "y"))
surface.context.move_to(0, 0)
surface.context.show_text(letter)
surface.context.restore()
x, y = x2, y2
surface.context.restore()
# Remember the relative cursor position
surface.cursor_position = \
size(surface, node.get("x"), "x"), size(surface, node.get("y"), "y")
def tspan(surface, node):
"""Draw a tspan ``node``."""
x, y = [[i] for i in surface.cursor_position]
if "x" in node:
x = [size(surface, i, "x")
for i in normalize(node["x"]).strip().split(" ")]
if "y" in node:
y = [size(surface, i, "y")
for i in normalize(node["y"]).strip().split(" ")]
string = (node.text or "").strip()
if not string:
return
fill = node.get("fill")
positions = list(zip_longest(x, y))
letters_positions = list(zip(positions, string))
letters_positions = letters_positions[:-1] + [
(letters_positions[-1][0], string[len(letters_positions) - 1:])]
for (x, y), letters in letters_positions:
if x == None:
x = surface.cursor_position[0]
if y == None:
y = surface.cursor_position[1]
node["x"] = str(x + size(surface, node.get("dx"), "x"))
node["y"] = str(y + size(surface, node.get("dy"), "y"))
node["fill"] = fill
node.text = letters
if node.parent.tag == "text":
text(surface, node)
else:
node["x"] = str(x + size(surface, node.get("dx"), "x"))
node["y"] = str(y + size(surface, node.get("dy"), "y"))
text_path(surface, node)
if node.parent.children[-1] == node:
surface.total_width = 0
# -*- coding: utf-8 -*-
# This file is part of CairoSVG
# Copyright © 2010-2012 Kozea
#
# This library is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option) any
# later version.
#
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with CairoSVG. If not, see <http://www.gnu.org/licenses/>.
"""
Units functions.
"""
UNITS = {
"mm": 1 / 25.4,
"cm": 1 / 2.54,
"in": 1,
"pt": 1 / 72.,
"pc": 1 / 6.,
"px": None}
def size(surface, string, reference = "xy"):
"""Replace a ``string`` with units by a float value.
If ``reference`` is a float, it is used as reference for percentages. If it
is ``'x'``, we use the viewport width as reference. If it is ``'y'``, we
use the viewport height as reference. If it is ``'xy'``, we use
``(viewport_width ** 2 + viewport_height ** 2) ** .5 / 2 ** .5`` as
reference.
"""
if not string:
return 0
try:
return float(string)
except ValueError:
# Not a float, try something else
pass
if "%" in string:
if reference == "x":
reference = surface.context_width or 0
elif reference == "y":
reference = surface.context_height or 0
elif reference == "xy":
reference = (
(surface.context_width ** 2 + surface.context_height ** 2)
** .5 / 2 ** .5)
return float(string.strip(" %")) * reference / 100
elif "em" in string:
return surface.font_size * float(string.strip(" em"))
elif "ex" in string:
# Assume that 1em == 2ex
return surface.font_size * float(string.strip(" ex")) / 2
for unit, coefficient in UNITS.items():
if unit in string:
number = float(string.strip(" " + unit))
return number * (surface.dpi * coefficient if coefficient else 1)
# Try to return the number at the beginning of the string
return_string = ""
while string and (string[0].isdigit() or string[0] in "+-."):
return_string += string[0]
string = string[1:]
# Unknown size or multiple sizes
return float(return_string) if return_string else 0
......@@ -31,7 +31,7 @@ class Line(object):
'raw','split_raw',
'command','is_move',
'relative','relative_e',
'current_x', 'current_y', 'current_z', 'extruding',
'current_x', 'current_y', 'current_z', 'extruding', 'current_tool',
'gcview_end_vertex')
def __init__(self, l):
......@@ -131,6 +131,7 @@ class GCode(object):
imperial = False
relative = False
relative_e = False
current_tool = 0
filament_length = None
xmin = None
......@@ -155,6 +156,9 @@ class GCode(object):
def __len__(self):
return len(self.line_idxs)
def __iter__(self):
return self.lines.__iter__()
def append(self, command):
command = command.strip()
if not command:
......@@ -175,10 +179,12 @@ class GCode(object):
imperial = self.imperial
relative = self.relative
relative_e = self.relative_e
current_tool = self.current_tool
for line in lines:
if line.is_move:
line.relative = relative
line.relative_e = relative_e
line.current_tool = current_tool
elif line.command == "G20":
imperial = True
elif line.command == "G21":
......@@ -193,11 +199,14 @@ class GCode(object):
relative_e = False
elif line.command == "M83":
relative_e = True
elif line.command[0] == "T":
current_tool = int(line.command[1:])
if line.command[0] == "G":
line.parse_coordinates(imperial)
self.imperial = imperial
self.relative = relative
self.relative_e = relative_e
self.current_tool = current_tool
def _preprocess_extrusion(self, lines = None, cur_e = 0):
if not lines:
......
......@@ -47,6 +47,7 @@ class wxGLPanel(wx.Panel):
self.sizer = wx.BoxSizer(wx.HORIZONTAL)
self.canvas = glcanvas.GLCanvas(self, attribList = attribList)
self.context = glcanvas.GLContext(self.canvas)
self.sizer.Add(self.canvas, 1, wx.EXPAND)
self.SetSizer(self.sizer)
self.sizer.Fit(self)
......@@ -56,33 +57,18 @@ class wxGLPanel(wx.Panel):
self.canvas.Bind(wx.EVT_SIZE, self.processSizeEvent)
self.canvas.Bind(wx.EVT_PAINT, self.processPaintEvent)
#==========================================================================
# Canvas Proxy Methods
#==========================================================================
def GetGLExtents(self):
'''Get the extents of the OpenGL canvas.'''
return self.canvas.GetClientSize()
def SwapBuffers(self):
'''Swap the OpenGL buffers.'''
self.canvas.SwapBuffers()
#==========================================================================
# wxPython Window Handlers
#==========================================================================
def processEraseBackgroundEvent(self, event):
'''Process the erase background event.'''
pass # Do nothing, to avoid flashing on MSWin
def processSizeEvent(self, event):
'''Process the resize event.'''
if self.canvas.GetContext():
if (wx.VERSION > (2,9) and self.canvas.IsShownOnScreen()) or self.canvas.GetContext():
# Make sure the frame is shown before calling SetCurrent.
self.Show()
self.canvas.SetCurrent()
size = self.GetGLExtents()
size = self.GetClientSize()
self.winsize = (size.width, size.height)
self.width, self.height = size.width, size.height
self.canvas.SetCurrent(self.context)
self.OnReshape(size.width, size.height)
self.canvas.Refresh(False)
event.Skip()
......@@ -90,7 +76,7 @@ class wxGLPanel(wx.Panel):
def processPaintEvent(self, event):
'''Process the drawing event.'''
self.canvas.SetCurrent()
self.canvas.SetCurrent(self.context)
if not self.GLinitialized:
self.OnInitGL()
......@@ -140,7 +126,6 @@ class wxGLPanel(wx.Panel):
gluPerspective(60., width / float(height), .1, 1000.)
glMatrixMode(GL_MODELVIEW)
glLoadIdentity()
#pyglet stuff
self.vpmat = (GLint * 4)(0, 0, *list(self.GetClientSize()))
glGetDoublev(GL_PROJECTION_MATRIX, self.pmat)
......@@ -154,7 +139,7 @@ class wxGLPanel(wx.Panel):
self.pygletcontext.set_current()
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
self.draw_objects()
self.SwapBuffers()
self.canvas.SwapBuffers()
#==========================================================================
# To be implemented by a sub class
......
......@@ -19,7 +19,7 @@
import time
import numpy
import math
import sys
import logging
from pyglet.gl import *
from pyglet import gl
......@@ -126,6 +126,41 @@ class Platform(object):
def display(self, mode_2d=False):
glCallList(self.display_list)
class PrintHead(object):
def __init__(self):
self.color = (43. / 255, 0., 175. / 255, 1.0)
self.scale = 5
self.height = 5
self.initialized = False
self.loaded = True
def init(self):
self.display_list = compile_display_list(self.draw)
self.initialized = True
def draw(self):
glPushMatrix()
glBegin(GL_LINES)
glColor4f(*self.color)
for di in [-1, 1]:
for dj in [-1, 1]:
glVertex3f(0, 0, 0)
glVertex3f(self.scale * di, self.scale * dj, self.height)
glEnd()
glPopMatrix()
def display(self, mode_2d=False):
glEnable(GL_LINE_SMOOTH)
orig_linewidth = (GLfloat)()
glGetFloatv(GL_LINE_WIDTH, orig_linewidth)
glLineWidth(3.0)
glCallList(self.display_list)
glLineWidth(orig_linewidth)
glDisable(GL_LINE_SMOOTH)
class Model(object):
"""
Parent class for models that provides common functionality.
......@@ -202,6 +237,9 @@ class GcodeModel(Model):
Model for displaying Gcode data.
"""
color_travel = (0.6, 0.6, 0.6, 0.6)
color_tool0 = (1.0, 0.0, 0.0, 0.6)
color_tool1 = (0.0, 0.0, 1.0, 0.6)
color_printed = (0.2, 0.75, 0, 0.6)
use_vbos = True
......@@ -247,8 +285,8 @@ class GcodeModel(Model):
t_end = time.time()
print >> sys.stderr, _('Initialized 3D visualization in %.2f seconds') % (t_end - t_start)
print >> sys.stderr, _('Vertex count: %d') % len(self.vertices)
logging.log(logging.INFO, _('Initialized 3D visualization in %.2f seconds') % (t_end - t_start))
logging.log(logging.INFO, _('Vertex count: %d') % len(self.vertices))
def copy(self):
copy = GcodeModel()
......@@ -262,28 +300,13 @@ class GcodeModel(Model):
"""
Return the color to use for particular type of movement.
"""
# default movement color is gray
color = [0.6, 0.6, 0.6, 0.6]
"""
extruder_on = (move.flags & Movement.FLAG_EXTRUDER_ON or
move.delta_e > 0)
outer_perimeter = (move.flags & Movement.FLAG_PERIMETER and
move.flags & Movement.FLAG_PERIMETER_OUTER)
if extruder_on and outer_perimeter:
color = [0.0, 0.875, 0.875, 0.6] # cyan
elif extruder_on and move.flags & Movement.FLAG_PERIMETER:
color = [0.0, 1.0, 0.0, 0.6] # green
elif extruder_on and move.flags & Movement.FLAG_LOOP:
color = [1.0, 0.875, 0.0, 0.6] # yellow
elif extruder_on:
color = [1.0, 0.0, 0.0, 0.6] # red
"""
if move.extruding:
color = [1.0, 0.0, 0.0, 0.6] # red
if move.current_tool == 0:
return self.color_tool0
else:
return self.color_tool1
return color
return self.color_travel
# ------------------------------------------------------------------------
# DRAWING
......
......@@ -67,8 +67,10 @@ class RemainingTimeEstimator(object):
self.previous_layers_estimate = 0
self.current_layer_estimate = 0
self.current_layer_lines = 0
self.remaining_layers_estimate = 0
self.gcode = gcode
self.remaining_layers_estimate = sum(layer.duration for layer in gcode.all_layers)
if len(gcode) > 0:
self.update_layer(0, 0)
def update_layer(self, layer, printtime):
self.previous_layers_estimate += self.current_layer_estimate
......@@ -77,9 +79,19 @@ class RemainingTimeEstimator(object):
self.current_layer_estimate = self.gcode.all_layers[layer].duration
self.current_layer_lines = len(self.gcode.all_layers[layer].lines)
self.remaining_layers_estimate -= self.current_layer_estimate
self.last_idx = -1
self.last_estimate = None
def __call__(self, idx):
def __call__(self, idx, printtime):
if not self.current_layer_lines:
return (0, 0)
if idx == self.last_idx:
return self.last_estimate
layer, line = self.gcode.idxs(idx)
layer_progress = (1 - ((line+1) / self.current_layer_lines))
layer_progress = (1 - (float(line+1) / self.current_layer_lines))
remaining = layer_progress * self.current_layer_estimate + self.remaining_layers_estimate
return drift * remaining
estimate = self.drift * remaining
total = estimate + printtime
self.last_idx = idx
self.last_estimate = (estimate, total)
return self.last_estimate
This diff is collapsed.
......@@ -115,6 +115,7 @@ class GLPanel(wx.Panel):
self.pmat = (GLdouble * 16)()
self.mvmat = (GLdouble * 16)()
self.pygletcontext = Context(current_context)
self.pygletcontext.canvas = self
self.pygletcontext.set_current()
self.dist = 1000
self.vpmat = None
......
# This file is part of the Printrun suite.
#
# Printrun is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Printrun is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Printrun. If not, see <http://www.gnu.org/licenses/>.
"""
"""
import wx
def AddEllipticalArc(self, x, y, w, h, startAngle, endAngle, clockwise = False):
""" Draws an arc of an ellipse within bounding rect (x, y, w, h)
from startArc to endArc (in radians, relative to the horizontal line of the eclipse)"""
if True:
import warnings
warnings.warn("elliptical arcs are not supported")
w = w/2.0
h = h/2.0
self.AddArc(x+w, y+h, ((w+h)/2), startAngle, endAngle, clockwise)
return
else:
#implement in terms of AddArc by applying a transformation matrix
#Sigh this can't work, still need to patch wx to allow
#either a) AddPath that's not a closed path or
#b) allow pushing and popping of states on a path, not just on a context
#a) is possible in GDI+, need to investigate other renderers.
#b) is possible in Quartz and Cairo, but not in GDI+. It could
#possibly be simulated by combining the current transform with option a.
mtx = wx.GraphicsRenderer_GetDefaultRenderer().CreateMatrix()
path = wx.GraphicsRenderer_GetDefaultRenderer().CreatePath()
mtx.Translate(x+(w/2.0), y+(h/2.0))
mtx.Scale(w/2.0, y/2.0)
path.AddArc(0, 0, 1, startAngle, endAngle, clockwise)
path.Transform(mtx)
self.AddPath(path)
self.MoveToPoint(path.GetCurrentPoint())
self.CloseSubpath()
if not hasattr(wx.GraphicsPath, "AddEllipticalArc"):
wx.GraphicsPath.AddEllipticalArc = AddEllipticalArc
del AddEllipticalArc
# This file is part of the Printrun suite.
#
# Printrun is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Printrun is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Printrun. If not, see <http://www.gnu.org/licenses/>.
"""
Parsers for specific attributes
"""
import urlparse
from pyparsing import (Literal,
Optional, oneOf, Group, StringEnd, Combine, Word, alphas, hexnums,
CaselessLiteral, SkipTo
)
from css.colour import colourValue
import string
##Paint values
none = CaselessLiteral("none").setParseAction(lambda t: ["NONE", ()])
currentColor = CaselessLiteral("currentColor").setParseAction(lambda t: ["CURRENTCOLOR", ()])
def parsePossibleURL(t):
possibleURL, fallback = t[0]
return [urlparse.urlsplit(possibleURL), fallback]
#Normal color declaration
colorDeclaration = none | currentColor | colourValue
urlEnd = (
Literal(")").suppress() +
Optional(Group(colorDeclaration), default = ()) +
StringEnd()
)
url = (
CaselessLiteral("URL")
+
Literal("(").suppress()+
Group(SkipTo(urlEnd, include = True).setParseAction(parsePossibleURL))
)
#paint value will parse into a (type, details) tuple.
#For none and currentColor, the details tuple will be the empty tuple
#for CSS color declarations, it will be (type, (R, G, B))
#for URLs, it will be ("URL", ((url tuple), fallback))
#The url tuple will be as returned by urlparse.urlsplit, and can be
#an empty tuple if the parser has an error
#The fallback will be another (type, details) tuple as a parsed
#colorDeclaration, but may be the empty tuple if it is not present
paintValue = url | colorDeclaration
# This file is part of the Printrun suite.
#
# Printrun is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Printrun is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Printrun. If not, see <http://www.gnu.org/licenses/>.
from __future__ import absolute_import
from .transform import transformList
from .inline import inlineStyle
# This file is part of the Printrun suite.
#
# Printrun is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Printrun is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Printrun. If not, see <http://www.gnu.org/licenses/>.
""" CSS at-rules"""
from pyparsing import Literal, Combine
from .identifier import identifier
atkeyword = Combine(Literal("@") + identifier)
# This file is part of the Printrun suite.
#
# Printrun is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Printrun is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Printrun. If not, see <http://www.gnu.org/licenses/>.
"""
CSS blocks
"""
from pyparsing import nestedExpr
block = nestedExpr(opener="{", closer="}")
This diff is collapsed.
# This file is part of the Printrun suite.
#
# Printrun is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Printrun is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Printrun. If not, see <http://www.gnu.org/licenses/>.
""" Parse CSS identifiers. More complicated than it sounds"""
from pyparsing import Word, Literal, Regex, Combine, Optional, White, oneOf, ZeroOrMore
import string
import re
class White(White):
""" Customize whitespace to match the CSS spec values"""
def __init__(self, ws=" \t\r\n\f", min=1, max=0, exact=0):
super(White, self).__init__(ws, min, max, exact)
escaped = (
Literal("\\").suppress() +
#chr(20)-chr(126) + chr(128)-unichr(sys.maxunicode)
Regex(u"[\u0020-\u007e\u0080-\uffff]", re.IGNORECASE)
)
def convertToUnicode(t):
return unichr(int(t[0], 16))
hex_unicode = (
Literal("\\").suppress() +
Regex("[0-9a-f]{1,6}", re.IGNORECASE) +
Optional(White(exact=1)).suppress()
).setParseAction(convertToUnicode)
escape = hex_unicode | escaped
#any unicode literal outside the 0-127 ascii range
nonascii = Regex(u"[^\u0000-\u007f]")
#single character for starting an identifier.
nmstart = Regex(u"[A-Z]", re.IGNORECASE) | nonascii | escape
nmchar = Regex(u"[0-9A-Z-]", re.IGNORECASE) | nonascii | escape
identifier = Combine(nmstart + ZeroOrMore(nmchar))
# This file is part of the Printrun suite.
#
# Printrun is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Printrun is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Printrun. If not, see <http://www.gnu.org/licenses/>.
""" Parser for inline CSS in style attributes """
def inlineStyle(styleString):
if not styleString:
return {}
styles = styleString.split(";")
rv = dict(style.split(":") for style in styles if len(style) != 0)
return rv
# This file is part of the Printrun suite.
#
# Printrun is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Printrun is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Printrun. If not, see <http://www.gnu.org/licenses/>.
"""
Parsing for CSS and CSS-style values, such as transform and filter attributes.
"""
from pyparsing import (Literal, Word, CaselessLiteral,
Optional, Combine, Forward, ZeroOrMore, nums, oneOf, Group, delimitedList)
#some shared definitions from pathdata
from ..pathdata import number, maybeComma
paren = Literal("(").suppress()
cparen = Literal(")").suppress()
def Parenthised(exp):
return Group(paren + exp + cparen)
skewY = Literal("skewY") + Parenthised(number)
skewX = Literal("skewX") + Parenthised(number)
rotate = Literal("rotate") + Parenthised(
number + Optional(maybeComma + number + maybeComma + number)
)
scale = Literal("scale") + Parenthised(
number + Optional(maybeComma + number)
)
translate = Literal("translate") + Parenthised(
number + Optional(maybeComma + number)
)
matrix = Literal("matrix") + Parenthised(
#there's got to be a better way to write this
number + maybeComma +
number + maybeComma +
number + maybeComma +
number + maybeComma +
number + maybeComma +
number
)
transform = (skewY | skewX | rotate | scale | translate | matrix)
transformList = delimitedList(Group(transform), delim=maybeComma)
if __name__ == '__main__':
from tests.test_css import *
unittest.main()
# This file is part of the Printrun suite.
#
# Printrun is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Printrun is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Printrun. If not, see <http://www.gnu.org/licenses/>.
"""
Parser for various kinds of CSS values as per CSS2 spec section 4.3
"""
from pyparsing import Word, Combine, Optional, Literal, oneOf, CaselessLiteral, StringEnd
def asInt(s,l,t):
return int(t[0])
def asFloat(s,l,t):
return float(t[0])
def asFloatOrInt(s,l,t):
""" Return an int if possible, otherwise a float"""
v = t[0]
try:
return int(v)
except ValueError:
return float(v)
integer = Word("0123456789").setParseAction(asInt)
number = Combine(
Optional(Word("0123456789")) + Literal(".") + Word("01234567890")
| integer
)
number.setName('number')
sign = oneOf("+ -")
signedNumber = Combine(Optional(sign) + number).setParseAction(asFloat)
lengthValue = Combine(Optional(sign) + number).setParseAction(asFloatOrInt)
lengthValue.setName('lengthValue')
#TODO: The physical units like in, mm
lengthUnit = oneOf(['em', 'ex', 'px', 'pt', '%'], caseless=True)
#the spec says that the unit is only optional for a 0 length, but
#there are just too many places where a default is permitted.
#TODO: Maybe should use a ctor like optional to let clients declare it?
length = lengthValue + Optional(lengthUnit, default=None) + StringEnd()
length.leaveWhitespace()
#set the parse action aftward so it doesn't "infect" the parsers that build on it
number.setParseAction(asFloat)
This diff is collapsed.
# This file is part of the Printrun suite.
#
# Printrun is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Printrun is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Printrun. If not, see <http://www.gnu.org/licenses/>.
"""
SVG path data parser
Usage:
steps = svg.parseString(pathdata)
for command, arguments in steps:
pass
"""
from pyparsing import (ParserElement, Literal, Word, CaselessLiteral,
Optional, Combine, Forward, ZeroOrMore, nums, oneOf, Group, ParseException, OneOrMore)
#ParserElement.enablePackrat()
def Command(char):
""" Case insensitive but case preserving"""
return CaselessPreservingLiteral(char)
def Arguments(token):
return Group(token)
class CaselessPreservingLiteral(CaselessLiteral):
""" Like CaselessLiteral, but returns the match as found
instead of as defined.
"""
def __init__( self, matchString ):
super(CaselessPreservingLiteral, self).__init__( matchString.upper() )
self.name = "'%s'" % matchString
self.errmsg = "Expected " + self.name
self.myException.msg = self.errmsg
def parseImpl( self, instring, loc, doActions = True ):
test = instring[ loc:loc+self.matchLen ]
if test.upper() == self.match:
return loc+self.matchLen, test
#~ raise ParseException( instring, loc, self.errmsg )
exc = self.myException
exc.loc = loc
exc.pstr = instring
raise exc
def Sequence(token):
""" A sequence of the token"""
return OneOrMore(token+maybeComma)
digit_sequence = Word(nums)
sign = oneOf("+ -")
def convertToFloat(s, loc, toks):
try:
return float(toks[0])
except:
raise ParseException(loc, "invalid float format %s"%toks[0])
exponent = CaselessLiteral("e")+Optional(sign)+Word(nums)
#note that almost all these fields are optional,
#and this can match almost anything. We rely on Pythons built-in
#float() function to clear out invalid values - loosely matching like this
#speeds up parsing quite a lot
floatingPointConstant = Combine(
Optional(sign) +
Optional(Word(nums)) +
Optional(Literal(".") + Optional(Word(nums)))+
Optional(exponent)
)
floatingPointConstant.setParseAction(convertToFloat)
number = floatingPointConstant
#same as FP constant but don't allow a - sign
nonnegativeNumber = Combine(
Optional(Word(nums)) +
Optional(Literal(".") + Optional(Word(nums)))+
Optional(exponent)
)
nonnegativeNumber.setParseAction(convertToFloat)
coordinate = number
#comma or whitespace can seperate values all over the place in SVG
maybeComma = Optional(Literal(',')).suppress()
coordinateSequence = Sequence(coordinate)
coordinatePair = (coordinate + maybeComma + coordinate).setParseAction(lambda t: tuple(t))
coordinatePairSequence = Sequence(coordinatePair)
coordinatePairPair = coordinatePair + maybeComma + coordinatePair
coordinatePairPairSequence = Sequence(Group(coordinatePairPair))
coordinatePairTriple = coordinatePair + maybeComma + coordinatePair + maybeComma + coordinatePair
coordinatePairTripleSequence = Sequence(Group(coordinatePairTriple))
#commands
lineTo = Group(Command("L") + Arguments(coordinatePairSequence))
moveTo = Group(Command("M") + Arguments(coordinatePairSequence))
closePath = Group(Command("Z")).setParseAction(lambda t: ('Z', (None,)))
flag = oneOf("1 0").setParseAction(lambda t: bool(int((t[0]))))
arcRadius = (
nonnegativeNumber + maybeComma + #rx
nonnegativeNumber #ry
).setParseAction(lambda t: tuple(t))
arcFlags = (flag + maybeComma + flag).setParseAction(lambda t: tuple(t))
ellipticalArcArgument = Group(
arcRadius + maybeComma + #rx, ry
number + maybeComma +#rotation
arcFlags + #large-arc-flag, sweep-flag
coordinatePair #(x, y)
)
ellipticalArc = Group(Command("A") + Arguments(Sequence(ellipticalArcArgument)))
smoothQuadraticBezierCurveto = Group(Command("T") + Arguments(coordinatePairSequence))
quadraticBezierCurveto = Group(Command("Q") + Arguments(coordinatePairPairSequence))
smoothCurve = Group(Command("S") + Arguments(coordinatePairPairSequence))
curve = Group(Command("C") + Arguments(coordinatePairTripleSequence))
horizontalLine = Group(Command("H") + Arguments(coordinateSequence))
verticalLine = Group(Command("V") + Arguments(coordinateSequence))
drawToCommand = (
lineTo | moveTo | closePath | ellipticalArc | smoothQuadraticBezierCurveto |
quadraticBezierCurveto | smoothCurve | curve | horizontalLine | verticalLine
)
#~ number.debug = True
moveToDrawToCommands = moveTo + ZeroOrMore(drawToCommand)
svg = ZeroOrMore(moveToDrawToCommands)
svg.keepTabs = True
def profile():
import cProfile
p = cProfile.Profile()
p.enable()
ptest()
ptest()
ptest()
p.disable()
p.print_stats()
bpath = """M204.33 139.83 C196.33 133.33 206.68 132.82 206.58 132.58 C192.33 97.08 169.35
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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"""
def ptest():
svg.parseString(bpath)
if __name__ == '__main__':
#~ from tests.test_pathdata import *
#~ unittest.main()
profile()
......@@ -17,7 +17,7 @@
import cmd, sys
import glob, os, time, datetime
import sys, subprocess
import sys, subprocess, traceback
import math, codecs
import shlex
from math import sqrt
......@@ -205,6 +205,21 @@ class Settings(object):
self._add(StringSetting("sliceoptscommand", "python skeinforge/skeinforge_application/skeinforge.py", _("Slicer options command"), _("Slice settings command\n default:\n python skeinforge/skeinforge_application/skeinforge.py")))
self._add(StringSetting("final_command", "", _("Final command"), _("Executable to run when the print is finished")))
self._add(HiddenSetting("project_offset_x", 0.0))
self._add(HiddenSetting("project_offset_y", 0.0))
self._add(HiddenSetting("project_interval", 2.0))
self._add(HiddenSetting("project_pause", 2.5))
self._add(HiddenSetting("project_scale", 1.0))
self._add(HiddenSetting("project_x", 1024.0))
self._add(HiddenSetting("project_y", 768.0))
self._add(HiddenSetting("project_projected_x", 150.0))
self._add(HiddenSetting("project_direction", "Top Down"))
self._add(HiddenSetting("project_overshoot", 3.0))
self._add(HiddenSetting("project_z_axis_rate", 200))
self._add(HiddenSetting("project_layer", 0.1))
self._add(HiddenSetting("project_prelift_gcode", ""))
self._add(HiddenSetting("project_postlift_gcode", ""))
_settings = []
def __setattr__(self, name, value):
if name.startswith("_"):
......@@ -952,7 +967,7 @@ class pronsole(cmd.Cmd):
self.tempreadings = l
self.status.update_tempreading(l)
tstring = l.rstrip()
if(tstring!="ok" and not tstring.startswith("ok T") and not tstring.startswith("T:") and not self.listing and not self.monitoring):
if tstring != "ok" and not self.listing and not self.monitoring:
if tstring[:5] == "echo:":
tstring = tstring[5:].lstrip()
if self.silent == False: print "\r" + tstring.ljust(15)
......@@ -1210,7 +1225,6 @@ class pronsole(cmd.Cmd):
print "Setting bed temp to 0"
self.p.send_now("M140 S0.0")
self.log("Disconnecting from printer...")
print self.p.printing
if self.p.printing:
print "Are you sure you want to exit while printing?"
print "(this will terminate the print)."
......@@ -1434,6 +1448,9 @@ if __name__ == "__main__":
interp.parse_cmdline(sys.argv[1:])
try:
interp.cmdloop()
except SystemExit:
interp.p.disconnect()
except:
print _("Caught an exception, exiting:")
traceback.print_exc()
interp.p.disconnect()
#raise
......@@ -49,11 +49,11 @@ import pronsole
from pronsole import dosify, wxSetting, HiddenSetting, StringSetting, SpinSetting, FloatSpinSetting, BooleanSetting
from printrun import gcoder
def parse_temperature_report(report, key):
if key in report:
return float(filter(lambda x: x.startswith(key), report.split())[0].split(":")[1].split("/")[0])
else:
return -1.0
tempreport_exp = re.compile("([TB]\d*):([-+]?\d*\.?\d*)(?: \/)?([-+]?\d*\.?\d*)")
def parse_temperature_report(report):
matches = tempreport_exp.findall(report)
return dict((m[0], (m[1], m[2])) for m in matches)
def format_time(timestamp):
return datetime.datetime.fromtimestamp(timestamp).strftime("%H:%M:%S")
......@@ -95,6 +95,8 @@ def parse_build_dimensions(bdim):
bdl_float = [float(value) if value else defaults[i] for i, value in enumerate(bdl)]
if len(bdl_float) < len(defaults):
bdl_float += [defaults[i] for i in range(len(bdl_float), len(defaults))]
for i in range(3): # Check for nonpositive dimensions for build volume
if bdl_float[i] <= 0: bdl_float[i] = 1
return bdl_float
class BuildDimensionsSetting(wxSetting):
......@@ -532,10 +534,7 @@ class PronterWindow(MainWindow, pronsole.pronsole):
def project(self,event):
from printrun import projectlayer
if self.p.online:
projectlayer.setframe(self,self.p).Show()
else:
print _("Printer is not online.")
projectlayer.SettingsFrame(self, self.p).Show()
def popmenu(self):
self.menustrip = wx.MenuBar()
......@@ -578,7 +577,7 @@ class PronterWindow(MainWindow, pronsole.pronsole):
def do_editgcode(self, e = None):
if self.filename is not None:
MacroEditor(self.filename, "\n".join([line.raw for line in self.fgcode]), self.doneediting, 1)
MacroEditor(self.filename, [line.raw for line in self.fgcode], self.doneediting, 1)
def new_macro(self, e = None):
dialog = wx.Dialog(self, -1, _("Enter macro name"), size = (260, 85))
......@@ -859,10 +858,10 @@ class PronterWindow(MainWindow, pronsole.pronsole):
def cbutton_remove(self, e, button):
n = button.custombutton
self.custombuttons[n]=None
self.cbutton_save(n, None)
#while len(self.custombuttons) and self.custombuttons[-1] is None:
# del self.custombuttons[-1]
del self.custombuttons[n]
for i in range(n, len(self.custombuttons)):
self.cbutton_save(i, self.custombuttons[i])
wx.CallAfter(self.cbuttons_reload)
def cbutton_order(self, e, button, dir):
......@@ -1136,10 +1135,18 @@ class PronterWindow(MainWindow, pronsole.pronsole):
def update_tempdisplay(self):
try:
hotend_temp = parse_temperature_report(self.tempreport, "T:")
# FIXME : we don't use setpoints here, we should probably exploit them
temps = parse_temperature_report(self.tempreport)
if "T0" in temps:
hotend_temp = float(temps["T0"][0])
else:
hotend_temp = float(temps["T"][0]) if "T" in temps else -1.0
wx.CallAfter(self.graph.SetExtruder0Temperature, hotend_temp)
if self.display_gauges: wx.CallAfter(self.hottgauge.SetValue, hotend_temp)
bed_temp = parse_temperature_report(self.tempreport, "B:")
if "T1" in temps:
hotend_temp = float(temps["T1"][0])
wx.CallAfter(self.graph.SetExtruder1Temperature, hotend_temp)
bed_temp = float(temps["B"][0]) if "B" in temps else -1.0
wx.CallAfter(self.graph.SetBedTemperature, bed_temp)
if self.display_gauges: wx.CallAfter(self.bedtgauge.SetValue, bed_temp)
except:
......@@ -1181,14 +1188,13 @@ class PronterWindow(MainWindow, pronsole.pronsole):
string += _(" Line# %d of %d lines |" ) % (self.p.queueindex, len(self.p.mainqueue))
if self.p.queueindex > 0:
secondselapsed = int(time.time() - self.starttime + self.extra_print_time)
secondsremain = self.compute_eta(self.p.queueindex)
secondsestimate = secondselapsed + secondsremain
secondsremain, secondsestimate = self.compute_eta(self.p.queueindex, secondselapsed)
string += _(" Est: %s of %s remaining | ") % (format_duration(secondsremain),
format_duration(secondsestimate))
string += _(" Z: %.3f mm") % self.curlayer
wx.CallAfter(self.status.SetStatusText, string)
wx.CallAfter(self.gviz.Refresh)
if(self.monitor and self.p.online):
if self.monitor and self.p.online:
if self.sdprinting:
self.p.send_now("M27")
if not hasattr(self, "auto_monitor_pattern"):
......
......@@ -145,7 +145,7 @@ setup (
license = "GPLv3",
data_files = data_files,
packages = ["printrun", "printrun.svg"],
scripts = ["pronsole.py", "pronterface.py", "plater.py", "printcore.py"],
scripts = ["pronsole.py", "pronterface.py", "plater.py", "printcore.py", "pronserve.py"],
cmdclass = {"uninstall" : uninstall,
"install" : install,
"install_data" : install_data}
......
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg" onload='init()' width="150mm" height="150mm" version="1.1">
<script type='text/ecmascript'>
<![CDATA[
function getParameterByName(name)
{
name = name.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]");
var regexS = "[\\?&]" + name + "=([^&#]*)";
var regex = new RegExp(regexS);
var results = regex.exec(window.location.search);
if(results == null)
return "";
else
return decodeURIComponent(results[1].replace(/\+/g, " "));
}
function init() {
var width = getParameterByName('width') || 150;
var height = getParameterByName('height') || 150;
var gridheight = getParameterByName('gridheight') || 10;
var gridwidth = getParameterByName('gridwidth') || 10;
var lineheight = getParameterByName('lineheight') || 0.5;
var linewidth = getParameterByName('linewidth') || 0.5;
var lineblockheight = (gridheight/2)-lineheight;
var lineblockwidth = (gridwidth/2)-linewidth;
console.log(lineblockheight,lineblockwidth)
document.getElementsByTagName('svg')[0].setAttribute('width',width+'mm');
document.getElementsByTagName('svg')[0].setAttribute('height',height+'mm');
document.getElementsByTagName('pattern')[0].setAttribute('height',gridheight+'mm');
document.getElementsByTagName('pattern')[0].setAttribute('width',gridwidth+'mm');
document.getElementsByClassName('background')[0].setAttribute('height',gridheight+'mm');
document.getElementsByClassName('background')[0].setAttribute('width',gridwidth+'mm');
var blocks = document.getElementsByClassName('block');
for (var i in blocks){
if (blocks[i] instanceof SVGRectElement){
blocks[i].setAttribute('height', lineblockheight+'mm');
blocks[i].setAttribute('width', lineblockwidth+'mm');
}
}
document.getElementsByClassName('topright')[0].setAttribute('x',lineblockwidth+'mm');
document.getElementsByClassName('bottomleft')[0].setAttribute('y',lineblockheight+'mm');
document.getElementsByClassName('bottomright')[0].setAttribute('x',lineblockwidth+'mm');
document.getElementsByClassName('bottomright')[0].setAttribute('y',lineblockheight+'mm');
}
]]>
</script>
<defs>
<pattern id="grd" patternUnits="userSpaceOnUse" width="10mm" height="10mm">
<rect class="background" width="10mm" height="10mm" fill="red"/>
<rect class="block topleft" width="4.9mm" height="4.9mm"/>
<rect class="block topright" width="4.9mm" height="4.9mm" x="4.9mm"/>
<rect class="block bottomleft" width="4.9mm" height="4.9mm" y="4.9mm"/>
<rect class="block bottomright" width="4.9mm" height="4.9mm" x="4.9mm" y="4.9mm"/>
</pattern>
</defs>
<rect height="100%" width="100%" fill="url(#grd)"/>
</svg>
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment