瀏覽代碼

Fixed tests so that they pass in v2.7.

Hans Petter Langtangen 10 年之前
父節點
當前提交
3e461a762c
共有 4 個文件被更改,包括 178 次插入114 次删除
  1. 21 10
      pysketcher/MatplotlibDraw.py
  2. 8 1
      pysketcher/__init__.py
  3. 94 81
      pysketcher/shapes.py
  4. 55 22
      pysketcher/tests/test_pysketcher.py

+ 21 - 10
pysketcher/MatplotlibDraw.py

@@ -1,3 +1,14 @@
+from __future__ import division
+from __future__ import unicode_literals
+from __future__ import print_function
+from __future__ import absolute_import
+from future import standard_library
+standard_library.install_aliases()
+from builtins import input
+from builtins import str
+from builtins import *
+from builtins import object
+from past.utils import old_div
 import os
 import os
 import matplotlib
 import matplotlib
 matplotlib.use('TkAgg')
 matplotlib.use('TkAgg')
@@ -5,7 +16,7 @@ import matplotlib.pyplot as mpl
 import matplotlib.transforms as transforms
 import matplotlib.transforms as transforms
 import numpy as np
 import numpy as np
 
 
-class MatplotlibDraw:
+class MatplotlibDraw(object):
     """
     """
     Simple interface for plotting. This interface makes use of
     Simple interface for plotting. This interface makes use of
     Matplotlib for plotting.
     Matplotlib for plotting.
@@ -53,8 +64,8 @@ class MatplotlibDraw:
         x_space = new_x_range - x_range
         x_space = new_x_range - x_range
         new_y_range = y_range*100./occupation_percent
         new_y_range = y_range*100./occupation_percent
         y_space = new_y_range - y_range
         y_space = new_y_range - y_range
