Quellcode durchsuchen

Fixed tests so that they pass in v2.7.

Hans Petter Langtangen vor 10 Jahren
Ursprung
Commit
3e461a762c
4 geänderte Dateien mit 178 neuen und 114 gelöschten Zeilen
  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 matplotlib
 matplotlib.use('TkAgg')
@@ -5,7 +16,7 @@ import matplotlib.pyplot as mpl
 import matplotlib.transforms as transforms
 import numpy as np
 
-class MatplotlibDraw:
+class MatplotlibDraw(object):
     """
     Simple interface for plotting. This interface makes use of
     Matplotlib for plotting.
@@ -53,8 +64,8 @@ class MatplotlibDraw:
         x_space = new_x_range - x_range
         new_y_range = y_range*100./occupation_percent
         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,
                               instruction_file=None, new_figure=True,
@@ -88,7 +99,7 @@ class MatplotlibDraw:
 
         # Compute the right X11 geometry on the screen based on the
         # 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.ysize = self.xsize*ratio
         geometry = '%dx%d' % (self.xsize, self.ysize)
@@ -301,7 +312,7 @@ ax.set_aspect('equal')
         if shadow:
             # 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
-            dx, dy = shadow/72., -shadow/72.
+            dx, dy = old_div(shadow,72.), old_div(-shadow,72.)
             offset = transforms.ScaledTranslation(
                 dx, dy, self.fig.dpi_scale_trans)
             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' %
                                 (filename, filename))
             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')
             failure = os.system('pdfcrop %s.pdf %s.pdf' %
                                 (filename, filename))
             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')
             if self.instruction_file:
                 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':
                 failure = os.system('convert -trim %s %s' % (filename, filename))
                 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':
                 failure = os.system('pdfcrop %s %s' % (filename, filename))
                 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:
                 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)
     d.plot_curve(x, y, arrow='end')
     d.display()
-    raw_input()
+    input()
 
 if __name__ == '__main__':
     _test()

+ 8 - 1
pysketcher/__init__.py

@@ -2,7 +2,14 @@
 Pysketcher is a simple tool which allows you to create
 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'
 __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
 import pprint, copy, glob, os
 from math import radians
 
-from MatplotlibDraw import MatplotlibDraw
+from .MatplotlibDraw import MatplotlibDraw
 drawing_tool = MatplotlibDraw()
 
 def point(x, y, check_inside=False):
@@ -21,7 +34,7 @@ def point(x, y, check_inside=False):
     if check_inside:
         ok, msg = drawing_tool.inside((x,y), exception=True)
         if not ok:
-            print msg
+            print(msg)
 
     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)):
         x = point(x, y)
     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:
         raise TypeError('x=%s is %s, must be float or ndarray 2D point' %
                         (x, type(x)))
@@ -52,7 +65,7 @@ def arr2D(x, check_inside=False):
     if check_inside:
         ok, msg = drawing_tool.inside(x, exception=True)
         if not ok:
-            print msg
+            print(msg)
 
     return asarray(x, dtype=float)
 
@@ -94,7 +107,7 @@ def is_sequence(*sequences, **kwargs):
         if check_inside:
             ok, msg = drawing_tool.inside(x, exception=True)
             if not ok:
-                print msg
+                print(msg)
 
 
 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
 
 
-class Shape:
+class Shape(object):
     """
     Superclass for drawing different geometric shapes.
     Subclasses define shapes, but drawing, rotation, translation,
@@ -157,8 +170,8 @@ class Shape:
         # We iterate over self.shapes many places, and will
         # get here if self.shapes is just a Shape object and
         # 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
 
     def copy(self):
@@ -297,14 +310,14 @@ class Shape:
             raise TypeError('recurse works only with dict self.shape, not %s' %
                             type(self.shapes))
         space = ' '*indent
-        print space, '%s: %s.shapes has entries' % \
+        print(space, '%s: %s.shapes has entries' % \
               (self.__class__.__name__, name), \
-              str(list(self.shapes.keys()))[1:-1]
+              str(list(self.shapes.keys()))[1:-1])
 
         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)
 
     def graphviz_dot(self, name, classname=True):
@@ -327,7 +340,7 @@ class Shape:
             parent += ' (%d)' % count[parent]
             child += ' (%d)' % count[child]
             couplings2.append((parent, child))
