Commit b5146672 authored by kliment's avatar kliment

Merge pull request #225 from garyhodgson/experimental

initial hack to accept Slic3r SVG
parents 0603d9c7 5dd35d48
...@@ -15,41 +15,62 @@ ...@@ -15,41 +15,62 @@
import xml.etree.ElementTree import xml.etree.ElementTree
import wx import wx
import time import svg.document as wxpsvgdocument
def parsesvg(name): def parsesvg(name):
et= xml.etree.ElementTree.ElementTree(file=name) et= xml.etree.ElementTree.ElementTree(file=name)
#xml.etree.ElementTree.dump(et)
slicer = 'Slic3r' if et.getroot().find('{http://www.w3.org/2000/svg}metadata') == None else 'Skeinforge'
zlast=0 zlast=0
zdiff=0 zdiff=0
ol=[] ol=[]
for i in et.findall("{http://www.w3.org/2000/svg}g")[0].findall("{http://www.w3.org/2000/svg}g"): if (slicer == 'Slic3r'):
z=float(i.get('id').split("z:")[-1]) height = et.getroot().get('height')
zdiff=z-zlast width = et.getroot().get('width')
zlast=z
path=i.find('{http://www.w3.org/2000/svg}path') for i in et.findall("{http://www.w3.org/2000/svg}g"):
ol+=[(path.get("d").split("z"))[:-1]] z=float(i.get('{http://slic3r.org/namespaces/slic3r}z'))
return ol,zdiff zdiff=z-zlast
zlast=z
svgSnippet = xml.etree.ElementTree.Element('{http://www.w3.org/2000/svg}svg')
svgSnippet.set('height', height+'mm')
svgSnippet.set('width', width+'mm')
svgSnippet.set('viewBox', '0 0 '+height+' '+width)
svgSnippet.append(i)
ol+=[svgSnippet]
else :
for i in et.findall("{http://www.w3.org/2000/svg}g")[0].findall("{http://www.w3.org/2000/svg}g"):
z=float(i.get('id').split("z:")[-1])
zdiff=z-zlast
zlast=z
path=i.find('{http://www.w3.org/2000/svg}path')
ol+=[(path.get("d").split("z"))[:-1]]
return ol,zdiff,slicer
class dispframe(wx.Frame): class dispframe(wx.Frame):
def __init__(self, parent, title, res=(1600,1200),printer=None): def __init__(self, parent, title, res=(800,600),printer=None):
wx.Frame.__init__(self, parent=parent, title=title) wx.Frame.__init__(self, parent=parent, title=title)
self.p=printer self.p=printer
self.pic=wx.StaticBitmap(self) self.pic=wx.StaticBitmap(self)
self.bitmap=wx.EmptyBitmap(*res) self.bitmap=wx.EmptyBitmap(*res)
self.bbitmap=wx.EmptyBitmap(*res) self.bbitmap=wx.EmptyBitmap(*res)
self.slicer='Skeinforge'
dc=wx.MemoryDC() dc=wx.MemoryDC()
dc.SelectObject(self.bbitmap) dc.SelectObject(self.bbitmap)
dc.SetBackground(wx.Brush("black")) dc.SetBackground(wx.Brush("black"))
dc.Clear() dc.Clear()
dc.SelectObject(wx.NullBitmap) dc.SelectObject(wx.NullBitmap)
self.SetBackgroundColour("black") self.SetBackgroundColour("black")
self.pic.Hide() self.pic.Hide()
self.pen=wx.Pen("white") self.pen=wx.Pen("white")
self.brush=wx.Brush("white") self.brush=wx.Brush("white")
self.SetDoubleBuffered(True) self.SetDoubleBuffered(True)
self.Show() self.Show()
def drawlayer(self,svg): def drawlayer(self,svg):
try: try:
dc=wx.MemoryDC() dc=wx.MemoryDC()
...@@ -58,18 +79,22 @@ class dispframe(wx.Frame): ...@@ -58,18 +79,22 @@ class dispframe(wx.Frame):
dc.Clear() dc.Clear()
dc.SetPen(self.pen) dc.SetPen(self.pen)
dc.SetBrush(self.brush) dc.SetBrush(self.brush)
for i in svg:
#print i if self.slicer == 'Skeinforge':
points=[wx.Point(*map(lambda x:int(round(float(x)*self.scale)),j.strip().split())) for j in i.strip().split("M")[1].split("L")] for i in svg:
dc.DrawPolygon(points,self.size[0]/2,self.size[1]/2) #print i
points=[wx.Point(*map(lambda x:int(round(float(x)*self.scale)),j.strip().split())) for j in i.strip().split("M")[1].split("L")]
dc.DrawPolygon(points,self.size[0]/2,self.size[1]/2)
dc.SelectObject(wx.NullBitmap) else:
gc = wx.GraphicsContext_Create(dc)
gc.Translate(*self.offset)
gc.Scale(self.scale, self.scale)
wxpsvgdocument.SVGDocument(svg).render(gc)
self.pic.SetBitmap(self.bitmap) self.pic.SetBitmap(self.bitmap)
self.pic.Show() self.pic.Show()
self.Refresh() self.Refresh()
#self.pic.SetBitmap(self.bitmap)
except: except:
raise raise
...@@ -79,19 +104,18 @@ class dispframe(wx.Frame): ...@@ -79,19 +104,18 @@ class dispframe(wx.Frame):
self.drawlayer(image) self.drawlayer(image)
self.pic.Show() self.pic.Show()
self.Refresh() self.Refresh()
# time.sleep(self.interval)
#self.pic.Hide()
self.Refresh() self.Refresh()
if self.p!=None and self.p.online: if self.p != None and self.p.online:
self.p.send_now("G91") self.p.send_now("G91")
self.p.send_now("G1 Z%f F300"%(self.thickness,)) self.p.send_now("G1 Z%f F300"%(self.thickness,))
self.p.send_now("G90") self.p.send_now("G90")
def nextimg(self,event): def nextimg(self,event):
#print "b"
if self.index<len(self.layers): if self.index<len(self.layers):
i=self.index i=self.index
#print self.layers[i]
print i print i
wx.CallAfter(self.showimgdelay,self.layers[i]) wx.CallAfter(self.showimgdelay,self.layers[i])
wx.FutureCall(1000*self.interval,self.pic.Hide) wx.FutureCall(1000*self.interval,self.pic.Hide)
...@@ -104,7 +128,7 @@ class dispframe(wx.Frame): ...@@ -104,7 +128,7 @@ class dispframe(wx.Frame):
wx.CallAfter(self.timer.Stop) wx.CallAfter(self.timer.Stop)
def present(self,layers,interval=0.5,pause=0.2,thickness=0.4,scale=20,size=(800,600)): def present(self,layers,interval=0.5,pause=0.2,thickness=0.4,scale=20,size=(800,600),offset=(0,0)):
wx.CallAfter(self.pic.Hide) wx.CallAfter(self.pic.Hide)
wx.CallAfter(self.Refresh) wx.CallAfter(self.Refresh)
self.layers=layers self.layers=layers
...@@ -113,12 +137,11 @@ class dispframe(wx.Frame): ...@@ -113,12 +137,11 @@ class dispframe(wx.Frame):
self.index=0 self.index=0
self.size=size self.size=size
self.interval=interval self.interval=interval
self.offset=offset
self.timer=wx.Timer(self,1) self.timer=wx.Timer(self,1)
self.timer.Bind(wx.EVT_TIMER,self.nextimg) self.timer.Bind(wx.EVT_TIMER,self.nextimg)
self.Bind(wx.EVT_TIMER,self.nextimg) self.Bind(wx.EVT_TIMER,self.nextimg)
self.timer.Start(1000*interval+1000*pause) self.timer.Start(1000*interval+1000*pause)
#print "x"
class setframe(wx.Frame): class setframe(wx.Frame):
...@@ -129,29 +152,43 @@ class setframe(wx.Frame): ...@@ -129,29 +152,43 @@ class setframe(wx.Frame):
self.panel.SetBackgroundColour("orange") self.panel.SetBackgroundColour("orange")
self.bload=wx.Button(self.panel,-1,"Load",pos=(0,0)) self.bload=wx.Button(self.panel,-1,"Load",pos=(0,0))
self.bload.Bind(wx.EVT_BUTTON,self.loadfile) self.bload.Bind(wx.EVT_BUTTON,self.loadfile)
wx.StaticText(self.panel,-1,"Layer:",pos=(0,30)) wx.StaticText(self.panel,-1,"Layer:",pos=(0,30))
wx.StaticText(self.panel,-1,"mm",pos=(130,30)) wx.StaticText(self.panel,-1,"mm",pos=(130,30))
self.thickness=wx.TextCtrl(self.panel,-1,"0.5",pos=(50,30)) self.thickness=wx.TextCtrl(self.panel,-1,"0.5",pos=(50,30))
wx.StaticText(self.panel,-1,"Exposure:",pos=(0,60)) wx.StaticText(self.panel,-1,"Exposure:",pos=(0,60))
wx.StaticText(self.panel,-1,"s",pos=(130,60)) wx.StaticText(self.panel,-1,"s",pos=(130,60))
self.interval=wx.TextCtrl(self.panel,-1,"0.5",pos=(50,60)) self.interval=wx.TextCtrl(self.panel,-1,"0.5",pos=(50,60))
wx.StaticText(self.panel,-1,"Blank:",pos=(0,90)) wx.StaticText(self.panel,-1,"Blank:",pos=(0,90))
wx.StaticText(self.panel,-1,"s",pos=(130,90)) wx.StaticText(self.panel,-1,"s",pos=(130,90))
self.delay=wx.TextCtrl(self.panel,-1,"0.5",pos=(50,90)) self.delay=wx.TextCtrl(self.panel,-1,"0.5",pos=(50,90))
wx.StaticText(self.panel,-1,"Scale:",pos=(0,120)) wx.StaticText(self.panel,-1,"Scale:",pos=(0,120))
wx.StaticText(self.panel,-1,"x",pos=(130,120)) wx.StaticText(self.panel,-1,"x",pos=(130,120))
self.scale=wx.TextCtrl(self.panel,-1,"5",pos=(50,120)) self.scale=wx.TextCtrl(self.panel,-1,"5",pos=(50,120))
wx.StaticText(self.panel,-1,"X:",pos=(160,30)) wx.StaticText(self.panel,-1,"X:",pos=(160,30))
self.X=wx.TextCtrl(self.panel,-1,"1024",pos=(180,30)) self.X=wx.TextCtrl(self.panel,-1,"1024",pos=(210,30))
wx.StaticText(self.panel,-1,"Y:",pos=(160,60)) wx.StaticText(self.panel,-1,"Y:",pos=(160,60))
self.Y=wx.TextCtrl(self.panel,-1,"768",pos=(180,60)) self.Y=wx.TextCtrl(self.panel,-1,"768",pos=(210,60))
wx.StaticText(self.panel,-1,"OffsetX:",pos=(160,90))
self.offsetX=wx.TextCtrl(self.panel,-1,"50",pos=(210,90))
wx.StaticText(self.panel,-1,"OffsetY:",pos=(160,120))
self.offsetY=wx.TextCtrl(self.panel,-1,"50",pos=(210,120))
self.bload=wx.Button(self.panel,-1,"Present",pos=(0,150)) self.bload=wx.Button(self.panel,-1,"Present",pos=(0,150))
self.bload.Bind(wx.EVT_BUTTON,self.startdisplay) self.bload.Bind(wx.EVT_BUTTON,self.startdisplay)
self.Show() self.Show()
def loadfile(self,event): def loadfile(self,event):
dlg=wx.FileDialog(self,("Open file to print"),style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST) dlg=wx.FileDialog(self,("Open file to print"),style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST)
dlg.SetWildcard(("Skeinforge svg files (;*.svg;*.SVG;)")) dlg.SetWildcard(("Slic3r or Skeinforge svg files (;*.svg;*.SVG;)"))
if(dlg.ShowModal() == wx.ID_OK): if(dlg.ShowModal() == wx.ID_OK):
name=dlg.GetPath() name=dlg.GetPath()
import os import os
...@@ -162,6 +199,7 @@ class setframe(wx.Frame): ...@@ -162,6 +199,7 @@ class setframe(wx.Frame):
print "Layer thickness detected:",layers[1], "mm" print "Layer thickness detected:",layers[1], "mm"
print len(layers[0]), "layers found, total height", layers[1]*len(layers[0]), "mm" print len(layers[0]), "layers found, total height", layers[1]*len(layers[0]), "mm"
self.thickness.SetValue(str(layers[1])) self.thickness.SetValue(str(layers[1]))
self.f.slicer = layers[2]
self.layers=layers self.layers=layers
dlg.Destroy() dlg.Destroy()
...@@ -169,11 +207,15 @@ class setframe(wx.Frame): ...@@ -169,11 +207,15 @@ class setframe(wx.Frame):
self.f.Raise() self.f.Raise()
self.f.ShowFullScreen(1) self.f.ShowFullScreen(1)
l=self.layers[0][:] l=self.layers[0][:]
#l=list(reversed(l)) self.f.present(l,
self.f.present(l,thickness=float(self.thickness.GetValue()),interval=float(self.interval.GetValue()),scale=float(self.scale.GetValue()),pause=float(self.delay.GetValue()), size=(float(self.X.GetValue()),float(self.Y.GetValue()))) thickness=float(self.thickness.GetValue()),
interval=float(self.interval.GetValue()),
scale=float(self.scale.GetValue()),
pause=float(self.delay.GetValue()),
size=(float(self.X.GetValue()),float(self.Y.GetValue())),
offset=(float(self.offsetX.GetValue()),float(self.offsetY.GetValue())))
if __name__=="__main__": if __name__=="__main__":
a=wx.App() a=wx.App()
setframe(None).Show() setframe(None).Show()
a.MainLoop() a.MainLoop()
"""
"""
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
\ No newline at end of file
"""
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
from __future__ import absolute_import
from .transform import transformList
from .inline import inlineStyle
""" CSS at-rules"""
from pyparsing import Literal, Combine
from .identifier import identifier
atkeyword = Combine(Literal("@") + identifier)
\ No newline at end of file
"""
CSS blocks
"""
from pyparsing import nestedExpr
block = nestedExpr(opener="{", closer="}")
"""
Parsing for CSS colour values.
Supported formats:
hex literal short: #fff
hex literal long: #fafafa
rgb bytes: rgb(255,100,0)
rgb percent: rgb(100%,100%,0%)
named color: black
"""
import wx
import string
import urlparse
from pyparsing import nums, Literal, Optional, oneOf, Group, StringEnd, Combine, Word, alphas, hexnums
from ..pathdata import number, sign
number = number.copy()
integerConstant = Word(nums+"+-").setParseAction(lambda t:int(t[0]))
#rgb format parser
comma = Literal(",").suppress()
def clampColourByte(val):
val = int(val)
return min(max(0,val), 255)
def clampColourPerc(val):
val = float(val)
return min(max(0,val), 100)
def parseColorPerc(token):
val = token[0]
val = clampColourPerc(val)
#normalize to bytes
return int(255 * (val / 100.0))
colorByte = Optional(sign) + integerConstant.setParseAction(lambda t: clampColourByte(t[0]))
colorPerc = number.setParseAction(parseColorPerc) + Literal("%").suppress()
rgb = (
Literal("rgb(").setParseAction(lambda t: "RGB") +
(
#integer constants, ie 255,255,255
Group(colorByte + comma + colorByte + comma + colorByte) ^
#percentage values, ie 100%, 50%
Group(colorPerc + comma + colorPerc + comma + colorPerc)
)
+
Literal(")").suppress() + StringEnd()
)
def parseShortHex(t):
return tuple(int(x*2, 16) for x in t[0])
doubleHex = Word(hexnums, exact=2).setParseAction(lambda t: int(t[0], 16))
hexLiteral = (Literal("#").setParseAction(lambda t: "RGB") +
(
Group(doubleHex + doubleHex + doubleHex) |
Word(hexnums, exact=3).setParseAction(parseShortHex)
) + StringEnd()
)
def parseNamedColour(t):
try:
return ["RGB", NamedColours[t[0].lower()]]
except KeyError:
return ["RGB", (0,0,0)]
namedColour = Word(alphas).setParseAction(parseNamedColour)
colourValue = rgb | hexLiteral | namedColour
##constants
NamedColours = {
#~ #html named colours
#~ "black":(0,0,0),
#~ "silver": (0xc0, 0xc0, 0xc0, 255),
#~ "gray": (0x80, 0x80, 0x80),
#~ "white":(255,255,255),
#~ "maroon":(0x80, 0, 0),
#~ "red":(0xff, 0, 0),
#~ "purple":(0x80, 0, 0x80),
#~ "fuchsia":(0xff, 0, 0xff),
#~ "green": (0, 0x80, 0),
#~ "lime": (0, 0xff, 0),
#~ "olive": (0x80, 0x80, 00),
#~ "yellow":(0xff, 0xff, 00),
#~ "navy": (0, 0, 0x80),
#~ "blue": (0, 0, 0xff),
#~ "teal": (0, 0x80, 0x80),
#~ "aqua": (0, 0xff, 0xff),
#expanded named colors from SVG spc
'aliceblue' : (240, 248, 255) ,
'antiquewhite' : (250, 235, 215) ,
'aqua' : ( 0, 255, 255) ,
'aquamarine' : (127, 255, 212) ,
'azure' : (240, 255, 255) ,
'beige' : (245, 245, 220) ,
'bisque' : (255, 228, 196) ,
'black' : ( 0, 0, 0) ,
'blanchedalmond' : (255, 235, 205) ,
'blue' : ( 0, 0, 255) ,
'blueviolet' : (138, 43, 226) ,
'brown' : (165, 42, 42) ,
'burlywood' : (222, 184, 135) ,
'cadetblue' : ( 95, 158, 160) ,
'chartreuse' : (127, 255, 0) ,
'chocolate' : (210, 105, 30) ,
'coral' : (255, 127, 80) ,
'cornflowerblue' : (100, 149, 237) ,
'cornsilk' : (255, 248, 220) ,
'crimson' : (220, 20, 60) ,
'cyan' : ( 0, 255, 255) ,
'darkblue' : ( 0, 0, 139) ,
'darkcyan' : ( 0, 139, 139) ,
'darkgoldenrod' : (184, 134, 11) ,
'darkgray' : (169, 169, 169) ,
'darkgreen' : ( 0, 100, 0) ,
'darkgrey' : (169, 169, 169) ,
'darkkhaki' : (189, 183, 107) ,
'darkmagenta' : (139, 0, 139) ,
'darkolivegreen' : ( 85, 107, 47) ,
'darkorange' : (255, 140, 0) ,
'darkorchid' : (153, 50, 204) ,
'darkred' : (139, 0, 0) ,
'darksalmon' : (233, 150, 122) ,
'darkseagreen' : (143, 188, 143) ,
'darkslateblue' : ( 72, 61, 139) ,
'darkslategray' : ( 47, 79, 79) ,
'darkslategrey' : ( 47, 79, 79) ,
'darkturquoise' : ( 0, 206, 209) ,
'darkviolet' : (148, 0, 211) ,
'deeppink' : (255, 20, 147) ,
'deepskyblue' : ( 0, 191, 255) ,
'dimgray' : (105, 105, 105) ,
'dimgrey' : (105, 105, 105) ,
'dodgerblue' : ( 30, 144, 255) ,
'firebrick' : (178, 34, 34) ,
'floralwhite' : (255, 250, 240) ,
'forestgreen' : ( 34, 139, 34) ,
'fuchsia' : (255, 0, 255) ,
'gainsboro' : (220, 220, 220) ,
'ghostwhite' : (248, 248, 255) ,
'gold' : (255, 215, 0) ,
'goldenrod' : (218, 165, 32) ,
'gray' : (128, 128, 128) ,
'grey' : (128, 128, 128) ,
'green' : ( 0, 128, 0) ,
'greenyellow' : (173, 255, 47) ,
'honeydew' : (240, 255, 240) ,
'hotpink' : (255, 105, 180) ,
'indianred' : (205, 92, 92) ,
'indigo' : ( 75, 0, 130) ,
'ivory' : (255, 255, 240) ,
'khaki' : (240, 230, 140) ,
'lavender' : (230, 230, 250) ,
'lavenderblush' : (255, 240, 245) ,
'lawngreen' : (124, 252, 0) ,
'lemonchiffon' : (255, 250, 205) ,
'lightblue' : (173, 216, 230) ,
'lightcoral' : (240, 128, 128) ,
'lightcyan' : (224, 255, 255) ,
'lightgoldenrodyellow' : (250, 250, 210) ,
'lightgray' : (211, 211, 211) ,
'lightgreen' : (144, 238, 144) ,
'lightgrey' : (211, 211, 211) ,
'lightpink' : (255, 182, 193) ,
'lightsalmon' : (255, 160, 122) ,
'lightseagreen' : ( 32, 178, 170) ,
'lightskyblue' : (135, 206, 250) ,
'lightslategray' : (119, 136, 153) ,
'lightslategrey' : (119, 136, 153) ,
'lightsteelblue' : (176, 196, 222) ,
'lightyellow' : (255, 255, 224) ,
'lime' : ( 0, 255, 0) ,
'limegreen' : ( 50, 205, 50) ,
'linen' : (250, 240, 230) ,
'magenta' : (255, 0, 255) ,
'maroon' : (128, 0, 0) ,
'mediumaquamarine' : (102, 205, 170) ,
'mediumblue' : ( 0, 0, 205) ,
'mediumorchid' : (186, 85, 211) ,
'mediumpurple' : (147, 112, 219) ,
'mediumseagreen' : ( 60, 179, 113) ,
'mediumslateblue' : (123, 104, 238) ,
'mediumspringgreen' : ( 0, 250, 154) ,
'mediumturquoise' : ( 72, 209, 204) ,
'mediumvioletred' : (199, 21, 133) ,
'midnightblue' : ( 25, 25, 112) ,
'mintcream' : (245, 255, 250) ,
'mistyrose' : (255, 228, 225) ,
'moccasin' : (255, 228, 181) ,
'navajowhite' : (255, 222, 173) ,
'navy' : ( 0, 0, 128) ,
'oldlace' : (253, 245, 230) ,
'olive' : (128, 128, 0) ,
'olivedrab' : (107, 142, 35) ,
'orange' : (255, 165, 0) ,
'orangered' : (255, 69, 0) ,
'orchid' : (218, 112, 214) ,
'palegoldenrod' : (238, 232, 170) ,
'palegreen' : (152, 251, 152) ,
'paleturquoise' : (175, 238, 238) ,
'palevioletred' : (219, 112, 147) ,
'papayawhip' : (255, 239, 213) ,
'peachpuff' : (255, 218, 185) ,
'peru' : (205, 133, 63) ,
'pink' : (255, 192, 203) ,
'plum' : (221, 160, 221) ,
'powderblue' : (176, 224, 230) ,
'purple' : (128, 0, 128) ,
'red' : (255, 0, 0) ,
'rosybrown' : (188, 143, 143) ,
'royalblue' : ( 65, 105, 225) ,
'saddlebrown' : (139, 69, 19) ,
'salmon' : (250, 128, 114) ,
'sandybrown' : (244, 164, 96) ,
'seagreen' : ( 46, 139, 87) ,
'seashell' : (255, 245, 238) ,
'sienna' : (160, 82, 45) ,
'silver' : (192, 192, 192) ,
'skyblue' : (135, 206, 235) ,
'slateblue' : (106, 90, 205) ,
'slategray' : (112, 128, 144) ,
'slategrey' : (112, 128, 144) ,
'snow' : (255, 250, 250) ,
'springgreen' : ( 0, 255, 127) ,
'steelblue' : ( 70, 130, 180) ,
'tan' : (210, 180, 140) ,
'teal' : ( 0, 128, 128) ,
'thistle' : (216, 191, 216) ,
'tomato' : (255, 99, 71) ,
'turquoise' : ( 64, 224, 208) ,
'violet' : (238, 130, 238) ,
'wheat' : (245, 222, 179) ,
'white' : (255, 255, 255) ,
'whitesmoke' : (245, 245, 245) ,
'yellow' : (255, 255, 0) ,
'yellowgreen' : (154, 205, 50) ,
}
def fillCSS2SystemColours():
#The system colours require a wxApp to be present to retrieve,
#so if you wnat support for them you'll need
#to call this function after your wxApp instance starts
systemColors = {
"ActiveBorder": wx.SYS_COLOUR_ACTIVEBORDER,
"ActiveCaption": wx.SYS_COLOUR_ACTIVECAPTION,
"AppWorkspace": wx.SYS_COLOUR_APPWORKSPACE,
"Background": wx.SYS_COLOUR_BACKGROUND,
"ButtonFace": wx.SYS_COLOUR_BTNFACE,
"ButtonHighlight": wx.SYS_COLOUR_BTNHIGHLIGHT,
"ButtonShadow": wx.SYS_COLOUR_BTNSHADOW,
"ButtonText": wx.SYS_COLOUR_BTNTEXT,
"CaptionText": wx.SYS_COLOUR_CAPTIONTEXT,
"GrayText": wx.SYS_COLOUR_GRAYTEXT,
"Highlight": wx.SYS_COLOUR_HIGHLIGHT,
"HighlightText": wx.SYS_COLOUR_HIGHLIGHTTEXT,
"InactiveBorder": wx.SYS_COLOUR_INACTIVEBORDER,
"InactiveCaption": wx.SYS_COLOUR_INACTIVECAPTION,
"InfoBackground": wx.SYS_COLOUR_INFOBK,
"InfoText": wx.SYS_COLOUR_INFOTEXT,
"Menu": wx.SYS_COLOUR_MENU,
"MenuText": wx.SYS_COLOUR_MENUTEXT,
"Scrollbar": wx.SYS_COLOUR_SCROLLBAR,
"ThreeDDarkShadow": wx.SYS_COLOUR_3DDKSHADOW,
"ThreeDFace": wx.SYS_COLOUR_3DFACE,
"ThreeDHighlight": wx.SYS_COLOUR_3DHIGHLIGHT,
"ThreeDLightShadow": wx.SYS_COLOUR_3DLIGHT,
"ThreeDShadow": wx.SYS_COLOUR_3DSHADOW,
"Window": wx.SYS_COLOUR_WINDOW,
"WindowFrame": wx.SYS_COLOUR_WINDOWFRAME,
"WindowText": wx.SYS_COLOUR_WINDOWTEXT
}
NamedColours.update(
#strip the alpha from the system colors. Is this really what we want to do?
(k.lower(), wx.SystemSettings.GetColour(v)[:3]) for (k,v) in systemColors.iteritems()
)
\ No newline at end of file
""" 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))
\ No newline at end of file
""" 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
\ No newline at end of file
"""
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()
\ No newline at end of file
"""
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)
\ No newline at end of file
This diff is collapsed.
"""
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()
This source diff could not be displayed because it is too large. You can view the blob instead.
This diff is collapsed.
This diff is collapsed.
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg width="23.838" height="23.838" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:slic3r="http://slic3r.org/namespaces/slic3r">
<!--
Generated using Slic3r 0.7.2b
http://slic3r.org/
-->
<g id="layer0" slic3r:z="0.03" >
<polygon slic3r:type="contour" points="11.919,23.457361 10.528202,23.373235 9.157683,23.122118 7.827451,22.707599 6.556855,22.135663 5.364459,21.414874 4.267648,20.555585 3.282414,19.570351 2.423125,18.47354 1.702336,17.281144 1.1304,16.010548 0.715881,14.680316 0.464764,13.309797 0.380639,11.919 0.464764,10.528202 0.715881,9.157683 1.1304,7.827451 1.702336,6.556855 2.423125,5.364459 3.282414,4.267648 4.267648,3.282414 5.364459,2.423125 6.556855,1.702336 7.827451,1.1304 9.157683,0.715881 10.528202,0.464764 11.919,0.380639 13.309797,0.464764 14.680316,0.715881 16.010548,1.1304 17.281144,1.702336 18.47354,2.423125 19.570351,3.282414 20.555585,4.267648 21.414874,5.364459 22.135663,6.556855 22.707599,7.827451 23.122118,9.157683 23.373235,10.528202 23.457361,11.919 23.373235,13.309797 23.122118,14.680316 22.707599,16.010548 22.135663,17.281144 21.414874,18.47354 20.555585,19.570351 19.570351,20.555585 18.47354,21.414874 17.281144,22.135663 16.010548,22.707599 14.680316,23.122118 13.309797,23.373235" style="fill: white" />
<polygon slic3r:type="hole" points="10.769,19.167808 10.769,16.269 13.069,16.269 13.069,19.167808 13.554669,19.085248 14.346729,18.85706 15.108288,18.541616 15.829724,18.14289 16.501994,17.665893 17.116627,17.116627 17.665893,16.501994 18.14289,15.829724 18.541616,15.108288 18.85706,14.346729 19.085248,13.554669 19.167808,13.069 16.269,13.069 16.269,10.769 19.167808,10.769 19.085248,10.283331 18.85706,9.491271 18.541616,8.729712 18.14289,8.008276 17.665893,7.336006 17.116627,6.721373 16.501994,6.172107 15.829724,5.69511 15.108288,5.296384 14.346729,4.98094 13.554669,4.752752 13.069,4.670192 13.069,7.569 10.769,7.569 10.769,4.670192 10.283331,4.752752 9.491271,4.98094 8.729712,5.296384 8.008276,5.69511 7.336006,6.172107 6.721373,6.721373 6.172107,7.336006 5.69511,8.008276 5.296384,8.729712 4.98094,9.491271 4.752752,10.283331 4.670192,10.769 7.569,10.769 7.569,13.069 4.670192,13.069 4.752752,13.554669 4.98094,14.346729 5.296384,15.108288 5.69511,15.829724 6.172107,16.501994 6.721373,17.116627 7.336006,17.665893 8.008276,18.14289 8.729712,18.541616 9.491271,18.85706 10.283331,19.085248" style="fill: black" />
</g>
<g id="layer1" slic3r:z="0.11">
<polygon slic3r:type="contour" points="11.919,23.377361 10.537846,23.293818 9.176828,23.04444 7.855816,22.632796 6.594036,22.06483 5.409906,21.349036 4.320698,20.495704 3.342295,19.517301 2.488963,18.428093 1.773169,17.243963 1.205203,15.982183 0.793559,14.661171 0.544181,13.300153 0.460639,11.919 0.544181,10.537846 0.793559,9.176828 1.205203,7.855816 1.773169,6.594036 2.488963,5.409906 3.342295,4.320698 4.320698,3.342295 5.409906,2.488963 6.594036,1.773169 7.855816,1.205203 9.176828,0.793559 10.537846,0.544181 11.919,0.460639 13.300153,0.544181 14.661171,0.793559 15.982183,1.205203 17.243963,1.773169 18.428093,2.488963 19.517301,3.342295 20.495704,4.320698 21.349036,5.409906 22.06483,6.594036 22.632796,7.855816 23.04444,9.176828 23.293818,10.537846 23.377361,11.919 23.293818,13.300153 23.04444,14.661171 22.632796,15.982183 22.06483,17.243963 21.349036,18.428093 20.495704,19.517301 19.517301,20.495704 18.428093,21.349036 17.243963,22.06483 15.982183,22.632796 14.661171,23.04444 13.300153,23.293818" style="fill: white" />
<polygon slic3r:type="hole" points="10.769,19.167808 10.769,16.269 13.069,16.269 13.069,19.167808 13.554669,19.085248 14.346729,18.85706 15.108288,18.541616 15.829724,18.14289 16.501994,17.665893 17.116627,17.116627 17.665893,16.501994 18.14289,15.829724 18.541616,15.108288 18.85706,14.346729 19.085248,13.554669 19.167808,13.069 16.269,13.069 16.269,10.769 19.167808,10.769 19.085248,10.283331 18.85706,9.491271 18.541616,8.729712 18.14289,8.008276 17.665893,7.336006 17.116627,6.721373 16.501994,6.172107 15.829724,5.69511 15.108288,5.296384 14.346729,4.98094 13.554669,4.752752 13.069,4.670192 13.069,7.569 10.769,7.569 10.769,4.670192 10.283331,4.752752 9.491271,4.98094 8.729712,5.296384 8.008276,5.69511 7.336006,6.172107 6.721373,6.721373 6.172107,7.336006 5.69511,8.008276 5.296384,8.729712 4.98094,9.491271 4.752752,10.283331 4.670192,10.769 7.569,10.769 7.569,13.069 4.670192,13.069 4.752752,13.554669 4.98094,14.346729 5.296384,15.108288 5.69511,15.829724 6.172107,16.501994 6.721373,17.116627 7.336006,17.665893 8.008276,18.14289 8.729712,18.541616 9.491271,18.85706 10.283331,19.085248" style="fill: black" />
</g>
<g id="layer2" slic3r:z="0.21">
<polygon slic3r:type="contour" points="11.919,23.277361 10.549901,23.194547 9.200759,22.947343 7.891273,22.539291 6.640509,21.976288 5.466718,21.266742 4.387008,20.42085 3.417149,19.450991 2.571257,18.371281 1.861711,17.19749 1.298708,15.946726 0.890656,14.63724 0.643452,13.288098 0.560639,11.919 0.643452,10.549901 0.890656,9.200759 1.298708,7.891273 1.861711,6.640509 2.571257,5.466718 3.417149,4.387008 4.387008,3.417149 5.466718,2.571257 6.640509,1.861711 7.891273,1.298708 9.200759,0.890656 10.549901,0.643452 11.919,0.560639 13.288098,0.643452 14.63724,0.890656 15.946726,1.298708 17.19749,1.861711 18.371281,2.571257 19.450991,3.417149 20.42085,4.387008 21.266742,5.466718 21.976288,6.640509 22.539291,7.891273 22.947343,9.200759 23.194547,10.549901 23.277361,11.919 23.194547,13.288098 22.947343,14.63724 22.539291,15.946726 21.976288,17.19749 21.266742,18.371281 20.42085,19.450991 19.450991,20.42085 18.371281,21.266742 17.19749,21.976288 15.946726,22.539291 14.63724,22.947343 13.288098,23.194547" style="fill: white" />
<polygon slic3r:type="hole" points="10.769,19.167808 10.769,16.269 13.069,16.269 13.069,19.167808 13.554669,19.085248 14.346729,18.85706 15.108288,18.541616 15.829724,18.14289 16.501994,17.665893 17.116627,17.116627 17.665893,16.501994 18.14289,15.829724 18.541616,15.108288 18.85706,14.346729 19.085248,13.554669 19.167808,13.069 16.269,13.069 16.269,10.769 19.167808,10.769 19.085248,10.283331 18.85706,9.491271 18.541616,8.729712 18.14289,8.008276 17.665893,7.336006 17.116627,6.721373 16.501994,6.172107 15.829724,5.69511 15.108288,5.296384 14.346729,4.98094 13.554669,4.752752 13.069,4.670192 13.069,7.569 10.769,7.569 10.769,4.670192 10.283331,4.752752 9.491271,4.98094 8.729712,5.296384 8.008276,5.69511 7.336006,6.172107 6.721373,6.721373 6.172107,7.336006 5.69511,8.008276 5.296384,8.729712 4.98094,9.491271 4.752752,10.283331 4.670192,10.769 7.569,10.769 7.569,13.069 4.670192,13.069 4.752752,13.554669 4.98094,14.346729 5.296384,15.108288 5.69511,15.829724 6.172107,16.501994 6.721373,17.116627 7.336006,17.665893 8.008276,18.14289 8.729712,18.541616 9.491271,18.85706 10.283331,19.085248" style="fill: black" />
</g>
</svg>
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