From 233c384c6c53f91e1405d797d9683f3296ddabc1 Mon Sep 17 00:00:00 2001 From: Nicolas Schodet Date: Sat, 14 Mar 2009 15:57:17 +0100 Subject: * host/inter, host/simu: - moved drawable and trans_matrix to the new directory structure. - new drawable behaviour. --- host/simu/__init__.py | 0 host/simu/inter/__init__.py | 0 host/simu/inter/drawable.py | 210 ++++++++++++++++++++++++++++++++++ host/simu/inter/test/test_drawable.py | 65 +++++++++++ host/simu/utils/__init__.py | 0 host/simu/utils/trans_matrix.py | 174 ++++++++++++++++++++++++++++ 6 files changed, 449 insertions(+) create mode 100644 host/simu/__init__.py create mode 100644 host/simu/inter/__init__.py create mode 100644 host/simu/inter/drawable.py create mode 100644 host/simu/inter/test/test_drawable.py create mode 100644 host/simu/utils/__init__.py create mode 100644 host/simu/utils/trans_matrix.py (limited to 'host/simu') diff --git a/host/simu/__init__.py b/host/simu/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/host/simu/inter/__init__.py b/host/simu/inter/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/host/simu/inter/drawable.py b/host/simu/inter/drawable.py new file mode 100644 index 00000000..6f04ffd8 --- /dev/null +++ b/host/simu/inter/drawable.py @@ -0,0 +1,210 @@ +# inter - Robot simulation interface. {{{ +# +# Copyright (C) 2008 Nicolas Schodet +# +# APBTeam: +# Web: http://apbteam.org/ +# Email: team AT apbteam DOT org +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +# }}} +"""Drawable and DrawableCanvas.""" +from math import sqrt, degrees +import simu.utils.trans_matrix +import Tkinter + +__all__ = ('Drawable', 'DrawableCanvas') + +class Drawable: + """Define a drawable area with embedded transformations.""" + + def __init__ (self, onto): + """Initialise the drawable.""" + self.__onto = onto + self.__trans_matrix = simu.utils.trans_matrix.TransMatrix () + self.__items = [ ] + self.trans_apply = self.__trans_matrix.apply + self.trans_apply_angle = self.__trans_matrix.apply_angle + self.trans_apply_distance = self.__trans_matrix.apply_distance + self.trans_rotate = self.__trans_matrix.rotate + self.trans_translate = self.__trans_matrix.translate + self.trans_scale = self.__trans_matrix.scale + self.trans_identity = self.__trans_matrix.identity + self.__children = [ ] + self.children = self.__children + self.__onto.__children.append (self) + + def __draw_rectangle (self, p1, p2, **kw): + if 'outline' not in kw: + kw = kw.copy () + kw['outline'] = 'black' + p = self.trans_apply (p1, (p2[0], p1[1]), p2, (p1[0], p2[1])) + return self.__onto.__draw_polygon (*p, **kw) + + def __draw_line (self, *p, **kw): + p = self.trans_apply (*p) + return self.__onto.__draw_line (*p, **kw) + + def __draw_polygon (self, *p, **kw): + p = self.trans_apply (*p) + return self.__onto.__draw_polygon (*p, **kw) + + def __draw_circle (self, p, r, **kw): + p = self.trans_apply (p) + r = self.trans_apply_distance (r) + return self.__onto.__draw_circle (p, r, **kw) + + def __draw_arc (self, p, r, **kw): + p = self.trans_apply (p) + r = self.trans_apply_distance (r) + if 'start' in kw: + kw = kw.copy () + kw['start'] = self.trans_apply_angle (kw['start']) + import math + return self.__onto.__draw_arc (p, r, **kw) + + def draw_rectangle (self, *p, **kw): + """Draw a rectangle.""" + self.__items.append (self.__draw_rectangle (*p, **kw)) + + def draw_line (self, *p, **kw): + """Draw a line.""" + self.__items.append (self.__draw_line (*p, **kw)) + + def draw_polygon (self, *p, **kw): + """Draw a line.""" + self.__items.append (self.__draw_polygon (*p, **kw)) + + def draw_circle (self, p, r, **kw): + """Draw a circle of the given radius centered on p.""" + self.__items.append (self.__draw_circle (p, r, **kw)) + + def draw_arc (self, p, r, **kw): + """Draw a arc of the given radius centered on p.""" + self.__items.append (self.__draw_arc (p, r, **kw)) + + def reset (self): + """Clear all drawn items, reset transformations.""" + for i in self.__children: + i.reset () + self.__delete (*self.__items) + self.__items = [ ] + self.trans_identity () + + def __delete (self, *list): + """Delete a list of items.""" + self.__onto.__delete (*list) + + def update (self, *list): + """Add provided arguments to update list, or self if no argument + provided.""" + if list: + self.__onto.update (*list) + else: + self.__onto.update (self) + + def draw (self): + """Default drawing method which redraw every children.""" + if self.__children: + self.update (*self.__children) + + +class DrawableCanvas(Tkinter.Canvas): + """Extend a Tkinter.Canvas to use Drawable on it. Children are drawn on + redraw.""" + + def __init__ (self, width, height, xorigin, yorigin, master = None, **kw): + """Initialise a DrawableCanvas. The width and height parameters + define the requested drawable area virtual size. The xorigin and + yorigin parameters define origin of the virtual coordinates relative + to the drawable center.""" + Tkinter.Canvas.__init__ (self, master, **kw) + self.__width = width + self.__height = height + self.__xorigin = xorigin + self.__yorigin = yorigin + self.bind ('', self.__resize) + self.__updated = [ ] + self._Drawable__children = [ ] + + def __resize (self, ev): + # Compute new scale. + w, h = float (ev.width), float (ev.height) + self.__scale = min (w / self.__width, h / self.__height) + self.__xoffset = w / 2 + self.__xorigin * self.__scale + self.__yoffset = h / 2 - self.__yorigin * self.__scale + # Redraw. + self.draw () + + def _Drawable__draw_line (self, *p, **kw): + p = self.__coord (*p) + return self.create_line (*p, **kw) + + def _Drawable__draw_polygon (self, *p, **kw): + p = self.__coord (*p) + return self.create_polygon (*p, **kw) + + def _Drawable__draw_circle (self, p, r, **kw): + p, = self.__coord (p) + r = r * self.__scale + p1 = (p[0] - r, p[1] - r) + p2 = (p[0] + r, p[1] + r) + return self.create_oval (p1, p2, **kw) + + def _Drawable__draw_arc (self, p, r, **kw): + p, = self.__coord (p) + r = r * self.__scale + p1 = (p[0] - r, p[1] - r) + p2 = (p[0] + r, p[1] + r) + for k in ('start', 'extent'): + if k in kw: + kw = kw.copy () + kw[k] = degrees (kw[k]) + return self.create_arc (p1, p2, **kw) + + def _Drawable__delete (self, *list): + self.delete (*list) + + def update (self, *list): + """If called with arguments, add them to the update list. + Else, redraw all element in the update list.""" + if list: + for i in list: + if i not in self.__updated: + self.__updated.append (i) + else: + while self.__updated: + updated = self.__updated + self.__updated = [ ] + for i in updated: + i.draw () + + def draw (self): + """Default drawing method which redraw every children.""" + if self._Drawable__children: + self.update (*self._Drawable__children) + self.update () + + def __coord (self, *args): + return [ (i[0] * self.__scale + self.__xoffset, + -i[1] * self.__scale + self.__yoffset) for i in args ] + + def screen_coord (self, screen): + """Return drawable coordinates corresponding to the given screen + coordinates.""" + return ((self.canvasx (screen[0]) - self.__xoffset) / self.__scale, + -(self.canvasy (screen[1]) - self.__yoffset) / self.__scale) + diff --git a/host/simu/inter/test/test_drawable.py b/host/simu/inter/test/test_drawable.py new file mode 100644 index 00000000..f67cb144 --- /dev/null +++ b/host/simu/inter/test/test_drawable.py @@ -0,0 +1,65 @@ +# inter - Robot simulation interface. {{{ +# +# Copyright (C) 2008 Nicolas Schodet +# +# APBTeam: +# Web: http://apbteam.org/ +# Email: team AT apbteam DOT org +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +# }}} */ +from math import pi + +from simu.inter.drawable import * + +class Test (Drawable): + + def draw (self): + self.draw_rectangle ((-100, -100), (100, 100), fill = '', outline = 'gray') + self.draw_rectangle ((0, 0), (5, 5), fill = 'red') + self.draw_rectangle ((20, 20), (50, 50), fill = '', outline = 'blue') + self.draw_line ((20, 20), (25, 25), (80, 0), (0, 80), fill = 'green') + self.draw_line ((20, 20), (25, 25), (80, 0), (0, 80), smooth = True) + self.draw_circle ((40, -40), 10) + self.draw_arc ((-40, 0), 20, start = pi / 4, extent = pi / 2) + +class App (DrawableCanvas): + + def __init__ (self, master = None): + DrawableCanvas.__init__ (self, 300, 300, 20, 20, master) + self.pack (expand = True, fill = 'both') + self.test = Test (self) + self.animated = False + self.i = 0 + + def animate (self): + # Real user should reset at each redraw. + self.after (500, self.animate) + self.test.draw () + self.test.trans_rotate (-pi/12) + self.test.trans_translate ((10, 10)) + self.test.trans_scale (1.05) + self.i += 1 + if self.i == 10: + self.test.reset () + + def draw (self): + if not self.animated: + self.animate () + self.animated = True + +app = App () +app.mainloop () diff --git a/host/simu/utils/__init__.py b/host/simu/utils/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/host/simu/utils/trans_matrix.py b/host/simu/utils/trans_matrix.py new file mode 100644 index 00000000..7071e188 --- /dev/null +++ b/host/simu/utils/trans_matrix.py @@ -0,0 +1,174 @@ +# inter - Robot simulation interface. {{{ +# +# Copyright (C) 2008 Nicolas Schodet +# +# APBTeam: +# Web: http://apbteam.org/ +# Email: team AT apbteam DOT org +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +# }}} +"""TransMatrix utility.""" +from math import sin, cos, sqrt, atan2 + +class TransMatrix: + """Define a matrix to be used for transformations on the plane. + + This is a "special" kind of matrix, because the last column is omitted as + it is always (0, 0, 1).""" + + IDENTITY = ((1, 0), (0, 1), (0, 0)) + + def __init__ (self, *m): + """Initialise the matrix, with optional initial value. + + >>> TransMatrix () + ((1, 0), (0, 1), (0, 0)) + >>> TransMatrix ((1, 2), (3, 4), (5, 6)) + ((1, 2), (3, 4), (5, 6)) + """ + if m: + assert len (m) == 3 + for i in m: + assert len (i) == 2 + self.matrix = m + else: + self.matrix = self.IDENTITY + + def identity (self): + """Set to identity. + + >>> a = TransMatrix () + >>> a.translate ((2, 3)) + >>> a.identity (); a + ((1, 0), (0, 1), (0, 0)) + """ + self.matrix = self.IDENTITY + + def rotate (self, angle): + """Transform the current matrix to do a rotation. + + >>> from math import pi + >>> a = TransMatrix () + >>> a.rotate (pi / 3); a # doctest: +ELLIPSIS + ((0.5..., 0.866...), (-0.866..., 0.5...), (0.0, 0.0)) + """ + s = sin (angle) + c = cos (angle) + m = TransMatrix ((c, s), (-s, c), (0, 0)) + self *= m + + def translate (self, by): + """Transform the current matrix to do a translation. + + >>> a = TransMatrix () + >>> a.translate ((2, 3)); a + ((1, 0), (0, 1), (2, 3)) + """ + m = TransMatrix ((1, 0), (0, 1), by) + self *= m + + def scale (self, factor): + """Transform the current matrix to do a scaling. + + >>> a = TransMatrix () + >>> a.scale (2); a + ((2, 0), (0, 2), (0, 0)) + """ + m = TransMatrix ((factor, 0), (0, factor), (0, 0)) + self *= m + + def __imul__ (self, other): + """Multiply by an other matrix. + + >>> a = TransMatrix ((1, 0), (0, 1), (1, 0)) + >>> b = TransMatrix ((0, 1), (1, 0), (0, 1)) + >>> a *= b; a + ((0, 1), (1, 0), (0, 2)) + """ + s = self.matrix + o = other.matrix + self.matrix = ( + (s[0][0] * o[0][0] + s[0][1] * o[1][0], + s[0][0] * o[0][1] + s[0][1] * o[1][1]), + (s[1][0] * o[0][0] + s[1][1] * o[1][0], + s[1][0] * o[0][1] + s[1][1] * o[1][1]), + (s[2][0] * o[0][0] + s[2][1] * o[1][0] + o[2][0], + s[2][0] * o[0][1] + s[2][1] * o[1][1] + o[2][1])) + return self + + def apply (self, *args): + """Apply (multiply) the matrix to all the given arguments. + + >>> m = TransMatrix ((1, 2), (4, 8), (16, 32)) + >>> m.apply ((1, 0)) + (17, 34) + >>> m.apply ((0, 1), (1, 1)) + ((20, 40), (21, 42)) + """ + r = tuple ( + (i[0] * self.matrix[0][0] + i[1] * self.matrix[1][0] + + self.matrix[2][0], + i[0] * self.matrix[0][1] + i[1] * self.matrix[1][1] + + self.matrix[2][1]) + for i in args) + if len (args) == 1: + return r[0] + else: + return r + + def apply_angle (self, angle): + """Apply the matrix to an angle. + + >>> from math import pi + >>> a = TransMatrix () + >>> a.rotate (pi / 6) + >>> a.translate ((2, 3)) + >>> a.scale (4) + >>> a.apply_angle (pi / 6), pi / 3 # doctest: +ELLIPSIS + (1.0471..., 1.0471...) + """ + o, m = self.apply ((0, 0), (cos (angle), sin (angle))) + v = (m[0] - o[0], m[1] - o[1]) + vl = sqrt (v[0] ** 2 + v[1] ** 2) + v = (v[0] / vl, v[1] / vl) + return atan2 (v[1], v[0]) + + def apply_distance (self, distance): + """Apply the matrix to a distance. + + >>> from math import pi + >>> a = TransMatrix () + >>> a.rotate (pi / 6) + >>> a.translate ((2, 3)) + >>> a.scale (4) + >>> round (a.apply_distance (2)) + 8.0 + """ + o, m = self.apply ((0, 0), (distance, 0)) + v = (m[0] - o[0], m[1] - o[1]) + vl = sqrt (v[0] ** 2 + v[1] ** 2) + return vl + + def __repr__ (self): + return self.matrix.__repr__ () + +def _test (): + import doctest + doctest.testmod () + +if __name__ == '__main__': + _test() -- cgit v1.2.3