-        print 'graphviz', couplings, count
+        print('graphviz', couplings, count)
         # Remove counter for names there are only one of
         for i in range(len(couplings)):
             parent2, child2 = couplings2[i]
@@ -337,14 +350,14 @@ class Shape:
             if count[child] > 1:
                 child = child2
             couplings[i] = (parent, child)
-        print couplings
+        print(couplings)
         f = open(dotfile, 'w')
         f.write('digraph G {\n')
         for parent, child in couplings:
             f.write('"%s" -> "%s";\n' % (parent, child))
         f.write('}\n')
         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):
         """Find all couplings of parent and child objects in a figure."""
@@ -380,7 +393,7 @@ class Shape:
     def set_linecolor(self, color):
         if color in drawing_tool.line_colors:
             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
         else:
             raise ValueError('%s: invalid color "%s", must be in %s' %
@@ -401,7 +414,7 @@ class Shape:
     def set_filled_curves(self, color='', pattern=''):
         if color in drawing_tool.line_colors:
             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
         else:
             raise ValueError('%s: invalid color "%s", must be in %s' %
@@ -417,8 +430,8 @@ class Shape:
     def show_hierarchy(self, indent=0, format='std'):
         """Recursive pretty print of hierarchy of objects."""
         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 = ''
         if format == 'dict':
             s += '{'
@@ -487,19 +500,19 @@ class Curve(Shape):
         if xmin < t.xmin:
             inside = False
             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:
             inside = False
             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:
             inside = False
             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:
             inside = False
             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
 
     def draw(self):
@@ -559,8 +572,8 @@ class Curve(Shape):
 
     def recurse(self, name, indent=0):
         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):
         return
@@ -784,8 +797,8 @@ class Point(Shape):
 
     def recurse(self, name, indent=0):
         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):
         return
@@ -820,14 +833,14 @@ class Rectangle(Shape):
 
         # Dimensions
         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'),
-            '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'),
             '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
 
@@ -907,7 +920,7 @@ class Line(Shape):
         # Define equations for line:
         # y = a*x + b,  x = c*y + d
         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]
         except ZeroDivisionError:
             # Vertical line, y is not a function of x
@@ -917,7 +930,7 @@ class Line(Shape):
             if self.a is None:
                 self.c = 0
             else:
-                self.c = 1/float(self.a)
+                self.c = old_div(1,float(self.a))
             if self.b is None:
                 self.d = x[1]
         except ZeroDivisionError:
@@ -932,7 +945,7 @@ class Line(Shape):
         # Define equations for line:
         # y = a*x + b,  x = c*y + d
         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]
         else:
             # Vertical line, y is not a function of x
@@ -941,7 +954,7 @@ class Line(Shape):
         if self.a is None:
             self.c = 0
         elif abs(self.a) > tol:
-            self.c = 1/float(self.a)
+            self.c = old_div(1,float(self.a))
             self.d = x[1]
         else:  # self.a is 0
             # Horizontal line, x is not a function of y
@@ -1023,7 +1036,7 @@ class Arc(Shape):
         # Stored geometric features
     def geometric_features(self):
         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]),
              'end': point(a.x[-1], a.y[-1]),
              '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))
 
         # Draw velocity arrows
-        dy = float(height)/(num_arrows-1)
+        dy = old_div(float(height),(num_arrows-1))
         x = start[0]
         y = start[1]
         r = profile(y)  # Test on return type
@@ -1227,7 +1240,7 @@ class VelocityProfile(Shape):
         xs = []
         ys = []
         n = 100
-        dy = float(height)/n
+        dy = old_div(float(height),n)
         for i in range(n+2):
             y = start[1] + i*dy
             vx, vy = profile(y)
@@ -1263,7 +1276,7 @@ class Arrow3(Shape):
         top = (self.bottom[0], self.bottom[1] + self.length)
         main = Line(self.bottom, top)
         #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_left_pt = (top[0] - head_length*sin(head_degrees),
                         top[1] - head_length*cos(head_degrees))
