printrun-src/printrun/svg2gcode/simplepath.py

changeset 16
36d478bde840
equal deleted inserted replaced
15:0bbb006204fc 16:36d478bde840
1 #!/usr/bin/env python
2 """
3 simplepath.py
4 functions for digesting paths into a simple list structure
5
6 Copyright (C) 2005 Aaron Spike, aaron@ekips.org
7
8 This program is free software; you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation; either version 2 of the License, or
11 (at your option) any later version.
12
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
17
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, write to the Free Software
20 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21
22 """
23 import re, math
24
25 def lexPath(d):
26 """
27 returns and iterator that breaks path data
28 identifies command and parameter tokens
29 """
30 offset = 0
31 length = len(d)
32 delim = re.compile(r'[ \t\r\n,]+')
33 command = re.compile(r'[MLHVCSQTAZmlhvcsqtaz]')
34 parameter = re.compile(r'(([-+]?[0-9]+(\.[0-9]*)?|[-+]?\.[0-9]+)([eE][-+]?[0-9]+)?)')
35 while 1:
36 m = delim.match(d, offset)
37 if m:
38 offset = m.end()
39 if offset >= length:
40 break
41 m = command.match(d, offset)
42 if m:
43 yield [d[offset:m.end()], True]
44 offset = m.end()
45 continue
46 m = parameter.match(d, offset)
47 if m:
48 yield [d[offset:m.end()], False]
49 offset = m.end()
50 continue
51 #TODO: create new exception
52 raise Exception, 'Invalid path data!'
53 '''
54 pathdefs = {commandfamily:
55 [
56 implicitnext,
57 #params,
58 [casts,cast,cast],
59 [coord type,x,y,0]
60 ]}
61 '''
62 pathdefs = {
63 'M':['L', 2, [float, float], ['x','y']],
64 'L':['L', 2, [float, float], ['x','y']],
65 'H':['H', 1, [float], ['x']],
66 'V':['V', 1, [float], ['y']],
67 'C':['C', 6, [float, float, float, float, float, float], ['x','y','x','y','x','y']],
68 'S':['S', 4, [float, float, float, float], ['x','y','x','y']],
69 'Q':['Q', 4, [float, float, float, float], ['x','y','x','y']],
70 'T':['T', 2, [float, float], ['x','y']],
71 'A':['A', 7, [float, float, float, int, int, float, float], [0,0,0,0,0,'x','y']],
72 'Z':['L', 0, [], []]
73 }
74 def parsePath(d):
75 """
76 Parse SVG path and return an array of segments.
77 Removes all shorthand notation.
78 Converts coordinates to absolute.
79 """
80 retval = []
81 lexer = lexPath(d)
82
83 pen = (0.0,0.0)
84 subPathStart = pen
85 lastControl = pen
86 lastCommand = ''
87
88 while 1:
89 try:
90 token, isCommand = lexer.next()
91 except StopIteration:
92 break
93 params = []
94 needParam = True
95 if isCommand:
96 if not lastCommand and token.upper() != 'M':
97 raise Exception, 'Invalid path, must begin with moveto.'
98 else:
99 command = token
100 else:
101 #command was omited
102 #use last command's implicit next command
103 needParam = False
104 if lastCommand:
105 if lastCommand.isupper():
106 command = pathdefs[lastCommand][0]
107 else:
108 command = pathdefs[lastCommand.upper()][0].lower()
109 else:
110 raise Exception, 'Invalid path, no initial command.'
111 numParams = pathdefs[command.upper()][1]
112 while numParams > 0:
113 if needParam:
114 try:
115 token, isCommand = lexer.next()
116 if isCommand:
117 raise Exception, 'Invalid number of parameters'
118 except StopIteration:
119 raise Exception, 'Unexpected end of path'
120 cast = pathdefs[command.upper()][2][-numParams]
121 param = cast(token)
122 if command.islower():
123 if pathdefs[command.upper()][3][-numParams]=='x':
124 param += pen[0]
125 elif pathdefs[command.upper()][3][-numParams]=='y':
126 param += pen[1]
127 params.append(param)
128 needParam = True
129 numParams -= 1
130 #segment is now absolute so
131 outputCommand = command.upper()
132
133 #Flesh out shortcut notation
134 if outputCommand in ('H','V'):
135 if outputCommand == 'H':
136 params.append(pen[1])
137 if outputCommand == 'V':
138 params.insert(0,pen[0])
139 outputCommand = 'L'
140 if outputCommand in ('S','T'):
141 params.insert(0,pen[1]+(pen[1]-lastControl[1]))
142 params.insert(0,pen[0]+(pen[0]-lastControl[0]))
143 if outputCommand == 'S':
144 outputCommand = 'C'
145 if outputCommand == 'T':
146 outputCommand = 'Q'
147
148 #current values become "last" values
149 if outputCommand == 'M':
150 subPathStart = tuple(params[0:2])
151 pen = subPathStart
152 if outputCommand == 'Z':
153 pen = subPathStart
154 else:
155 pen = tuple(params[-2:])
156
157 if outputCommand in ('Q','C'):
158 lastControl = tuple(params[-4:-2])
159 else:
160 lastControl = pen
161 lastCommand = command
162
163 retval.append([outputCommand,params])
164 return retval
165
166 def formatPath(a):
167 """Format SVG path data from an array"""
168 return "".join([cmd + " ".join([str(p) for p in params]) for cmd, params in a])
169
170 def translatePath(p, x, y):
171 for cmd,params in p:
172 defs = pathdefs[cmd]
173 for i in range(defs[1]):
174 if defs[3][i] == 'x':
175 params[i] += x
176 elif defs[3][i] == 'y':
177 params[i] += y
178
179 def scalePath(p, x, y):
180 for cmd,params in p:
181 defs = pathdefs[cmd]
182 for i in range(defs[1]):
183 if defs[3][i] == 'x':
184 params[i] *= x
185 elif defs[3][i] == 'y':
186 params[i] *= y
187
188 def rotatePath(p, a, cx = 0, cy = 0):
189 if a == 0:
190 return p
191 for cmd,params in p:
192 defs = pathdefs[cmd]
193 for i in range(defs[1]):
194 if defs[3][i] == 'x':
195 x = params[i] - cx
196 y = params[i + 1] - cy
197 r = math.sqrt((x**2) + (y**2))
198 if r != 0:
199 theta = math.atan2(y, x) + a
200 params[i] = (r * math.cos(theta)) + cx
201 params[i + 1] = (r * math.sin(theta)) + cy
202

mercurial