Wed, 20 Jan 2021 11:37:03 +0100
reimplemented lasercutter changes
16 | 1 | #!/usr/bin/env python |
2 | ''' | |
3 | Copyright (C) 2006 Jean-Francois Barraud, barraud@math.univ-lille1.fr | |
4 | Copyright (C) 2010 Alvin Penner, penner@vaxxine.com | |
5 | ||
6 | This program is free software; you can redistribute it and/or modify | |
7 | it under the terms of the GNU General Public License as published by | |
8 | the Free Software Foundation; either version 2 of the License, or | |
9 | (at your option) any later version. | |
10 | ||
11 | This program is distributed in the hope that it will be useful, | |
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
14 | GNU General Public License for more details. | |
15 | ||
16 | You should have received a copy of the GNU General Public License | |
17 | along with this program; if not, write to the Free Software | |
18 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |
19 | barraud@math.univ-lille1.fr | |
20 | ||
21 | This code defines several functions to make handling of transform | |
22 | attribute easier. | |
23 | ''' | |
24 | import cubicsuperpath, bezmisc | |
25 | import copy, math, re | |
26 | ||
27 | def parseTransform(transf,mat=[[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]]): | |
28 | if transf=="" or transf==None: | |
29 | return(mat) | |
30 | stransf = transf.strip() | |
31 | result=re.match("(translate|scale|rotate|skewX|skewY|matrix)\s*\(([^)]*)\)\s*,?",stransf) | |
32 | #-- translate -- | |
33 | if result.group(1)=="translate": | |
34 | args=result.group(2).replace(',',' ').split() | |
35 | dx=float(args[0]) | |
36 | if len(args)==1: | |
37 | dy=0.0 | |
38 | else: | |
39 | dy=float(args[1]) | |
40 | matrix=[[1,0,dx],[0,1,dy]] | |
41 | #-- scale -- | |
42 | if result.group(1)=="scale": | |
43 | args=result.group(2).replace(',',' ').split() | |
44 | sx=float(args[0]) | |
45 | if len(args)==1: | |
46 | sy=sx | |
47 | else: | |
48 | sy=float(args[1]) | |
49 | matrix=[[sx,0,0],[0,sy,0]] | |
50 | #-- rotate -- | |
51 | if result.group(1)=="rotate": | |
52 | args=result.group(2).replace(',',' ').split() | |
53 | a=float(args[0])*math.pi/180 | |
54 | if len(args)==1: | |
55 | cx,cy=(0.0,0.0) | |
56 | else: | |
57 | cx,cy=map(float,args[1:]) | |
58 | matrix=[[math.cos(a),-math.sin(a),cx],[math.sin(a),math.cos(a),cy]] | |
59 | matrix=composeTransform(matrix,[[1,0,-cx],[0,1,-cy]]) | |
60 | #-- skewX -- | |
61 | if result.group(1)=="skewX": | |
62 | a=float(result.group(2))*math.pi/180 | |
63 | matrix=[[1,math.tan(a),0],[0,1,0]] | |
64 | #-- skewY -- | |
65 | if result.group(1)=="skewY": | |
66 | a=float(result.group(2))*math.pi/180 | |
67 | matrix=[[1,0,0],[math.tan(a),1,0]] | |
68 | #-- matrix -- | |
69 | if result.group(1)=="matrix": | |
70 | a11,a21,a12,a22,v1,v2=result.group(2).replace(',',' ').split() | |
71 | matrix=[[float(a11),float(a12),float(v1)], [float(a21),float(a22),float(v2)]] | |
72 | ||
73 | matrix=composeTransform(mat,matrix) | |
74 | if result.end() < len(stransf): | |
75 | return(parseTransform(stransf[result.end():], matrix)) | |
76 | else: | |
77 | return matrix | |
78 | ||
79 | def formatTransform(mat): | |
80 | return ("matrix(%f,%f,%f,%f,%f,%f)" % (mat[0][0], mat[1][0], mat[0][1], mat[1][1], mat[0][2], mat[1][2])) | |
81 | ||
82 | def composeTransform(M1,M2): | |
83 | a11 = M1[0][0]*M2[0][0] + M1[0][1]*M2[1][0] | |
84 | a12 = M1[0][0]*M2[0][1] + M1[0][1]*M2[1][1] | |
85 | a21 = M1[1][0]*M2[0][0] + M1[1][1]*M2[1][0] | |
86 | a22 = M1[1][0]*M2[0][1] + M1[1][1]*M2[1][1] | |
87 | ||
88 | v1 = M1[0][0]*M2[0][2] + M1[0][1]*M2[1][2] + M1[0][2] | |
89 | v2 = M1[1][0]*M2[0][2] + M1[1][1]*M2[1][2] + M1[1][2] | |
90 | return [[a11,a12,v1],[a21,a22,v2]] | |
91 | ||
92 | def composeParents(node, mat): | |
93 | trans = node.get('transform') | |
94 | if trans: | |
95 | mat = composeTransform(parseTransform(trans), mat) | |
96 | if node.getparent().tag == inkex.addNS('g','svg'): | |
97 | mat = composeParents(node.getparent(), mat) | |
98 | return mat | |
99 | ||
100 | def applyTransformToNode(mat,node): | |
101 | m=parseTransform(node.get("transform")) | |
102 | newtransf=formatTransform(composeTransform(mat,m)) | |
103 | node.set("transform", newtransf) | |
104 | ||
105 | def applyTransformToPoint(mat,pt): | |
106 | x = mat[0][0]*pt[0] + mat[0][1]*pt[1] + mat[0][2] | |
107 | y = mat[1][0]*pt[0] + mat[1][1]*pt[1] + mat[1][2] | |
108 | pt[0]=x | |
109 | pt[1]=y | |
110 | ||
111 | def applyTransformToPath(mat,path): | |
112 | for comp in path: | |
113 | for ctl in comp: | |
114 | for pt in ctl: | |
115 | applyTransformToPoint(mat,pt) | |
116 | ||
117 | def fuseTransform(node): | |
118 | if node.get('d')==None: | |
119 | #FIXME: how do you raise errors? | |
120 | raise AssertionError, 'can not fuse "transform" of elements that have no "d" attribute' | |
121 | t = node.get("transform") | |
122 | if t == None: | |
123 | return | |
124 | m = parseTransform(t) | |
125 | d = node.get('d') | |
126 | p = cubicsuperpath.parsePath(d) | |
127 | applyTransformToPath(m,p) | |
128 | node.set('d', cubicsuperpath.formatPath(p)) | |
129 | del node.attrib["transform"] | |
130 | ||
131 | #################################################################### | |
132 | ##-- Some functions to compute a rough bbox of a given list of objects. | |
133 | ##-- this should be shipped out in an separate file... | |
134 | ||
135 | def boxunion(b1,b2): | |
136 | if b1 is None: | |
137 | return b2 | |
138 | elif b2 is None: | |
139 | return b1 | |
140 | else: | |
141 | return((min(b1[0],b2[0]), max(b1[1],b2[1]), min(b1[2],b2[2]), max(b1[3],b2[3]))) | |
142 | ||
143 | def roughBBox(path): | |
144 | xmin,xMax,ymin,yMax = path[0][0][0][0],path[0][0][0][0],path[0][0][0][1],path[0][0][0][1] | |
145 | for pathcomp in path: | |
146 | for ctl in pathcomp: | |
147 | for pt in ctl: | |
148 | xmin = min(xmin,pt[0]) | |
149 | xMax = max(xMax,pt[0]) | |
150 | ymin = min(ymin,pt[1]) | |
151 | yMax = max(yMax,pt[1]) | |
152 | return xmin,xMax,ymin,yMax | |
153 | ||
154 | def refinedBBox(path): | |
155 | xmin,xMax,ymin,yMax = path[0][0][1][0],path[0][0][1][0],path[0][0][1][1],path[0][0][1][1] | |
156 | for pathcomp in path: | |
157 | for i in range(1, len(pathcomp)): | |
158 | cmin, cmax = cubicExtrema(pathcomp[i-1][1][0], pathcomp[i-1][2][0], pathcomp[i][0][0], pathcomp[i][1][0]) | |
159 | xmin = min(xmin, cmin) | |
160 | xMax = max(xMax, cmax) | |
161 | cmin, cmax = cubicExtrema(pathcomp[i-1][1][1], pathcomp[i-1][2][1], pathcomp[i][0][1], pathcomp[i][1][1]) | |
162 | ymin = min(ymin, cmin) | |
163 | yMax = max(yMax, cmax) | |
164 | return xmin,xMax,ymin,yMax | |
165 | ||
166 | def cubicExtrema(y0, y1, y2, y3): | |
167 | cmin = min(y0, y3) | |
168 | cmax = max(y0, y3) | |
169 | d1 = y1 - y0 | |
170 | d2 = y2 - y1 | |
171 | d3 = y3 - y2 | |
172 | if (d1 - 2*d2 + d3): | |
173 | if (d2*d2 > d1*d3): | |
174 | t = (d1 - d2 + math.sqrt(d2*d2 - d1*d3))/(d1 - 2*d2 + d3) | |
175 | if (t > 0) and (t < 1): | |
176 | y = y0*(1-t)*(1-t)*(1-t) + 3*y1*t*(1-t)*(1-t) + 3*y2*t*t*(1-t) + y3*t*t*t | |
177 | cmin = min(cmin, y) | |
178 | cmax = max(cmax, y) | |
179 | t = (d1 - d2 - math.sqrt(d2*d2 - d1*d3))/(d1 - 2*d2 + d3) | |
180 | if (t > 0) and (t < 1): | |
181 | y = y0*(1-t)*(1-t)*(1-t) + 3*y1*t*(1-t)*(1-t) + 3*y2*t*t*(1-t) + y3*t*t*t | |
182 | cmin = min(cmin, y) | |
183 | cmax = max(cmax, y) | |
184 | elif (d3 - d1): | |
185 | t = -d1/(d3 - d1) | |
186 | if (t > 0) and (t < 1): | |
187 | y = y0*(1-t)*(1-t)*(1-t) + 3*y1*t*(1-t)*(1-t) + 3*y2*t*t*(1-t) + y3*t*t*t | |
188 | cmin = min(cmin, y) | |
189 | cmax = max(cmax, y) | |
190 | return cmin, cmax | |
191 | ||
192 | def computeBBox(aList,mat=[[1,0,0],[0,1,0]]): | |
193 | bbox=None | |
194 | for node in aList: | |
195 | m = parseTransform(node.get('transform')) | |
196 | m = composeTransform(mat,m) | |
197 | #TODO: text not supported! | |
198 | d = None | |
199 | if node.get("d"): | |
200 | d = node.get('d') | |
201 | elif node.get('points'): | |
202 | d = 'M' + node.get('points') | |
203 | elif node.tag in [ inkex.addNS('rect','svg'), 'rect', inkex.addNS('image','svg'), 'image' ]: | |
204 | d = 'M' + node.get('x', '0') + ',' + node.get('y', '0') + \ | |
205 | 'h' + node.get('width') + 'v' + node.get('height') + \ | |
206 | 'h-' + node.get('width') | |
207 | elif node.tag in [ inkex.addNS('line','svg'), 'line' ]: | |
208 | d = 'M' + node.get('x1') + ',' + node.get('y1') + \ | |
209 | ' ' + node.get('x2') + ',' + node.get('y2') | |
210 | elif node.tag in [ inkex.addNS('circle','svg'), 'circle', \ | |
211 | inkex.addNS('ellipse','svg'), 'ellipse' ]: | |
212 | rx = node.get('r') | |
213 | if rx is not None: | |
214 | ry = rx | |
215 | else: | |
216 | rx = node.get('rx') | |
217 | ry = node.get('ry') | |
218 | cx = float(node.get('cx', '0')) | |
219 | cy = float(node.get('cy', '0')) | |
220 | x1 = cx - float(rx) | |
221 | x2 = cx + float(rx) | |
222 | d = 'M %f %f ' % (x1, cy) + \ | |
223 | 'A' + rx + ',' + ry + ' 0 1 0 %f,%f' % (x2, cy) + \ | |
224 | 'A' + rx + ',' + ry + ' 0 1 0 %f,%f' % (x1, cy) | |
225 | ||
226 | if d is not None: | |
227 | p = cubicsuperpath.parsePath(d) | |
228 | applyTransformToPath(m,p) | |
229 | bbox=boxunion(refinedBBox(p),bbox) | |
230 | ||
231 | elif node.tag == inkex.addNS('use','svg') or node.tag=='use': | |
232 | refid=node.get(inkex.addNS('href','xlink')) | |
233 | path = '//*[@id="%s"]' % refid[1:] | |
234 | refnode = node.xpath(path) | |
235 | bbox=boxunion(computeBBox(refnode,m),bbox) | |
236 | ||
237 | bbox=boxunion(computeBBox(node,m),bbox) | |
238 | return bbox | |
239 | ||
240 | ||
241 | # vim: expandtab shiftwidth=4 tabstop=8 softtabstop=4 fileencoding=utf-8 textwidth=99 |