add a convenience function for mering attrs with default attrs, checking, and None...
[PyX.git] / trafo.py
blobbf486e444f40fb001803ca66243c7c5080540b32
1 # -*- encoding: utf-8 -*-
4 # Copyright (C) 2002-2011 Jörg Lehmann <joergl@users.sourceforge.net>
5 # Copyright (C) 2002-2011 André Wobst <wobsta@users.sourceforge.net>
7 # This file is part of PyX (http://pyx.sourceforge.net/).
9 # PyX is free software; you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation; either version 2 of the License, or
12 # (at your option) any later version.
14 # PyX is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 # GNU General Public License for more details.
19 # You should have received a copy of the GNU General Public License
20 # along with PyX; if not, write to the Free Software
21 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
23 import math
24 from . import attr, baseclasses, unit
26 # global epsilon (used to judge whether a matrix is singular)
27 _epsilon = (1e-5)**2
29 def set(epsilon=None):
30 global _epsilon
31 if epsilon is not None:
32 _epsilon = epsilon
35 # some helper routines
37 def _rmatrix(angle):
38 phi = math.pi*angle/180.0
40 return ((math.cos(phi), -math.sin(phi)),
41 (math.sin(phi), math.cos(phi)))
43 def _rvector(angle, x, y):
44 phi = math.pi*angle/180.0
46 return ((1-math.cos(phi))*x + math.sin(phi) *y,
47 -math.sin(phi) *x + (1-math.cos(phi))*y)
50 def _mmatrix(angle):
51 phi = math.pi*angle/180.0
53 return ( (math.cos(phi)*math.cos(phi)-math.sin(phi)*math.sin(phi),
54 -2*math.sin(phi)*math.cos(phi) ),
55 (-2*math.sin(phi)*math.cos(phi),
56 math.sin(phi)*math.sin(phi)-math.cos(phi)*math.cos(phi) ) )
58 class _marker: pass
60 # Exception
62 class TrafoException(Exception):
63 pass
65 # trafo: affine transformations
67 class trafo_pt(baseclasses.deformer):
69 """affine transformation (coordinates in constructor in pts)
71 Note that though the coordinates in the constructor are in
72 pts (which is useful for internal purposes), all other
73 methods only accept units in the standard user notation.
75 """
77 def __init__(self, matrix=((1, 0), (0, 1)), vector=(0, 0), epsilon=_marker):
78 """Return trafo with given transformation matrix and vector. If epsilon
79 is passed it is used instead of the global epsilon defined in the module to
80 check whether the matrix is singular or not. Use epsilon=None to turn of this
81 checking.
82 """
83 if epsilon is _marker:
84 epsilon = _epsilon
85 self.epsilon = epsilon
86 if epsilon is not None and abs(matrix[0][0]*matrix[1][1] - matrix[0][1]*matrix[1][0]) < epsilon:
87 raise TrafoException("transformation matrix must not be singular")
88 else:
89 self.matrix = matrix
90 self.vector = vector
92 def __mul__(self, other):
93 if isinstance(other, trafo_pt):
94 if self.epsilon is None or other.epsilon is None:
95 epsilon = None
96 elif self.epsilon <= other.epsilon:
97 epsilon = self.epsilon
98 else:
99 epsilon = other.epsilon
100 matrix = ( ( self.matrix[0][0]*other.matrix[0][0] +
101 self.matrix[0][1]*other.matrix[1][0],
102 self.matrix[0][0]*other.matrix[0][1] +
103 self.matrix[0][1]*other.matrix[1][1] ),
104 ( self.matrix[1][0]*other.matrix[0][0] +
105 self.matrix[1][1]*other.matrix[1][0],
106 self.matrix[1][0]*other.matrix[0][1] +
107 self.matrix[1][1]*other.matrix[1][1] )
110 vector = ( self.matrix[0][0]*other.vector[0] +
111 self.matrix[0][1]*other.vector[1] +
112 self.vector[0],
113 self.matrix[1][0]*other.vector[0] +
114 self.matrix[1][1]*other.vector[1] +
115 self.vector[1] )
117 return trafo_pt(matrix=matrix, vector=vector, epsilon=epsilon)
118 else:
119 raise NotImplementedError("can only multiply two transformations")
121 def __str__(self):
122 return "[%f %f %f %f %f %f]" % \
123 ( self.matrix[0][0], self.matrix[1][0],
124 self.matrix[0][1], self.matrix[1][1],
125 self.vector[0], self.vector[1] )
127 def processPS(self, file, writer, context, registry):
128 file.write("[%f %f %f %f %f %f] concat\n" % \
129 ( self.matrix[0][0], self.matrix[1][0],
130 self.matrix[0][1], self.matrix[1][1],
131 self.vector[0], self.vector[1] ) )
133 def processPDF(self, file, writer, context, registry):
134 file.write("%f %f %f %f %f %f cm\n" % \
135 ( self.matrix[0][0], self.matrix[1][0],
136 self.matrix[0][1], self.matrix[1][1],
137 self.vector[0], self.vector[1] ) )
139 def apply_pt(self, x_pt, y_pt):
140 """apply transformation to point (x_pt, y_pt) in pts"""
141 return ( self.matrix[0][0]*x_pt + self.matrix[0][1]*y_pt + self.vector[0],
142 self.matrix[1][0]*x_pt + self.matrix[1][1]*y_pt + self.vector[1] )
144 def apply(self, x, y):
145 # for the transformation we have to convert to points
146 tx, ty = self.apply_pt(unit.topt(x), unit.topt(y))
147 return tx * unit.t_pt, ty * unit.t_pt
149 def deform(self, path):
150 return path.transformed(self)
152 def inverse(self):
153 det = 1.0*(self.matrix[0][0]*self.matrix[1][1] - self.matrix[0][1]*self.matrix[1][0])
154 matrix = ( ( self.matrix[1][1]/det, -self.matrix[0][1]/det),
155 (-self.matrix[1][0]/det, self.matrix[0][0]/det) )
156 return ( trafo_pt(matrix=matrix, epsilon=self.epsilon) *
157 trafo_pt(vector=(-self.vector[0], -self.vector[1]), epsilon=self.epsilon) )
159 def mirrored(self, angle):
160 return mirror(angle, epsilon=self.epsilon) * self
162 def rotated_pt(self, angle, x=None, y=None):
163 return rotate_pt(angle, x, y, epsilon=self.epsilon) * self
165 def rotated(self, angle, x=None, y=None):
166 return rotate(angle, x, y, epsilon=self.epsilon) * self
168 def scaled_pt(self, sx, sy=None, x=None, y=None):
169 return scale_pt(sx, sy, x, y, epsilon=self.epsilon) * self
171 def scaled(self, sx, sy=None, x=None, y=None):
172 return scale(sx, sy, x, y, epsilon=self.epsilon) * self
174 def slanted_pt(self, a, angle=0, x=None, y=None):
175 return slant_pt(a, angle, x, y, epsilon=self.epsilon) * self
177 def slanted(self, a, angle=0, x=None, y=None):
178 return slant(a, angle, x, y, epsilon=self.epsilon) * self
180 def translated_pt(self, x, y):
181 return translate_pt(x, y, epsilon=self.epsilon) * self
183 def translated(self, x, y):
184 return translate(x, y, epsilon=self.epsilon) * self
187 class trafo(trafo_pt):
189 """affine transformation"""
191 def __init__(self, matrix=((1,0), (0,1)), vector=(0, 0), epsilon=_marker):
192 trafo_pt.__init__(self,
193 matrix, (unit.topt(vector[0]), unit.topt(vector[1])),
194 epsilon=epsilon)
197 # some standard transformations
200 identity = trafo()
202 class mirror(trafo):
203 def __init__(self, angle=0, epsilon=_marker):
204 trafo.__init__(self, matrix=_mmatrix(angle), epsilon=epsilon)
207 class rotate_pt(trafo_pt):
208 def __init__(self, angle, x=None, y=None, epsilon=_marker):
209 vector = 0, 0
210 if x is not None or y is not None:
211 if x is None or y is None:
212 raise TrafoException("either specify both x and y or none of them")
213 vector=_rvector(angle, x, y)
215 trafo_pt.__init__(self, matrix=_rmatrix(angle), vector=vector, epsilon=epsilon)
218 class rotate(trafo_pt):
219 def __init__(self, angle, x=None, y=None, epsilon=_marker):
220 vector = 0, 0
221 if x is not None or y is not None:
222 if x is None or y is None:
223 raise TrafoException("either specify both x and y or none of them")
224 vector=_rvector(angle, unit.topt(x), unit.topt(y))
226 trafo_pt.__init__(self, matrix=_rmatrix(angle), vector=vector, epsilon=epsilon)
229 class scale_pt(trafo_pt):
230 def __init__(self, sx, sy=None, x=None, y=None, epsilon=_marker):
231 if sy is None:
232 sy = sx
233 vector = 0, 0
234 if x is not None or y is not None:
235 if x is None or y is None:
236 raise TrafoException("either specify both x and y or none of them")
237 vector = (1-sx)*x, (1-sy)*y
238 trafo_pt.__init__(self, matrix=((sx, 0), (0, sy)), vector=vector, epsilon=epsilon)
241 class scale(trafo):
242 def __init__(self, sx, sy=None, x=None, y=None, epsilon=_marker):
243 if sy is None:
244 sy = sx
245 vector = 0, 0
246 if x is not None or y is not None:
247 if x is None or y is None:
248 raise TrafoException("either specify both x and y or none of them")
249 vector = (1-sx)*x, (1-sy)*y
250 trafo.__init__(self, matrix=((sx, 0), (0, sy)), vector=vector, epsilon=epsilon)
253 class slant_pt(trafo_pt):
254 def __init__(self, a, angle=0, x=None, y=None, epsilon=_marker):
255 t = ( rotate_pt(-angle, x, y, epsilon=epsilon) *
256 trafo(matrix=((1, a), (0, 1)), epsilon=epsilon) *
257 rotate_pt(angle, x, y, epsilon=epsilon) )
258 trafo_pt.__init__(self, t.matrix, t.vector, epsilon=epsilon)
261 class slant(trafo):
262 def __init__(self, a, angle=0, x=None, y=None, epsilon=_marker):
263 t = ( rotate(-angle, x, y, epsilon=epsilon) *
264 trafo(matrix=((1, a), (0, 1)), epsilon=epsilon) *
265 rotate(angle, x, y, epsilon=epsilon) )
266 trafo.__init__(self, t.matrix, t.vector, epsilon=epsilon)
269 class translate_pt(trafo_pt):
270 def __init__(self, x, y, epsilon=_marker):
271 trafo_pt.__init__(self, vector=(x, y), epsilon=epsilon)
274 class translate(trafo):
275 def __init__(self, x, y, epsilon=_marker):
276 trafo.__init__(self, vector=(x, y), epsilon=epsilon)