-        self.ax.set_xlim(minmax['xmin']-x_space/2., minmax['xmax']+x_space/2.)
-        self.ax.set_ylim(minmax['ymin']-y_space/2., minmax['ymax']+y_space/2.)
+        self.ax.set_xlim(minmax['xmin']-old_div(x_space,2.), minmax['xmax']+old_div(x_space,2.))
+        self.ax.set_ylim(minmax['ymin']-old_div(y_space,2.), minmax['ymax']+old_div(y_space,2.))
 
 
     def set_coordinate_system(self, xmin, xmax, ymin, ymax, axis=False,
     def set_coordinate_system(self, xmin, xmax, ymin, ymax, axis=False,
                               instruction_file=None, new_figure=True,
                               instruction_file=None, new_figure=True,
@@ -88,7 +99,7 @@ class MatplotlibDraw:
 
 
         # Compute the right X11 geometry on the screen based on the
         # Compute the right X11 geometry on the screen based on the
         # x-y ratio of axis ranges
         # x-y ratio of axis ranges
-        ratio = (self.ymax-self.ymin)/(self.xmax-self.xmin)
+        ratio = old_div((self.ymax-self.ymin),(self.xmax-self.xmin))
         self.xsize = 800  # pixel size
         self.xsize = 800  # pixel size
         self.ysize = self.xsize*ratio
         self.ysize = self.xsize*ratio
         geometry = '%dx%d' % (self.xsize, self.ysize)
         geometry = '%dx%d' % (self.xsize, self.ysize)
@@ -301,7 +312,7 @@ ax.set_aspect('equal')
         if shadow:
         if shadow:
             # http://matplotlib.sourceforge.net/users/transforms_tutorial.html#using-offset-transforms-to-create-a-shadow-effect
             # http://matplotlib.sourceforge.net/users/transforms_tutorial.html#using-offset-transforms-to-create-a-shadow-effect
             # shift the object over 2 points, and down 2 points
             # shift the object over 2 points, and down 2 points
-            dx, dy = shadow/72., -shadow/72.
+            dx, dy = old_div(shadow,72.), old_div(-shadow,72.)
             offset = transforms.ScaledTranslation(
             offset = transforms.ScaledTranslation(
                 dx, dy, self.fig.dpi_scale_trans)
                 dx, dy, self.fig.dpi_scale_trans)
             shadow_transform = self.ax.transData + offset
             shadow_transform = self.ax.transData + offset
@@ -351,12 +362,12 @@ self.ax.plot(x, y, linewidth=%d, color='gray',
             failure = os.system('convert -trim %s.png %s.png' %
             failure = os.system('convert -trim %s.png %s.png' %
                                 (filename, filename))
                                 (filename, filename))
             if failure:
             if failure:
-                print 'convert from ImageMagick is not installed - needed for cropping PNG files'
+                print('convert from ImageMagick is not installed - needed for cropping PNG files')
             self.mpl.savefig(filename + '.pdf')
             self.mpl.savefig(filename + '.pdf')
             failure = os.system('pdfcrop %s.pdf %s.pdf' %
             failure = os.system('pdfcrop %s.pdf %s.pdf' %
                                 (filename, filename))
                                 (filename, filename))
             if failure:
             if failure:
-                print 'pdfcrop is not installed - needed for cropping PDF files'
+                print('pdfcrop is not installed - needed for cropping PDF files')
             #self.mpl.savefig(filename + '.eps')
             #self.mpl.savefig(filename + '.eps')
             if self.instruction_file:
             if self.instruction_file:
                 self.instruction_file.write('mpl.savefig("%s.png", dpi=%s)\n'
                 self.instruction_file.write('mpl.savefig("%s.png", dpi=%s)\n'
@@ -368,11 +379,11 @@ self.ax.plot(x, y, linewidth=%d, color='gray',
             if ext == '.png':
             if ext == '.png':
                 failure = os.system('convert -trim %s %s' % (filename, filename))
                 failure = os.system('convert -trim %s %s' % (filename, filename))
                 if failure:
                 if failure:
-                    print 'convert from ImageMagick is not installed - needed for cropping PNG files'
+                    print('convert from ImageMagick is not installed - needed for cropping PNG files')
             elif ext == '.pdf':
             elif ext == '.pdf':
                 failure = os.system('pdfcrop %s %s' % (filename, filename))
                 failure = os.system('pdfcrop %s %s' % (filename, filename))
                 if failure:
                 if failure:
-                    print 'pdfcrop is not installed - needed for cropping PDF files'
+                    print('pdfcrop is not installed - needed for cropping PDF files')
 
 
             if self.instruction_file:
             if self.instruction_file:
                 self.instruction_file.write('mpl.savefig("%s", dpi=%s)\n'
                 self.instruction_file.write('mpl.savefig("%s", dpi=%s)\n'
@@ -535,7 +546,7 @@ def _test():
     y = 4.5 + 0.45*np.cos(0.5*np.pi*x)
     y = 4.5 + 0.45*np.cos(0.5*np.pi*x)
     d.plot_curve(x, y, arrow='end')
     d.plot_curve(x, y, arrow='end')
     d.display()
     d.display()
-    raw_input()
+    input()
 
 
 if __name__ == '__main__':
 if __name__ == '__main__':
     _test()
     _test()

+ 8 - 1
pysketcher/__init__.py

@@ -2,7 +2,14 @@
 Pysketcher is a simple tool which allows you to create
 Pysketcher is a simple tool which allows you to create
 sketches of, e.g., mechanical systems in Python.
 sketches of, e.g., mechanical systems in Python.
 """
 """
+from __future__ import unicode_literals
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from future import standard_library
+standard_library.install_aliases()
+from builtins import *
 __version__ = '0.1'
 __version__ = '0.1'
 __author__ = 'Hans Petter Langtangen <hpl@simula.no>'
 __author__ = 'Hans Petter Langtangen <hpl@simula.no>'
 
 
-from shapes import *
+from .shapes import *

+ 94 - 81
pysketcher/shapes.py

@@ -1,8 +1,21 @@
+from __future__ import division
+from __future__ import unicode_literals
+from __future__ import print_function
+from __future__ import absolute_import
+from future import standard_library
+standard_library.install_aliases()
+from builtins import input
+from builtins import zip
+from builtins import str
+from builtins import range
+from builtins import *
+from builtins import object
+from past.utils import old_div
 from numpy import linspace, sin, cos, pi, array, asarray, ndarray, sqrt, abs
 from numpy import linspace, sin, cos, pi, array, asarray, ndarray, sqrt, abs
 import pprint, copy, glob, os
 import pprint, copy, glob, os
 from math import radians
 from math import radians
 
 
-from MatplotlibDraw import MatplotlibDraw
+from .MatplotlibDraw import MatplotlibDraw
 drawing_tool = MatplotlibDraw()
 drawing_tool = MatplotlibDraw()
 
 
 def point(x, y, check_inside=False):
 def point(x, y, check_inside=False):
@@ -21,7 +34,7 @@ def point(x, y, check_inside=False):
     if check_inside:
     if check_inside:
         ok, msg = drawing_tool.inside((x,y), exception=True)
         ok, msg = drawing_tool.inside((x,y), exception=True)
         if not ok:
         if not ok:
-            print msg
+            print(msg)
 
 
     return array((x, y), dtype=float)
     return array((x, y), dtype=float)
 
 
@@ -35,7 +48,7 @@ def unit_vec(x, y=None):
     if isinstance(x, (float,int)) and isinstance(y, (float,int)):
     if isinstance(x, (float,int)) and isinstance(y, (float,int)):
         x = point(x, y)
         x = point(x, y)
     elif isinstance(x, (list,tuple,ndarray)) and y is None:
     elif isinstance(x, (list,tuple,ndarray)) and y is None:
-        return arr2D(x)/sqrt(x[0]**2 + x[1]**2)
+        return old_div(arr2D(x),sqrt(x[0]**2 + x[1]**2))
     else:
     else:
         raise TypeError('x=%s is %s, must be float or ndarray 2D point' %
         raise TypeError('x=%s is %s, must be float or ndarray 2D point' %
                         (x, type(x)))
                         (x, type(x)))
@@ -52,7 +65,7 @@ def arr2D(x, check_inside=False):
     if check_inside:
     if check_inside:
         ok, msg = drawing_tool.inside(x, exception=True)
         ok, msg = drawing_tool.inside(x, exception=True)
         if not ok:
         if not ok:
-            print msg
+            print(msg)
 
 
     return asarray(x, dtype=float)
     return asarray(x, dtype=float)
 
 
@@ -94,7 +107,7 @@ def is_sequence(*sequences, **kwargs):
         if check_inside:
         if check_inside:
             ok, msg = drawing_tool.inside(x, exception=True)
             ok, msg = drawing_tool.inside(x, exception=True)
             if not ok:
             if not ok:
-                print msg
+                print(msg)
 
 
 
 
 def animate(fig, time_points, action, moviefiles=False,
 def animate(fig, time_points, action, moviefiles=False,
@@ -130,7 +143,7 @@ def animate(fig, time_points, action, moviefiles=False,
         return '%s%%04d.png' % framefilestem
         return '%s%%04d.png' % framefilestem
 
 
 
 
-class Shape:
+class Shape(object):
     """
     """
     Superclass for drawing different geometric shapes.
     Superclass for drawing different geometric shapes.
     Subclasses define shapes, but drawing, rotation, translation,
     Subclasses define shapes, but drawing, rotation, translation,
@@ -157,8 +170,8 @@ class Shape:
         # We iterate over self.shapes many places, and will
         # We iterate over self.shapes many places, and will
         # get here if self.shapes is just a Shape object and
         # get here if self.shapes is just a Shape object and
         # not the assumed dict/list.
         # not the assumed dict/list.
-        print 'Warning: class %s does not define self.shapes\n'\
-              'as a dict of Shape objects'
+        print('Warning: class %s does not define self.shapes\n'\
+              'as a dict of Shape objects')
         return [self]  # Make the iteration work
         return [self]  # Make the iteration work
 
 
     def copy(self):
     def copy(self):
@@ -297,14 +310,14 @@ class Shape:
             raise TypeError('recurse works only with dict self.shape, not %s' %
             raise TypeError('recurse works only with dict self.shape, not %s' %
                             type(self.shapes))
                             type(self.shapes))
         space = ' '*indent
         space = ' '*indent
-        print space, '%s: %s.shapes has entries' % \
+        print(space, '%s: %s.shapes has entries' % \
               (self.__class__.__name__, name), \
               (self.__class__.__name__, name), \
-              str(list(self.shapes.keys()))[1:-1]
+              str(list(self.shapes.keys()))[1:-1])
 
 
         for shape in self.shapes:
         for shape in self.shapes:
-            print space,
-            print 'call %s.shapes["%s"].recurse("%s", %d)' % \
-                  (name, shape, shape, indent+2)
+            print(space, end=' ')
+            print('call %s.shapes["%s"].recurse("%s", %d)' % \
+                  (name, shape, shape, indent+2))
             self.shapes[shape].recurse(shape, indent+2)
             self.shapes[shape].recurse(shape, indent+2)
 
 
     def graphviz_dot(self, name, classname=True):
     def graphviz_dot(self, name, classname=True):
@@ -327,7 +340,7 @@ class Shape:
             parent += ' (%d)' % count[parent]
             parent += ' (%d)' % count[parent]
             child += ' (%d)' % count[child]
             child += ' (%d)' % count[child]
             couplings2.append((parent, child))
             couplings2.append((parent, child))
-        print 'graphviz', couplings, count
+        print('graphviz', couplings, count)
         # Remove counter for names there are only one of
         # Remove counter for names there are only one of
         for i in range(len(couplings)):
         for i in range(len(couplings)):
             parent2, child2 = couplings2[i]
             parent2, child2 = couplings2[i]
@@ -337,14 +350,14 @@ class Shape:
             if count[child] > 1:
             if count[child] > 1:
                 child = child2
                 child = child2
             couplings[i] = (parent, child)
             couplings[i] = (parent, child)
-        print couplings
+        print(couplings)
         f = open(dotfile, 'w')
         f = open(dotfile, 'w')
         f.write('digraph G {\n')
         f.write('digraph G {\n')
         for parent, child in couplings:
         for parent, child in couplings:
             f.write('"%s" -> "%s";\n' % (parent, child))
             f.write('"%s" -> "%s";\n' % (parent, child))
         f.write('}\n')
         f.write('}\n')
         f.close()
         f.close()
-        print 'Run dot -Tpng -o %s %s' % (pngfile, dotfile)
+        print('Run dot -Tpng -o %s %s' % (pngfile, dotfile))
 
 
     def _object_couplings(self, parent, couplings=[], classname=True):
     def _object_couplings(self, parent, couplings=[], classname=True):
         """Find all couplings of parent and child objects in a figure."""
         """Find all couplings of parent and child objects in a figure."""
@@ -380,7 +393,7 @@ class Shape:
     def set_linecolor(self, color):
     def set_linecolor(self, color):
         if color in drawing_tool.line_colors:
         if color in drawing_tool.line_colors:
             color = drawing_tool.line_colors[color]
             color = drawing_tool.line_colors[color]
-        elif color in drawing_tool.line_colors.values():
+        elif color in list(drawing_tool.line_colors.values()):
             pass # color is ok
             pass # color is ok
         else:
         else:
             raise ValueError('%s: invalid color "%s", must be in %s' %
             raise ValueError('%s: invalid color "%s", must be in %s' %
@@ -401,7 +414,7 @@ class Shape:
     def set_filled_curves(self, color='', pattern=''):
     def set_filled_curves(self, color='', pattern=''):
         if color in drawing_tool.line_colors:
         if color in drawing_tool.line_colors:
             color = drawing_tool.line_colors[color]
             color = drawing_tool.line_colors[color]
-        elif color in drawing_tool.line_colors.values():
+        elif color in list(drawing_tool.line_colors.values()):
             pass # color is ok
             pass # color is ok
         else:
         else:
             raise ValueError('%s: invalid color "%s", must be in %s' %
             raise ValueError('%s: invalid color "%s", must be in %s' %
@@ -417,8 +430,8 @@ class Shape:
     def show_hierarchy(self, indent=0, format='std'):
     def show_hierarchy(self, indent=0, format='std'):
         """Recursive pretty print of hierarchy of objects."""
         """Recursive pretty print of hierarchy of objects."""
         if not isinstance(self.shapes, dict):
         if not isinstance(self.shapes, dict):
-            print 'cannot print hierarchy when %s.shapes is not a dict' % \
-                  self.__class__.__name__
+            print('cannot print hierarchy when %s.shapes is not a dict' % \
+                  self.__class__.__name__)
         s = ''
         s = ''
         if format == 'dict':
         if format == 'dict':
             s += '{'
             s += '{'
@@ -487,19 +500,19 @@ class Curve(Shape):
         if xmin < t.xmin:
         if xmin < t.xmin:
             inside = False
             inside = False
             if verbose:
             if verbose:
-                print 'x_min=%g < plot area x_min=%g' % (xmin, t.xmin)
+                print('x_min=%g < plot area x_min=%g' % (xmin, t.xmin))
         if xmax > t.xmax:
         if xmax > t.xmax:
             inside = False
             inside = False
             if verbose:
             if verbose:
-                print 'x_max=%g > plot area x_max=%g' % (xmax, t.xmax)
+                print('x_max=%g > plot area x_max=%g' % (xmax, t.xmax))
         if ymin < t.ymin:
         if ymin < t.ymin:
             inside = False
             inside = False
             if verbose:
             if verbose:
-                print 'y_min=%g < plot area y_min=%g' % (ymin, t.ymin)
+                print('y_min=%g < plot area y_min=%g' % (ymin, t.ymin))
         if ymax > t.ymax:
         if ymax > t.ymax:
             inside = False
             inside = False
             if verbose:
             if verbose:
-                print 'y_max=%g > plot area y_max=%g' % (ymax, t.ymax)
+                print('y_max=%g > plot area y_max=%g' % (ymax, t.ymax))
         return inside
         return inside
 
 
     def draw(self):
     def draw(self):
@@ -559,8 +572,8 @@ class Curve(Shape):
 
 
     def recurse(self, name, indent=0):
     def recurse(self, name, indent=0):
         space = ' '*indent
         space = ' '*indent
-        print space, 'reached "bottom" object %s' % \
-              self.__class__.__name__
+        print(space, 'reached "bottom" object %s' % \
+              self.__class__.__name__)
 
 
     def _object_couplings(self, parent, couplings=[], classname=True):
     def _object_couplings(self, parent, couplings=[], classname=True):
         return
         return
@@ -784,8 +797,8 @@ class Point(Shape):
 
 
     def recurse(self, name, indent=0):
     def recurse(self, name, indent=0):
         space = ' '*indent
         space = ' '*indent
-        print space, 'reached "bottom" object %s' % \
-              self.__class__.__name__
+        print(space, 'reached "bottom" object %s' % \
+              self.__class__.__name__)
 
 
     def _object_couplings(self, parent, couplings=[], classname=True):
     def _object_couplings(self, parent, couplings=[], classname=True):
         return
         return
@@ -820,14 +833,14 @@ class Rectangle(Shape):
 
 
         # Dimensions
         # Dimensions
         dims = {
         dims = {
-            'width': Distance_wText(p + point(0, -height/5.),
-                                    p + point(width, -height/5.),
+            'width': Distance_wText(p + point(0, old_div(-height,5.)),
+                                    p + point(width, old_div(-height,5.)),
                                     'width'),
                                     'width'),
-            'height': Distance_wText(p + point(width + width/5., 0),
-                                     p + point(width + width/5., height),
+            'height': Distance_wText(p + point(width + old_div(width,5.), 0),
+                                     p + point(width + old_div(width,5.), height),
                                    'height'),
                                    'height'),
             'lower_left_corner': Text_wArrow('lower_left_corner',
             'lower_left_corner': Text_wArrow('lower_left_corner',
-                                             p - point(width/5., height/5.), p)
+                                             p - point(old_div(width,5.), old_div(height,5.)), p)
             }
             }
         self.dimensions = dims
         self.dimensions = dims
 
 
@@ -907,7 +920,7 @@ class Line(Shape):
         # Define equations for line:
         # Define equations for line:
         # y = a*x + b,  x = c*y + d
         # y = a*x + b,  x = c*y + d
         try:
         try:
-            self.a = (y[1] - y[0])/(x[1] - x[0])
+            self.a = old_div((y[1] - y[0]),(x[1] - x[0]))
             self.b = y[0] - self.a*x[0]
             self.b = y[0] - self.a*x[0]
         except ZeroDivisionError:
         except ZeroDivisionError:
             # Vertical line, y is not a function of x
             # Vertical line, y is not a function of x
@@ -917,7 +930,7 @@ class Line(Shape):
             if self.a is None:
             if self.a is None:
                 self.c = 0
                 self.c = 0
             else:
             else:
-                self.c = 1/float(self.a)
+                self.c = old_div(1,float(self.a))
             if self.b is None:
             if self.b is None:
                 self.d = x[1]
                 self.d = x[1]
         except ZeroDivisionError:
         except ZeroDivisionError:
@@ -932,7 +945,7 @@ class Line(Shape):
         # Define equations for line:
         # Define equations for line:
         # y = a*x + b,  x = c*y + d
         # y = a*x + b,  x = c*y + d
         if abs(x[1] - x[0]) > tol:
         if abs(x[1] - x[0]) > tol:
-            self.a = (y[1] - y[0])/(x[1] - x[0])
+            self.a = old_div((y[1] - y[0]),(x[1] - x[0]))
             self.b = y[0] - self.a*x[0]
             self.b = y[0] - self.a*x[0]
         else:
         else:
             # Vertical line, y is not a function of x
             # Vertical line, y is not a function of x
@@ -941,7 +954,7 @@ class Line(Shape):
         if self.a is None:
         if self.a is None:
             self.c = 0
             self.c = 0
         elif abs(self.a) > tol:
         elif abs(self.a) > tol:
-            self.c = 1/float(self.a)
+            self.c = old_div(1,float(self.a))
             self.d = x[1]
             self.d = x[1]
         else:  # self.a is 0
         else:  # self.a is 0
             # Horizontal line, x is not a function of y
             # Horizontal line, x is not a function of y
@@ -1023,7 +1036,7 @@ class Arc(Shape):
         # Stored geometric features
         # Stored geometric features
     def geometric_features(self):
     def geometric_features(self):
         a = self.shapes['arc']
         a = self.shapes['arc']
-        m = len(a.x)/2  # mid point in array
+        m = old_div(len(a.x),2)  # mid point in array
         d = {'start': point(a.x[0], a.y[0]),
         d = {'start': point(a.x[0], a.y[0]),
              'end': point(a.x[-1], a.y[-1]),
              'end': point(a.x[-1], a.y[-1]),
              'mid': point(a.x[m], a.y[m])}
              'mid': point(a.x[m], a.y[m])}
@@ -1207,7 +1220,7 @@ class VelocityProfile(Shape):
         shapes['start line'] = Line(start, (start[0], start[1]+height))
         shapes['start line'] = Line(start, (start[0], start[1]+height))
 
 
         # Draw velocity arrows
         # Draw velocity arrows
-        dy = float(height)/(num_arrows-1)
+        dy = old_div(float(height),(num_arrows-1))
         x = start[0]
         x = start[0]
         y = start[1]
         y = start[1]
         r = profile(y)  # Test on return type
         r = profile(y)  # Test on return type
@@ -1227,7 +1240,7 @@ class VelocityProfile(Shape):
         xs = []
         xs = []
         ys = []
         ys = []
         n = 100
         n = 100
-        dy = float(height)/n
+        dy = old_div(float(height),n)
         for i in range(n+2):
         for i in range(n+2):
             y = start[1] + i*dy
             y = start[1] + i*dy
             vx, vy = profile(y)
             vx, vy = profile(y)
@@ -1263,7 +1276,7 @@ class Arrow3(Shape):
         top = (self.bottom[0], self.bottom[1] + self.length)
         top = (self.bottom[0], self.bottom[1] + self.length)
         main = Line(self.bottom, top)
         main = Line(self.bottom, top)
         #head_length = self.length/8.0
         #head_length = self.length/8.0
-        head_length = drawing_tool.xrange/50.
+        head_length = old_div(drawing_tool.xrange,50.)
         head_degrees = radians(30)
         head_degrees = radians(30)
         head_left_pt = (top[0] - head_length*sin(head_degrees),
         head_left_pt = (top[0] - head_length*sin(head_degrees),
                         top[1] - head_length*cos(head_degrees))
                         top[1] - head_length*cos(head_degrees))
@@ -1341,7 +1354,7 @@ class Text_wArrow(Text):
 class Axis(Shape):
 class Axis(Shape):
     def __init__(self, start, length, label,
     def __init__(self, start, length, label,
                  rotation_angle=0, fontsize=0,
                  rotation_angle=0, fontsize=0,
-                 label_spacing=1./45, label_alignment='left'):
+                 label_spacing=old_div(1.,45), label_alignment='left'):
         """
         """
         Draw axis from start with `length` to the right
         Draw axis from start with `length` to the right
         (x axis). Place label at the end of the arrow tip.
         (x axis). Place label at the end of the arrow tip.
@@ -1387,7 +1400,7 @@ class Force(Arrow1):
     a distance `text_spacing` times the width of the total plotting
     a distance `text_spacing` times the width of the total plotting
     area away from the specified point.
     area away from the specified point.
     """
     """
-    def __init__(self, start, end, text, text_spacing=1./60,
+    def __init__(self, start, end, text, text_spacing=old_div(1.,60),
                  fontsize=0, text_pos='start', text_alignment='center'):
                  fontsize=0, text_pos='start', text_alignment='center'):
         Arrow1.__init__(self, start, end, style='->')
         Arrow1.__init__(self, start, end, style='->')
         if isinstance(text_spacing, (tuple,list)):
         if isinstance(text_spacing, (tuple,list)):
@@ -1434,7 +1447,7 @@ class Force(Arrow1):
 class Axis2(Force):
 class Axis2(Force):
     def __init__(self, start, length, label,
     def __init__(self, start, length, label,
                  rotation_angle=0, fontsize=0,
                  rotation_angle=0, fontsize=0,
-                 label_spacing=1./45, label_alignment='left'):
+                 label_spacing=old_div(1.,45), label_alignment='left'):
         direction = point(cos(radians(rotation_angle)),
         direction = point(cos(radians(rotation_angle)),
                           sin(radians(rotation_angle)))
                           sin(radians(rotation_angle)))
         Force.__init__(start=start, end=length*direction, text=label,
         Force.__init__(start=start, end=length*direction, text=label,
@@ -1451,7 +1464,7 @@ class Gravity(Axis):
     """Downward-pointing gravity arrow with the symbol g."""
     """Downward-pointing gravity arrow with the symbol g."""
     def __init__(self, start, length, fontsize=0):
     def __init__(self, start, length, fontsize=0):
         Axis.__init__(self, start, length, '$g$', below=False,
         Axis.__init__(self, start, length, '$g$', below=False,
-                      rotation_angle=-90, label_spacing=1./30,
+                      rotation_angle=-90, label_spacing=old_div(1.,30),
                       fontsize=fontsize)
                       fontsize=fontsize)
         self.shapes['arrow'].set_linecolor('black')
         self.shapes['arrow'].set_linecolor('black')
 
 
@@ -1460,7 +1473,7 @@ class Gravity(Force):
     """Downward-pointing gravity arrow with the symbol g."""
     """Downward-pointing gravity arrow with the symbol g."""
     def __init__(self, start, length, text='$g$', fontsize=0):
     def __init__(self, start, length, text='$g$', fontsize=0):
         Force.__init__(self, start, (start[0], start[1]-length),
         Force.__init__(self, start, (start[0], start[1]-length),
-                       text, text_spacing=1./60,
+                       text, text_spacing=old_div(1.,60),
                        fontsize=0, text_pos='end')
                        fontsize=0, text_pos='end')
         self.shapes['arrow'].set_linecolor('black')
         self.shapes['arrow'].set_linecolor('black')
 
 
@@ -1475,7 +1488,7 @@ class Distance_wText(Shape):
     horizontal-like arrows, the text is placed the same distance
     horizontal-like arrows, the text is placed the same distance
     above, but aligned 'center' by default (when `alignment` is None).
     above, but aligned 'center' by default (when `alignment` is None).
     """
     """
-    def __init__(self, start, end, text, fontsize=0, text_spacing=1/60.,
+    def __init__(self, start, end, text, fontsize=0, text_spacing=old_div(1,60.),
                  alignment=None, text_pos='mid'):
                  alignment=None, text_pos='mid'):
         start = arr2D(start)
         start = arr2D(start)
         end   = arr2D(end)
         end   = arr2D(end)
@@ -1522,10 +1535,10 @@ class Distance_wText(Shape):
 class Arc_wText(Shape):
 class Arc_wText(Shape):
     def __init__(self, text, center, radius,
     def __init__(self, text, center, radius,
                  start_angle, arc_angle, fontsize=0,
                  start_angle, arc_angle, fontsize=0,
-                 resolution=180, text_spacing=1/60.):
+                 resolution=180, text_spacing=old_div(1,60.)):
         arc = Arc(center, radius, start_angle, arc_angle,
         arc = Arc(center, radius, start_angle, arc_angle,
                   resolution)
                   resolution)
-        mid = arr2D(arc(arc_angle/2.))
+        mid = arr2D(arc(old_div(arc_angle,2.)))
         normal = unit_vec(mid - arr2D(center))
         normal = unit_vec(mid - arr2D(center))
         text_pos = mid + normal*drawing_tool.xrange*text_spacing
         text_pos = mid + normal*drawing_tool.xrange*text_spacing
         self.shapes = {'arc': arc,
         self.shapes = {'arc': arc,
@@ -1555,11 +1568,11 @@ class Composition(Shape):
 class SimplySupportedBeam(Shape):
 class SimplySupportedBeam(Shape):
     def __init__(self, pos, size):
     def __init__(self, pos, size):
         pos = arr2D(pos)
         pos = arr2D(pos)
-        P0 = (pos[0] - size/2., pos[1]-size)
-        P1 = (pos[0] + size/2., pos[1]-size)
+        P0 = (pos[0] - old_div(size,2.), pos[1]-size)
+        P1 = (pos[0] + old_div(size,2.), pos[1]-size)
         triangle = Triangle(P0, P1, pos)
         triangle = Triangle(P0, P1, pos)
-        gap = size/5.
-        h = size/4.  # height of rectangle
+        gap = old_div(size,5.)
+        h = old_div(size,4.)  # height of rectangle
         P2 = (P0[0], P0[1]-gap-h)
         P2 = (P0[0], P0[1]-gap-h)
         rectangle = Rectangle(P2, size, h).set_filled_curves(pattern='/')
         rectangle = Rectangle(P2, size, h).set_filled_curves(pattern='/')
         self.shapes = {'triangle': triangle, 'rectangle': rectangle}
         self.shapes = {'triangle': triangle, 'rectangle': rectangle}
@@ -1594,7 +1607,7 @@ class ConstantBeamLoad(Shape):
     def __init__(self, lower_left_corner, width, height, num_arrows=10):
     def __init__(self, lower_left_corner, width, height, num_arrows=10):
         box = Rectangle(lower_left_corner, width, height)
         box = Rectangle(lower_left_corner, width, height)
         self.shapes = {'box': box}
         self.shapes = {'box': box}
-        dx = float(width)/(num_arrows-1)
+        dx = old_div(float(width),(num_arrows-1))
         y_top = lower_left_corner[1] + height
         y_top = lower_left_corner[1] + height
         y_tip = lower_left_corner[1]
         y_tip = lower_left_corner[1]
         for i in range(num_arrows):
         for i in range(num_arrows):
@@ -1608,7 +1621,7 @@ class ConstantBeamLoad(Shape):
 class Moment(Arc_wText):
 class Moment(Arc_wText):
     def __init__(self, text, center, radius,
     def __init__(self, text, center, radius,
                  left=True, counter_clockwise=True,
                  left=True, counter_clockwise=True,
-                 fontsize=0, text_spacing=1/60.):
+                 fontsize=0, text_spacing=old_div(1,60.)):
         style = '->' if counter_clockwise else '<-'
         style = '->' if counter_clockwise else '<-'
         start_angle = 90 if left else -90
         start_angle = 90 if left else -90
         Arc_wText.__init__(self, text, center, radius,
         Arc_wText.__init__(self, text, center, radius,
@@ -1622,7 +1635,7 @@ class Moment(Arc_wText):
 class Wheel(Shape):
 class Wheel(Shape):
     def __init__(self, center, radius, inner_radius=None, nlines=10):
     def __init__(self, center, radius, inner_radius=None, nlines=10):
         if inner_radius is None:
         if inner_radius is None:
-            inner_radius = radius/5.0
+            inner_radius = old_div(radius,5.0)
 
 
         outer = Circle(center, radius)
         outer = Circle(center, radius)
         inner = Circle(center, inner_radius)
         inner = Circle(center, inner_radius)
@@ -1653,7 +1666,7 @@ class SineWave(Shape):
         self.amplitude = amplitude
         self.amplitude = amplitude
         self.mean_level = mean_level
         self.mean_level = mean_level
 
 
-        npoints = (self.xstop - self.xstart)/(self.wavelength/61.0)
+        npoints = old_div((self.xstop - self.xstart),(old_div(self.wavelength,61.0)))
         x = linspace(self.xstart, self.xstop, npoints)
         x = linspace(self.xstart, self.xstop, npoints)
         k = 2*pi/self.wavelength # frequency
         k = 2*pi/self.wavelength # frequency
         y = self.mean_level + self.amplitude*sin(k*x)
         y = self.mean_level + self.amplitude*sin(k*x)
@@ -1674,7 +1687,7 @@ class Spring(Shape):
     (these parameters can later be extracted as attributes, see table
     (these parameters can later be extracted as attributes, see table
     below).
     below).
     """
     """
-    spring_fraction = 1./2  # fraction of total length occupied by spring
+    spring_fraction = old_div(1.,2)  # fraction of total length occupied by spring
 
 
     def __init__(self, start, length, width=None, bar_length=None,
     def __init__(self, start, length, width=None, bar_length=None,
                  num_windings=11, teeth=False):
                  num_windings=11, teeth=False):
@@ -1687,9 +1700,9 @@ class Spring(Shape):
             n = n+1
             n = n+1
         L = length
         L = length
         if width is None:
         if width is None:
-            w = L/10.
+            w = old_div(L,10.)
         else:
         else:
-            w = width/2.0
+            w = old_div(width,2.0)
         s = bar_length
         s = bar_length
 
 
         # [0, x, L-x, L], f = (L-2*x)/L
         # [0, x, L-x, L], f = (L-2*x)/L
@@ -1720,7 +1733,7 @@ class Spring(Shape):
 
 
         shapes['bar1'] = Line(B, P0)
         shapes['bar1'] = Line(B, P0)
         spring_length = L - 2*s
         spring_length = L - 2*s
-        t = spring_length/n  # height increment per winding
+        t = old_div(spring_length,n)  # height increment per winding
         if teeth:
         if teeth:
             resolution = 4
             resolution = 4
         else:
         else:
@@ -1740,7 +1753,7 @@ class Spring(Shape):
                                 'length')
                                 'length')
         num_windings = Text_wArrow('num_windings',
         num_windings = Text_wArrow('num_windings',
                                    (B[0]+2*w,P2[1]+w),
                                    (B[0]+2*w,P2[1]+w),
-                                   (B[0]+1.2*w, B[1]+L/2.))
+                                   (B[0]+1.2*w, B[1]+old_div(L,2.)))
         blength1 = Distance_wText((B[0]-2*w, B[1]), (B[0]-2*w, P0[1]),
         blength1 = Distance_wText((B[0]-2*w, B[1]), (B[0]-2*w, P0[1]),
                                        'bar_length',
                                        'bar_length',
                                        text_pos=(P0[0]-7*w, P0[1]+w))
                                        text_pos=(P0[0]-7*w, P0[1]+w))
@@ -1789,18 +1802,18 @@ class Dashpot(Shape):
     ``geometric_features``.
     ``geometric_features``.
 
 
     """
     """
-    dashpot_fraction = 1./2            # fraction of total_length
-    piston_gap_fraction = 1./6         # fraction of width
-    piston_thickness_fraction = 1./8   # fraction of dashplot_length
+    dashpot_fraction = old_div(1.,2)            # fraction of total_length
+    piston_gap_fraction = old_div(1.,6)         # fraction of width
+    piston_thickness_fraction = old_div(1.,8)   # fraction of dashplot_length
 
 
     def __init__(self, start, total_length, bar_length=None,
     def __init__(self, start, total_length, bar_length=None,
                  width=None, dashpot_length=None, piston_pos=None):
                  width=None, dashpot_length=None, piston_pos=None):
         B = start
         B = start
         L = total_length
         L = total_length
         if width is None:
         if width is None:
-            w = L/10.    # total width 1/5 of length
+            w = old_div(L,10.)    # total width 1/5 of length
         else:
         else:
-            w = width/2.0
+            w = old_div(width,2.0)
         s = bar_length
         s = bar_length
 
 
         # [0, x, L-x, L], f = (L-2*x)/L
         # [0, x, L-x, L], f = (L-2*x)/L
@@ -1824,7 +1837,7 @@ class Dashpot(Shape):
             dashpot_length = f*L
             dashpot_length = f*L
         else:
         else:
             if s is None:
             if s is None:
-                f = 1./2  # the bar lengths are taken as f*dashpot_length
+                f = old_div(1.,2)  # the bar lengths are taken as f*dashpot_length
                 s = f*dashpot_length # default
                 s = f*dashpot_length # default
             P1 = (B[0], B[1]+s+dashpot_length)
             P1 = (B[0], B[1]+s+dashpot_length)
         P0 = (B[0], B[1]+s)
         P0 = (B[0], B[1]+s)
@@ -1937,7 +1950,7 @@ class Wavy(Shape):
 
 
         A_0 = amplitude_of_perturbations
         A_0 = amplitude_of_perturbations
         A_p = 0.3*A_0
         A_p = 0.3*A_0
-        A_k = k_0/2
+        A_k = old_div(k_0,2)
 
 
         x = linspace(xmin, xmax, 2001)
         x = linspace(xmin, xmax, 2001)
 
 
@@ -1952,7 +1965,7 @@ class Wavy(Shape):
         # to store all the parameters A_0, A_k, etc. as attributes
         # to store all the parameters A_0, A_k, etc. as attributes
         self.__call__ = w
         self.__call__ = w
 
 
-class StochasticWavyCurve:
+class StochasticWavyCurve(object):
     """
     """
     Precomputed stochastic wavy graphs.
     Precomputed stochastic wavy graphs.
     There are three graphs with different look.
     There are three graphs with different look.
@@ -2500,7 +2513,7 @@ def _test1():
     set_coordinate_system(xmin=0, xmax=10, ymin=0, ymax=10)
     set_coordinate_system(xmin=0, xmax=10, ymin=0, ymax=10)
     l1 = Line((0,0), (1,1))
     l1 = Line((0,0), (1,1))
     l1.draw()
     l1.draw()
-    input(': ')
+    eval(input(': '))
     c1 = Circle((5,2), 1)
     c1 = Circle((5,2), 1)
     c2 = Circle((6,2), 1)
     c2 = Circle((6,2), 1)
     w1 = Wheel((7,2), 1)
     w1 = Wheel((7,2), 1)
@@ -2514,7 +2527,7 @@ def _test2():
     set_coordinate_system(xmin=0, xmax=10, ymin=0, ymax=10)
     set_coordinate_system(xmin=0, xmax=10, ymin=0, ymax=10)
     l1 = Line((0,0), (1,1))
     l1 = Line((0,0), (1,1))
     l1.draw()
     l1.draw()
-    input(': ')
+    eval(input(': '))
     c1 = Circle((5,2), 1)
     c1 = Circle((5,2), 1)
     c2 = Circle((6,2), 1)
     c2 = Circle((6,2), 1)
     w1 = Wheel((7,2), 1)
     w1 = Wheel((7,2), 1)
@@ -2570,8 +2583,8 @@ def _test5():
     c = 6.  # center point of box
     c = 6.  # center point of box
     w = 2.  # size of box
     w = 2.  # size of box
     L = 3
     L = 3
-    r1 = Rectangle((c-w/2, c-w/2), w, w)
-    l1 = Line((c,c-w/2), (c,c-w/2-L))
+    r1 = Rectangle((c-old_div(w,2), c-old_div(w,2)), w, w)
+    l1 = Line((c,c-old_div(w,2)), (c,c-old_div(w,2)-L))
     linecolor('blue')
     linecolor('blue')
     filled_curves(True)
     filled_curves(True)
     r1.draw()
     r1.draw()
@@ -2591,9 +2604,9 @@ def rolling_wheel(total_rotation_angle):
     angle = 2.0
     angle = 2.0
     pngfiles = []
     pngfiles = []
     w1 = Wheel(center=center, radius=radius, inner_radius=0.5, nlines=7)
     w1 = Wheel(center=center, radius=radius, inner_radius=0.5, nlines=7)
-    for i in range(int(total_rotation_angle/angle)):
+    for i in range(int(old_div(total_rotation_angle,angle))):
         w1.draw()
         w1.draw()
-        print 'XXXX BIG PROBLEM WITH ANIMATE!!!'
+        print('XXXX BIG PROBLEM WITH ANIMATE!!!')
         display()
         display()
 
 
 
 
@@ -2610,10 +2623,10 @@ def rolling_wheel(total_rotation_angle):
         erase()
         erase()
     cmd = 'convert -delay 50 -loop 1000 %s tmp_movie.gif' \
     cmd = 'convert -delay 50 -loop 1000 %s tmp_movie.gif' \
           % (' '.join(pngfiles))
           % (' '.join(pngfiles))
-    print 'converting PNG files to animated GIF:\n', cmd
-    import commands
-    failure, output = commands.getstatusoutput(cmd)
-    if failure:  print 'Could not run', cmd
+    print('converting PNG files to animated GIF:\n', cmd)
+    import subprocess
+    failure, output = subprocess.getstatusoutput(cmd)
+    if failure:  print('Could not run', cmd)
 
 
 
 
 if __name__ == '__main__':
 if __name__ == '__main__':
@@ -2626,4 +2639,4 @@ if __name__ == '__main__':
         ]
         ]
     for func in funcs:
     for func in funcs:
         func()
         func()
-        raw_input('Type Return: ')
+        input('Type Return: ')

+ 55 - 22
pysketcher/tests/test_pysketcher.py

@@ -1,5 +1,24 @@
 from pysketcher import *
 from pysketcher import *
 
 
+def equal_dict(d1, d2):
+    """Return True if nested dicts d1 and d2 are equal."""
+    for k in d1:
+        #print 'comparing', k
+        if k not in d2:
+            return False
+        else:
+            if isinstance(d1[k], dict):
+                if not equal_dict(d1[k], d2[k]):
+                    return False
+            else:
+                # Hack: remove u' for unicode if present
+                d1_k = d1[k].replace("u'", "'")
+                d2_k = d2[k].replace("u'", "'")
+                if d1_k != d2_k:
+                    #print 'values differ: [%s] vs [%s]' % (d1_k, d2_k)
+                    return False
+    return True
+
 def test_Axis():
 def test_Axis():
     drawing_tool.set_coordinate_system(
     drawing_tool.set_coordinate_system(
         xmin=0, xmax=15, ymin=-7, ymax=8, axis=True,
         xmin=0, xmax=15, ymin=-7, ymax=8, axis=True,
@@ -41,8 +60,9 @@ def test_Axis():
                 'line': {'line': "2 (x,y) coords linestyle='dotted'",},
                 'line': {'line': "2 (x,y) coords linestyle='dotted'",},
                 'head right': {'line': "2 (x,y) coords linestyle='dotted'",},},
                 'head right': {'line': "2 (x,y) coords linestyle='dotted'",},},
             'label': "Text at (12.7523,1.07388)",},}
             'label': "Text at (12.7523,1.07388)",},}
-    msg = 'expected=%s, a=%s' % (expected, eval(repr(system)))
-    assert eval(repr(system)) == expected, msg
+    computed = eval(repr(system))
+    msg = 'expected=%s, computed=%s' % (expected, computed)
+    assert equal_dict(computed, expected), msg
 
 
 
 
 def test_Distance_wText():
 def test_Distance_wText():
@@ -106,8 +126,9 @@ def test_Distance_wText():
         'c2': "Text_wArrow at (4,0.5)",
         'c2': "Text_wArrow at (4,0.5)",
         'c1': "Text_wArrow at (4,3.5)",
         'c1': "Text_wArrow at (4,3.5)",
         }
         }
-    msg = 'expected=%s, a=%s' % (expected, eval(repr(examples)))
-    assert eval(repr(examples)) == expected, msg
+    computed = eval(repr(examples))
+    msg = 'expected=%s, computed=%s' % (expected, computed)
+    assert equal_dict(computed, expected), msg
 
 
 
 
 def test_Rectangle():
 def test_Rectangle():
@@ -128,8 +149,9 @@ def test_Rectangle():
     drawing_tool.savefig('tmp_Rectangle')
     drawing_tool.savefig('tmp_Rectangle')
 
 
     expected = {'rectangle': "5 (x,y) coords",}
     expected = {'rectangle': "5 (x,y) coords",}
-    msg = 'expected=%s, a=%s' % (expected, eval(repr(r)))
-    assert eval(repr(r)) == expected, msg
+    computed = eval(repr(r))
+    msg = 'expected=%s, computed=%s' % (expected, computed)
+    assert equal_dict(computed, expected), msg
 
 
 def test_Triangle():
 def test_Triangle():
     L = 3.0
     L = 3.0
@@ -149,8 +171,9 @@ def test_Triangle():
     drawing_tool.savefig('tmp_Triangle')
     drawing_tool.savefig('tmp_Triangle')
 
 
     expected = {'triangle': "4 (x,y) coords",}
     expected = {'triangle': "4 (x,y) coords",}
-    msg = 'expected=%s, a=%s' % (expected, eval(repr(t)))
-    assert eval(repr(t)) == expected, msg
+    computed = eval(repr(t))
+    msg = 'expected=%s, computed=%s' % (expected, computed)
+    assert equal_dict(computed, expected), msg
 
 
 
 
 def test_Arc():
 def test_Arc():
@@ -208,8 +231,10 @@ def test_Arc():
     drawing_tool.savefig('tmp_Arc')
     drawing_tool.savefig('tmp_Arc')
 
 
     expected = {'arc': "181 (x,y) coords"}
     expected = {'arc': "181 (x,y) coords"}
-    msg = 'expected=%s, a=%s' % (expected, eval(repr(a)))
-    assert eval(repr(a)) == expected, msg
+    computed = eval(repr(a))
+    msg = 'expected=%s, computed=%s' % (expected, computed)
+    assert equal_dict(computed, expected), msg
+
     expected = {
     expected = {
         'center': 'text "center" at (-0.2,-0.2)',
         'center': 'text "center" at (-0.2,-0.2)',
         'start_angle': {'text': "Text at (2.68468,1.55)",
         'start_angle': {'text': "Text at (2.68468,1.55)",
@@ -224,8 +249,9 @@ def test_Arc():
         'arc_angle': {'text': "Text at (0.430736,3.27177)",
         'arc_angle': {'text': "Text at (0.430736,3.27177)",
                       'arc': {'arc': "181 (x,y) coords",},}
                       'arc': {'arc': "181 (x,y) coords",},}
         }
         }
-    msg = 'expected=%s, a=%s' % (expected, eval(repr(a.dimensions)))
-    assert eval(repr(a.dimensions)) == expected, msg
+    computed = eval(repr(a.dimensions))
+    msg = 'expected=%s, computed=%s' % (expected, computed)
+    assert equal_dict(computed, expected), msg
 
 
 def test_Spring():
 def test_Spring():
     L = 5.0
     L = 5.0
@@ -257,8 +283,9 @@ def test_Spring():
         'bar1': {'line': "2 (x,y) coords",},
         'bar1': {'line': "2 (x,y) coords",},
         'bar2': {'line': "2 (x,y) coords",},
         'bar2': {'line': "2 (x,y) coords",},
         'spiral': "45 (x,y) coords",}
         'spiral': "45 (x,y) coords",}
-    msg = 'expected=%s, a=%s' % (expected, eval(repr(s1)))
-    assert eval(repr(s1)) == expected, msg
+    computed = eval(repr(s1))
+    msg = 'expected=%s, computed=%s' % (expected, computed)
+    assert equal_dict(computed, expected), msg
 
 
     expected = {
     expected = {
         'bar_length1': {'text': "Text_wArrow at (-1.5,1.75)",
         'bar_length1': {'text': "Text_wArrow at (-1.5,1.75)",
@@ -276,8 +303,9 @@ def test_Spring():
                        'line': "2 (x,y) coords linecolor='k' linewidth=1 arrow='<->'",},},},
                        'line': "2 (x,y) coords linecolor='k' linewidth=1 arrow='<->'",},},},
         'num_windings': 'annotation "num_windings" at (3,5.5) with arrow to (2.6,2.5)'
         'num_windings': 'annotation "num_windings" at (3,5.5) with arrow to (2.6,2.5)'
         }
         }
-    msg = 'expected=%s, a=%s' % (expected, eval(repr(s1.dimensions)))
-    assert eval(repr(s1.dimensions)) == expected, msg
+    computed = eval(repr(s1.dimensions))
+    msg = 'expected=%s, computed=%s' % (expected, computed)
+    assert equal_dict(computed, expected), msg
 
 
 
 
 def test_Dashpot():
 def test_Dashpot():
@@ -318,8 +346,9 @@ def test_Dashpot():
                 'rectangle': "5 (x,y) coords fillcolor='' fillpattern='X'",},},
                 'rectangle': "5 (x,y) coords fillcolor='' fillpattern='X'",},},
         'pot': "4 (x,y) coords",
         'pot': "4 (x,y) coords",
         }
         }
-    msg = 'expected=%s, a=%s' % (expected, eval(repr(d2)))
-    assert eval(repr(d2)) == expected, msg
+    computed = eval(repr(d2))
+    msg = 'expected=%s, computed=%s' % (expected, computed)
+    assert equal_dict(computed, expected), msg
 
 
     expected = {
     expected = {
         'width': {'text': "Text at (6.5,-1.56667)",
         'width': {'text': "Text at (6.5,-1.56667)",
@@ -339,8 +368,9 @@ def test_Dashpot():
                        'arrow': {'arrow': {
                        'arrow': {'arrow': {
                            'line': "2 (x,y) coords linecolor='k' linewidth=1 arrow='<->'",},},}
                            'line': "2 (x,y) coords linecolor='k' linewidth=1 arrow='<->'",},},}
         }
         }
-    msg = 'expected=%s, a=%s' % (expected, eval(repr(d2.dimensions)))
-    assert eval(repr(d2.dimensions)) == expected, msg
+    computed = eval(repr(d2.dimensions))
+    msg = 'expected=%s, computed=%s' % (expected, computed)
+    assert equal_dict(computed, expected), msg
 
 
 def test_Wavy():
 def test_Wavy():
     drawing_tool.set_coordinate_system(xmin=0, xmax=1.5,
     drawing_tool.set_coordinate_system(xmin=0, xmax=1.5,
@@ -357,8 +387,9 @@ def test_Wavy():
     drawing_tool.savefig('tmp_Wavy')
     drawing_tool.savefig('tmp_Wavy')
 
 
     expected = {'wavy': "2001 (x,y) coords",}
     expected = {'wavy': "2001 (x,y) coords",}
-    msg = 'expected=%s, a=%s' % (expected, eval(repr(w)))
-    assert eval(repr(w)) == expected, msg
+    computed = eval(repr(w))
+    msg = 'expected=%s, computed=%s' % (expected, computed)
+    assert equal_dict(computed, expected), msg
 
 
 
 
 def diff_files(files1, files2, mode='HTML'):
 def diff_files(files1, files2, mode='HTML'):
@@ -404,3 +435,5 @@ def _test_test():
         resfile = mplfile.replace('tmp_', 'res_')
         resfile = mplfile.replace('tmp_', 'res_')
         res_files.append(resfile)
         res_files.append(resfile)
     diff_files(new_files, res_files)
     diff_files(new_files, res_files)
+
+test_Arc()