@@ -1341,7 +1354,7 @@ class Text_wArrow(Text):
 class Axis(Shape):
     def __init__(self, start, length, label,
                  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
         (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
     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'):
         Arrow1.__init__(self, start, end, style='->')
         if isinstance(text_spacing, (tuple,list)):
@@ -1434,7 +1447,7 @@ class Force(Arrow1):
 class Axis2(Force):
     def __init__(self, start, length, label,
                  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)),
                           sin(radians(rotation_angle)))
         Force.__init__(start=start, end=length*direction, text=label,
@@ -1451,7 +1464,7 @@ class Gravity(Axis):
     """Downward-pointing gravity arrow with the symbol g."""
     def __init__(self, start, length, fontsize=0):
         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)
         self.shapes['arrow'].set_linecolor('black')
 
@@ -1460,7 +1473,7 @@ class Gravity(Force):
     """Downward-pointing gravity arrow with the symbol g."""
     def __init__(self, start, length, text='$g$', fontsize=0):
         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')
         self.shapes['arrow'].set_linecolor('black')
 
@@ -1475,7 +1488,7 @@ class Distance_wText(Shape):
     horizontal-like arrows, the text is placed the same distance
     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'):
         start = arr2D(start)
         end   = arr2D(end)
@@ -1522,10 +1535,10 @@ class Distance_wText(Shape):
 class Arc_wText(Shape):
     def __init__(self, text, center, radius,
                  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,
                   resolution)
-        mid = arr2D(arc(arc_angle/2.))
+        mid = arr2D(arc(old_div(arc_angle,2.)))
         normal = unit_vec(mid - arr2D(center))
         text_pos = mid + normal*drawing_tool.xrange*text_spacing
         self.shapes = {'arc': arc,
@@ -1555,11 +1568,11 @@ class Composition(Shape):
 class SimplySupportedBeam(Shape):
     def __init__(self, pos, size):
         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)
-        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)
         rectangle = Rectangle(P2, size, h).set_filled_curves(pattern='/')
         self.shapes = {'triangle': triangle, 'rectangle': rectangle}
@@ -1594,7 +1607,7 @@ class ConstantBeamLoad(Shape):
     def __init__(self, lower_left_corner, width, height, num_arrows=10):
         box = Rectangle(lower_left_corner, width, height)
         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_tip = lower_left_corner[1]
         for i in range(num_arrows):
@@ -1608,7 +1621,7 @@ class ConstantBeamLoad(Shape):
 class Moment(Arc_wText):
     def __init__(self, text, center, radius,
                  left=True, counter_clockwise=True,
-                 fontsize=0, text_spacing=1/60.):
+                 fontsize=0, text_spacing=old_div(1,60.)):
         style = '->' if counter_clockwise else '<-'
         start_angle = 90 if left else -90
         Arc_wText.__init__(self, text, center, radius,
@@ -1622,7 +1635,7 @@ class Moment(Arc_wText):
 class Wheel(Shape):
     def __init__(self, center, radius, inner_radius=None, nlines=10):
         if inner_radius is None:
-            inner_radius = radius/5.0
+            inner_radius = old_div(radius,5.0)
 
         outer = Circle(center, radius)
         inner = Circle(center, inner_radius)
@@ -1653,7 +1666,7 @@ class SineWave(Shape):
         self.amplitude = amplitude
         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)
         k = 2*pi/self.wavelength # frequency
         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
     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,
                  num_windings=11, teeth=False):
@@ -1687,9 +1700,9 @@ class Spring(Shape):
             n = n+1
         L = length
         if width is None:
-            w = L/10.
+            w = old_div(L,10.)
         else:
-            w = width/2.0
+            w = old_div(width,2.0)
         s = bar_length
 
         # [0, x, L-x, L], f = (L-2*x)/L
@@ -1720,7 +1733,7 @@ class Spring(Shape):
 
         shapes['bar1'] = Line(B, P0)
         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:
             resolution = 4
         else:
@@ -1740,7 +1753,7 @@ class Spring(Shape):
                                 'length')
         num_windings = Text_wArrow('num_windings',
                                    (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]),
                                        'bar_length',
                                        text_pos=(P0[0]-7*w, P0[1]+w))
@@ -1789,18 +1802,18 @@ class Dashpot(Shape):
     ``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,
                  width=None, dashpot_length=None, piston_pos=None):
         B = start
         L = total_length
         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:
-            w = width/2.0
+            w = old_div(width,2.0)
         s = bar_length
 
         # [0, x, L-x, L], f = (L-2*x)/L
@@ -1824,7 +1837,7 @@ class Dashpot(Shape):
             dashpot_length = f*L
         else:
             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
             P1 = (B[0], B[1]+s+dashpot_length)
         P0 = (B[0], B[1]+s)
