- introduce _epsilon for checking the singularity of a trafo
[PyX/mjg.git] / pyx / trafo.py
blob803209ec7907f5f30115d81a5876d38514c80f48
1 #!/usr/bin/env python
2 # -*- coding: ISO-8859-1 -*-
5 # Copyright (C) 2002-2004 Jörg Lehmann <joergl@users.sourceforge.net>
6 # Copyright (C) 2002-2004 André Wobst <wobsta@users.sourceforge.net>
8 # This file is part of PyX (http://pyx.sourceforge.net/).
10 # PyX is free software; you can redistribute it and/or modify
11 # it under the terms of the GNU General Public License as published by
12 # the Free Software Foundation; either version 2 of the License, or
13 # (at your option) any later version.
15 # PyX is distributed in the hope that it will be useful,
16 # but WITHOUT ANY WARRANTY; without even the implied warranty of
17 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 # GNU General Public License for more details.
20 # You should have received a copy of the GNU General Public License
21 # along with PyX; if not, write to the Free Software
22 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
24 import math
25 import attr, canvas, deformer, unit
27 # global epsilon (used to judge whether a matrix is singular
28 _epsilon = 1e-5
30 def set(epsilon=None):
31 global _epsilon
32 if epsilon is not None:
33 _epsilon = epsilon
36 # some helper routines
38 def _rmatrix(angle):
39 phi = math.pi*angle/180.0
41 return ((math.cos(phi), -math.sin(phi)),
42 (math.sin(phi), math.cos(phi)))
44 def _rvector(angle, x, y):
45 phi = math.pi*angle/180.0
47 return ((1-math.cos(phi))*x + math.sin(phi) *y,
48 -math.sin(phi) *x + (1-math.cos(phi))*y)
51 def _mmatrix(angle):
52 phi = math.pi*angle/180.0
54 return ( (math.cos(phi)*math.cos(phi)-math.sin(phi)*math.sin(phi),
55 -2*math.sin(phi)*math.cos(phi) ),
56 (-2*math.sin(phi)*math.cos(phi),
57 math.sin(phi)*math.sin(phi)-math.cos(phi)*math.cos(phi) ) )
59 # Exception
61 class TrafoException(Exception):
62 pass
64 # trafo: affine transformations
66 class trafo_pt(canvas.canvasitem, deformer.deformer):
68 """affine transformation (coordinates in constructor in pts)
70 Note that though the coordinates in the constructor are in
71 pts (which is useful for internal purposes), all other
72 methods only accept units in the standard user notation.
74 """
76 def __init__(self, matrix=((1, 0), (0, 1)), vector=(0, 0)):
77 if abs(matrix[0][0]*matrix[1][1] - matrix[0][1]*matrix[1][0]) < _epsilon:
78 raise TrafoException("transformation matrix must not be singular")
79 else:
80 self.matrix = matrix
81 self.vector = vector
83 def __mul__(self, other):
84 if isinstance(other, trafo_pt):
85 matrix = ( ( self.matrix[0][0]*other.matrix[0][0] +
86 self.matrix[0][1]*other.matrix[1][0],
87 self.matrix[0][0]*other.matrix[0][1] +
88 self.matrix[0][1]*other.matrix[1][1] ),
89 ( self.matrix[1][0]*other.matrix[0][0] +
90 self.matrix[1][1]*other.matrix[1][0],
91 self.matrix[1][0]*other.matrix[0][1] +
92 self.matrix[1][1]*other.matrix[1][1] )
95 vector = ( self.matrix[0][0]*other.vector[0] +
96 self.matrix[0][1]*other.vector[1] +
97 self.vector[0],
98 self.matrix[1][0]*other.vector[0] +
99 self.matrix[1][1]*other.vector[1] +
100 self.vector[1] )
102 return trafo_pt(matrix=matrix, vector=vector)
103 else:
104 raise NotImplementedError("can only multiply two transformations")
106 def __str__(self):
107 return "[%f %f %f %f %f %f]" % \
108 ( self.matrix[0][0], self.matrix[1][0],
109 self.matrix[0][1], self.matrix[1][1],
110 self.vector[0], self.vector[1] )
112 def outputPS(self, file, writer, context):
113 file.write("[%f %f %f %f %f %f] concat\n" % \
114 ( self.matrix[0][0], self.matrix[1][0],
115 self.matrix[0][1], self.matrix[1][1],
116 self.vector[0], self.vector[1] ) )
118 def outputPDF(self, file, writer, context):
119 file.write("%f %f %f %f %f %f cm\n" % \
120 ( self.matrix[0][0], self.matrix[1][0],
121 self.matrix[0][1], self.matrix[1][1],
122 self.vector[0], self.vector[1] ) )
124 def bbox(self):
125 return None
127 def apply_pt(self, x_pt, y_pt):
128 """apply transformation to point (x_pt, y_pt) in pts"""
129 return (self.matrix[0][0]*x_pt +
130 self.matrix[0][1]*y_pt +
131 self.vector[0],
132 self.matrix[1][0]*x_pt +
133 self.matrix[1][1]*y_pt +
134 self.vector[1])
136 def apply(self, x, y):
137 # for the transformation we have to convert to points
138 tx, ty = self.apply_pt(unit.topt(x), unit.topt(y))
139 return tx * unit.t_pt, ty * unit.t_pt
141 def deform(self, path):
142 return path.transformed(self)
144 def inverse(self):
145 det = 1.0*(self.matrix[0][0]*self.matrix[1][1] - self.matrix[0][1]*self.matrix[1][0])
146 matrix = ( ( self.matrix[1][1]/det, -self.matrix[0][1]/det),
147 (-self.matrix[1][0]/det, self.matrix[0][0]/det) )
148 return trafo_pt(matrix=matrix) * trafo_pt(vector=(-self.vector[0], -self.vector[1]))
150 def mirrored(self, angle):
151 return mirror(angle)*self
153 def rotated_pt(self, angle, x=None, y=None):
154 return rotate_pt(angle, x, y)*self
156 def rotated(self, angle, x=None, y=None):
157 return rotate(angle, x, y)*self
159 def scaled_pt(self, sx, sy=None, x=None, y=None):
160 return scale_pt(sx, sy, x, y)*self
162 def scaled(self, sx, sy=None, x=None, y=None):
163 return scale(sx, sy, x, y)*self
165 def slanted_pt(self, a, angle=0, x=None, y=None):
166 return slant_pt(a, angle, x, y)*self
168 def slanted(self, a, angle=0, x=None, y=None):
169 return slant(a, angle, x, y)*self
171 def translated_pt(self, x, y):
172 return translate_pt(x,y)*self
174 def translated(self, x, y):
175 return translate(x, y)*self
178 class trafo(trafo_pt):
180 """affine transformation"""
182 def __init__(self, matrix=((1,0),(0,1)), vector=(0,0)):
183 trafo_pt.__init__(self,
184 matrix,
185 (unit.topt(vector[0]), unit.topt(vector[1])))
189 # some standard transformations
192 class mirror(trafo):
193 def __init__(self,angle=0):
194 trafo.__init__(self, matrix=_mmatrix(angle))
197 class rotate_pt(trafo_pt):
198 def __init__(self, angle, x=None, y=None):
199 vector = 0, 0
200 if x is not None or y is not None:
201 if x is None or y is None:
202 raise TrafoException("either specify both x and y or none of them")
203 vector=_rvector(angle, x, y)
205 trafo_pt.__init__(self,
206 matrix=_rmatrix(angle),
207 vector=vector)
210 class rotate(trafo_pt):
211 def __init__(self, angle, x=None, y=None):
212 vector = 0, 0
213 if x is not None or y is not None:
214 if x is None or y is None:
215 raise TrafoException("either specify both x and y or none of them")
216 vector=_rvector(angle, unit.topt(x), unit.topt(y))
218 trafo_pt.__init__(self,
219 matrix=_rmatrix(angle),
220 vector=vector)
223 class scale_pt(trafo_pt):
224 def __init__(self, sx, sy=None, x=None, y=None):
225 if sy is None:
226 sy = sx
227 vector = 0, 0
228 if x is not None or y is not None:
229 if x is None or y is None:
230 raise TrafoException("either specify both x and y or none of them")
231 vector = (1-sx)*x, (1-sy)*y
232 trafo_pt.__init__(self, matrix=((sx,0), (0,sy)), vector=vector)
235 class scale(trafo):
236 def __init__(self, sx, sy=None, x=None, y=None):
237 if sy is None:
238 sy = sx
239 vector = 0, 0
240 if x is not None or y is not None:
241 if x is None or y is None:
242 raise TrafoException("either specify both x and y or none of them")
243 vector = (1-sx)*x, (1-sy)*y
244 trafo.__init__(self, matrix=((sx,0), (0,sy)), vector=vector)
247 class slant_pt(trafo_pt):
248 def __init__(self, a, angle=0, x=None, y=None):
249 t = ( rotate_pt(-angle, x, y)*
250 trafo(matrix=((1, a), (0, 1)))*
251 rotate_pt(angle, x, y) )
252 trafo_pt.__init__(self, t.matrix, t.vector)
255 class slant(trafo):
256 def __init__(self, a, angle=0, x=None, y=None):
257 t = ( rotate(-angle, x, y)*
258 trafo(matrix=((1, a), (0, 1)))*
259 rotate(angle, x, y) )
260 trafo.__init__(self, t.matrix, t.vector)
263 class translate_pt(trafo_pt):
264 def __init__(self, x, y):
265 trafo_pt.__init__(self, vector=(x, y))
268 class translate(trafo):
269 def __init__(self, x, y):
270 trafo.__init__(self, vector=(x, y))