@@ -1937,7 +1950,7 @@ class Wavy(Shape):
 
         A_0 = amplitude_of_perturbations
         A_p = 0.3*A_0
-        A_k = k_0/2
+        A_k = old_div(k_0,2)
 
         x = linspace(xmin, xmax, 2001)
 
@@ -1952,7 +1965,7 @@ class Wavy(Shape):
         # to store all the parameters A_0, A_k, etc. as attributes
         self.__call__ = w
 
-class StochasticWavyCurve:
+class StochasticWavyCurve(object):
     """
     Precomputed stochastic wavy graphs.
     There are three graphs with different look.
@@ -2500,7 +2513,7 @@ def _test1():
     set_coordinate_system(xmin=0, xmax=10, ymin=0, ymax=10)
     l1 = Line((0,0), (1,1))
     l1.draw()
-    input(': ')
+    eval(input(': '))
     c1 = Circle((5,2), 1)
     c2 = Circle((6,2), 1)
     w1 = Wheel((7,2), 1)
@@ -2514,7 +2527,7 @@ def _test2():
     set_coordinate_system(xmin=0, xmax=10, ymin=0, ymax=10)
     l1 = Line((0,0), (1,1))
     l1.draw()
-    input(': ')
+    eval(input(': '))
     c1 = Circle((5,2), 1)
     c2 = Circle((6,2), 1)
     w1 = Wheel((7,2), 1)
@@ -2570,8 +2583,8 @@ def _test5():
     c = 6.  # center point of box
     w = 2.  # size of box
     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')
     filled_curves(True)
     r1.draw()
@@ -2591,9 +2604,9 @@ def rolling_wheel(total_rotation_angle):
     angle = 2.0
     pngfiles = []
     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()
-        print 'XXXX BIG PROBLEM WITH ANIMATE!!!'
+        print('XXXX BIG PROBLEM WITH ANIMATE!!!')
         display()
 
 
@@ -2610,10 +2623,10 @@ def rolling_wheel(total_rotation_angle):
         erase()
     cmd = 'convert -delay 50 -loop 1000 %s tmp_movie.gif' \
           % (' '.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__':
@@ -2626,4 +2639,4 @@ if __name__ == '__main__':
         ]
     for func in funcs:
         func()
-        raw_input('Type Return: ')
+        input('Type Return: ')

+ 55 - 22
pysketcher/tests/test_pysketcher.py

@@ -1,5 +1,24 @@
 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():
     drawing_tool.set_coordinate_system(
         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'",},
                 'head right': {'line': "2 (x,y) coords linestyle='dotted'",},},
             '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():
@@ -106,8 +126,9 @@ def test_Distance_wText():
         'c2': "Text_wArrow at (4,0.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():
@@ -128,8 +149,9 @@ def test_Rectangle():
     drawing_tool.savefig('tmp_Rectangle')
 
     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():
     L = 3.0
@@ -149,8 +171,9 @@ def test_Triangle():
     drawing_tool.savefig('tmp_Triangle')
 
     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():
@@ -208,8 +231,10 @@ def test_Arc():
     drawing_tool.savefig('tmp_Arc')
 
     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 = {
         'center': 'text "center" at (-0.2,-0.2)',
         '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': {'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():
     L = 5.0
@@ -257,8 +283,9 @@ def test_Spring():
         'bar1': {'line': "2 (x,y) coords",},
         'bar2': {'line': "2 (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 = {
         '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='<->'",},},},
         '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():
@@ -318,8 +346,9 @@ def test_Dashpot():
                 'rectangle': "5 (x,y) coords fillcolor='' fillpattern='X'",},},
         '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 = {
         'width': {'text': "Text at (6.5,-1.56667)",
@@ -339,8 +368,9 @@ def test_Dashpot():
                        'arrow': {'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():
     drawing_tool.set_coordinate_system(xmin=0, xmax=1.5,
@@ -357,8 +387,9 @@ def test_Wavy():
     drawing_tool.savefig('tmp_Wavy')
 
     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'):
@@ -404,3 +435,5 @@ def _test_test():
         resfile = mplfile.replace('tmp_', 'res_')
         res_files.append(resfile)
     diff_files(new_files, res_files)
+
+test_Arc()