Hans Petter Langtangen 10 年之前
父節點
當前提交
fa56954feb

+ 39 - 0
.gitignore

@@ -0,0 +1,39 @@
+syntax: glob
+# compiled files:
+*.o
+*.so
+*.a
+# temporary files:
+build
+*.bak
+*.swp
+*~
+.*~
+*.old
+tmp*
+temp*
+.#*
+\#*
+# tex files:
+*.log
+*.dvi
+*.aux
+*.blg
+*.idx
+*.nav
+*.out
+*.toc
+*.snm
+*.vrb
+# eclipse files:
+*.cproject
+*.project
+# misc:
+.DS_Store
+.idea
+__pycache__
+_minted-*
+# doconce:
+.*_html_file_collection
+.*.exerinfo
+.*.copyright

+ 1 - 1
doc/src/tut/make.sh

@@ -24,7 +24,7 @@ doconce format html $name --skip_inline_comments --html_style=boostrap_bluegray
 doconce split_html ${html}.html
 
 doconce format sphinx $name --skip_inline_comments
-doconce sphinx_dir author="H. P. Langtangen" version=0.1 theme=pyramid $name
+doconce sphinx_dir copyright="H. P. Langtangen" version=0.1 theme=pyramid $name
 python automake_sphinx.py
 
 # Publish

+ 1 - 1
doc/src/tut/src-tut/vehicle0.py

@@ -45,7 +45,7 @@ drawing_tool.display()
 drawing_tool.savefig('tmp2.png')
 drawing_tool.savefig('tmp2.pdf')
 
-print fig
+print(fig)
 fig.recurse('fig')
 fig.graphviz_dot('fig', False)
 

+ 1 - 1
doc/src/tut/src-tut/vehicle0_dim.py

@@ -54,6 +54,6 @@ drawing_tool.display()
 drawing_tool.savefig('tmp1.png')
 drawing_tool.savefig('tmp1.pdf')
 
-print fig
+print(fig)
 
 raw_input()

+ 1 - 1
doc/src/tut/src-tut/vehicle1.py

@@ -35,7 +35,7 @@ drawing_tool.display()
 drawing_tool.savefig('tmp1.png')
 drawing_tool.savefig('tmp1.pdf')
 
-print fig
+print(fig)
 
 import time
 time.sleep(1)

+ 1 - 1
doc/src/tut/src-tut/vehicle2.py

@@ -42,7 +42,7 @@ def _test():
     vehicle = Vehicle0(w_1, R, L, H)
     vehicle.draw()
     drawing_tool.display()
-    print vehicle
+    print(vehicle)
     vehicle.graphviz_dot('Vehicle0', classname=False)
     vehicle.recurse('vehicle')
 

二進制
examples/FE_comic_strip.pdf


二進制
examples/FE_comic_strip.png


+ 3 - 4
examples/flow_over_gaussian.py

@@ -19,7 +19,8 @@ def gaussian(x):
 
 x = linspace(0, W+L, 51)
 y = gaussian(x)
-wall = Wall(x, y, thickness=-0.3, pattern='|').set_linecolor('brown')
+wall = Wall(x, y, thickness=-0.3, pattern='|', transparent=True).\
+       set_linecolor('brown')
 wall['eraser'].set_linecolor('white')
 def velprofile(y):
     return [2*y*(2*H-y)/H**2, 0]
@@ -30,7 +31,6 @@ symmetry_line.set_linestyle('dashed')
 outlet = Line((W+L,0), (W+L,H))
 outlet.set_linestyle('dashed')
 
-#print repr(inlet_profile)
 fig = Composition({
     'bottom': wall,
     'inlet': inlet_profile,
@@ -55,7 +55,6 @@ symbols = Composition(symbols)
 symbols.draw()
 
 drawing_tool.display()
-drawing_tool.savefig('tmp1.png')
-
+drawing_tool.savefig('tmp1')
 
 raw_input()

+ 30 - 0
examples/layered_medium_2.py

@@ -0,0 +1,30 @@
+from pysketcher import *
+from numpy import exp, linspace
+
+W = 10.
+H = 10.
+a = [0, 5, 10]
+
+drawing_tool.set_coordinate_system(xmin=-1, xmax=W+1,
+                                   ymin=-1, ymax=H+1,
+                                   axis=False)
+drawing_tool.set_linecolor('black')
+drawing_tool.set_fontsize(24)
+
+layers =      {'layer%d' % i: Line((0,a[i]), (W,a[i]))
+               for i in range(len(a))}
+symbols_q =   {'Omega_k%d' % i: Text(r'$\Omega_%d$: $k_%d$' % (i, i),
+                                     (W/2,0.5*(a[i]+a[i+1])))
+               for i in range(len(a)-1)}
+
+sides ={'left': Line((0,0), (0,H)), 'right': Line((W,0), (W,H))}
+d = sides.copy()
+d.update(layers)
+d.update(symbols_q)
+fig = Composition(d)
+
+fig.draw()
+drawing_tool.display()
+drawing_tool.savefig('tmp2')
+
+raw_input()

+ 15 - 8
examples/mesh_function.py

@@ -24,16 +24,23 @@ drawing_tool.set_coordinate_system(t_min, t_max, u_min, u_max, axis=False)
 drawing_tool.set_linecolor('black')
 
 r = 0.005*(t_max-t_min)     # radius of circles placed at mesh points
-u_discrete = Composition({i: Composition(dict(
-    circle=Circle(point(t, u(t)), r).set_filled_curves('black'),
-    u_point=Text('$u^%d$' % i,
-                 point(t, u(t)) + (point(0,3*r)
-                                   if i > 0 else point(-3*r,0))),
-    )) for i, t in enumerate(t_mesh)})
+#import random; random.seed(12)
+perturbations = [0, 0.1, 0.1, 0.2, -0.4, -0.1]
+u_points = {}
+u_values = []
+for i, t in enumerate(t_mesh):
+    u_value = u(t) + perturbations[i]
+    u_values.append(u_value)
+    u_points[i] = Composition(dict(
+        circle=Circle(point(t, u_value), r).set_filled_curves('black'),
+        u_point=Text('$u^%d$' % i,
+                     point(t, u_value) + (point(0,3*r)
+                                          if i > 0 else point(-3*r,0)))))
+u_discrete = Composition(u_points)
 
 interpolant = Composition({
-    i: Line(point(t_mesh[i-1], u(t_mesh[i-1])),
-            point(t_mesh[i], u(t_mesh[i]))).set_linewidth(1)
+    i: Line(point(t_mesh[i-1], u_values[i-1]),
+            point(t_mesh[i], u_values[i])).set_linewidth(1)
     for i in range(1, len(t_mesh))})
 
 axes = Composition(dict(

二進制
examples/pendulum.pdf


二進制
examples/pendulum.png


+ 2 - 0
examples/pendulum.py

@@ -104,6 +104,8 @@ ith = Force(P, P + L/10*unit_vec((-rod_vec[1], rod_vec[0])),
 
 body_diagram['ir'] = ir
 body_diagram['ith'] = ith
+body_diagram['origin'] = Text('$(x_0,y_0)$', P + point(-0.4,-0.1))
+
 body_diagram.draw()
 #drawing_tool.display('Body diagram')
 drawing_tool.savefig('tmp_pendulum5')

二進制
examples/pendulum2.pdf


二進制
examples/pendulum2.png


二進制
examples/pendulum_body_diagram.pdf


二進制
examples/pendulum_body_diagram.png


+ 2 - 2
examples/wheel_on_inclined_plane.py

@@ -93,8 +93,8 @@ def inclined_plane():
     animate(fig, time_points, move, pause_per_frame=0,
             dt=time_points[1]-time_points[0])
 
-    print str(fig)
-    print repr(fig)
+    print(str(fig))
+    print(repr(fig))
 
 inclined_plane()
 raw_input()

+ 22 - 11
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)
@@ -266,7 +277,7 @@ ax.set_aspect('equal')
         if fillcolor or fillpattern:
             if fillpattern != '':
                 fillcolor = 'white'
-            #print '%d coords, fillcolor="%s" linecolor="%s" fillpattern="%s"' % (x.size, fillcolor, linecolor, fillpattern)
+            #print('%d coords, fillcolor="%s" linecolor="%s" fillpattern="%s"' % (x.size, fillcolor, linecolor, fillpattern))
             [line] = self.ax.fill(x, y, fillcolor, edgecolor=linecolor,
                                   linewidth=linewidth, hatch=fillpattern)
             if self.instruction_file:
@@ -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 *

+ 98 - 341
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 += '{'
@@ -434,7 +447,7 @@ class Shape:
             else:
                 class_str = ' (%s)' % \
                             self.shapes[shape].__class__.__name__
-            s += '\n%s%s%s %s' % (
+            s += '\n%s%s%s %s,' % (
                 ' '*indent,
                 shape_str,
                 class_str,
@@ -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))
@@ -1310,7 +1323,7 @@ class Text(Point):
         return 'text "%s" at (%g,%g)' % (self.text, self.x, self.y)
 
     def __repr__(self):
-        return str(self)
+        return repr(str(self))
 
 
 class Text_wArrow(Text):
@@ -1335,13 +1348,13 @@ class Text_wArrow(Text):
                 self.arrow_tip[0], self.arrow_tip[1])
 
     def __repr__(self):
-        return str(self)
+        return repr(str(self))
 
 
 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:
@@ -1728,7 +1741,7 @@ class Spring(Shape):
         q = linspace(0, n, n*resolution + 1)
         x = P0[0] + w*sin(2*pi*q)
         y = P0[1] + q*t
-        shapes['sprial'] = Curve(x, y)
+        shapes['spiral'] = Curve(x, y)
         shapes['bar2'] = Line(P1,P2)
         self.shapes = shapes
 
@@ -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.
@@ -2495,268 +2508,12 @@ See also hplgit.github.io/pysketcher/doc/src/tut/fig-tut/StochasticWavyCurve.png
 # must be easy to find the tip of the arrow
 # Maybe extra dict: self.name['mass'] = Rectangle object - YES!
 
-def test_Axis():
-    drawing_tool.set_coordinate_system(
-        xmin=0, xmax=15, ymin=-7, ymax=8, axis=True,
-        instruction_file='tmp_Axis.py')
-    # Draw normal x and y axis with origin at (7.5, 2)
-    # in the coordinate system of the sketch: [0,15]x[-7,8]
-    x_axis = Axis((7.5,2), 5, 'x', rotation_angle=0)
-    y_axis = Axis((7.5,2), 5, 'y', rotation_angle=90)
-    system = Composition({'x axis': x_axis, 'y axis': y_axis})
-    system.draw()
-    drawing_tool.display()
-
-    # Rotate this system 40 degrees counter clockwise
-    # and draw it with dashed lines
-    system.set_linestyle('dashed')
-    system.rotate(40, (7.5,2))
-    system.draw()
-    drawing_tool.display()
-
-    # Rotate this system another 220 degrees and show
-    # with dotted lines
-    system.set_linestyle('dotted')
-    system.rotate(220, (7.5,2))
-    system.draw()
-    drawing_tool.display()
-
-    drawing_tool.display('Axis')
-    drawing_tool.savefig('tmp_Axis')
-    print repr(system)
-
-def test_Distance_wText():
-    drawing_tool.set_coordinate_system(
-        xmin=0, xmax=10, ymin=0, ymax=6,
-        axis=True, instruction_file='tmp_Distance_wText.py')
-
-    fontsize=14
-    t = r'$ 2\pi R^2 $'  # sample text
-    examples = Composition({
-        'a0': Distance_wText((4,5), (8, 5), t, fontsize),
-        'a6': Distance_wText((4,5), (4, 4), t, fontsize),
-        'a1': Distance_wText((0,2), (2, 4.5), t, fontsize),
-        'a2': Distance_wText((0,2), (2, 0), t, fontsize),
-        'a3': Distance_wText((2,4.5), (0, 5.5), t, fontsize),
-        'a4': Distance_wText((8,4), (10, 3), t, fontsize,
-                             text_spacing=-1./60),
-        'a5': Distance_wText((8,2), (10, 1), t, fontsize,
-                             text_spacing=-1./40, alignment='right'),
-        'c1': Text_wArrow('text_spacing=-1./60',
-                          (4, 3.5), (9, 3.2),
-                          fontsize=10, alignment='left'),
-        'c2': Text_wArrow('text_spacing=-1./40, alignment="right"',
-                          (4, 0.5), (9, 1.2),
-                          fontsize=10, alignment='left'),
-        })
-    examples.draw()
-    drawing_tool.display('Distance_wText and text positioning')
-    drawing_tool.savefig('tmp_Distance_wText')
-
-def test_Rectangle():
-    L = 3.0
-    W = 4.0
-
-    drawing_tool.set_coordinate_system(
-        xmin=0, xmax=2*W, ymin=-L/2, ymax=2*L,
-        axis=True, instruction_file='tmp_Rectangle.py')
-    drawing_tool.set_linecolor('blue')
-    drawing_tool.set_grid(True)
-
-    xpos = W/2
-    r = Rectangle(lower_left_corner=(xpos,0), width=W, height=L)
-    r.draw()
-    r.draw_dimensions()
-    drawing_tool.display('Rectangle')
-    drawing_tool.savefig('tmp_Rectangle')
-
-
-def test_Triangle():
-    L = 3.0
-    W = 4.0
-
-    drawing_tool.set_coordinate_system(
-        xmin=0, xmax=2*W, ymin=-L/2, ymax=1.2*L,
-        axis=True, instruction_file='tmp_Triangle.py')
-    drawing_tool.set_linecolor('blue')
-    drawing_tool.set_grid(True)
-
-    xpos = 1
-    t = Triangle(p1=(W/2,0), p2=(3*W/2,W/2), p3=(4*W/5.,L))
-    t.draw()
-    t.draw_dimensions()
-    drawing_tool.display('Triangle')
-    drawing_tool.savefig('tmp_Triangle')
-
-def test_Arc():
-    L = 4.0
-    W = 4.0
-
-    drawing_tool.set_coordinate_system(
-        xmin=-W/2, xmax=W, ymin=-L/2, ymax=1.5*L,
-        axis=True, instruction_file='tmp_Arc.py')
-    drawing_tool.set_linecolor('blue')
-    drawing_tool.set_grid(True)
-
-    center = point(0,0)
-    radius = L/2
-    start_angle = 60
-    arc_angle = 45
-    a = Arc(center, radius, start_angle, arc_angle)
-    a.set_arrow('->')
-    a.draw()
-
-    R1 = 1.25*radius
-    R2 = 1.5*radius
-    R = 2*radius
-    a.dimensions = {
-        'start_angle': Arc_wText(
-            'start_angle', center, R1, start_angle=0,
-            arc_angle=start_angle, text_spacing=1/10.),
-        'arc_angle': Arc_wText(
-            'arc_angle', center, R2, start_angle=start_angle,
-            arc_angle=arc_angle, text_spacing=1/20.),
-        'r=0': Line(center, center +
-                    point(R*cos(radians(start_angle)),
-                          R*sin(radians(start_angle)))),
-        'r=start_angle': Line(center, center +
-                              point(R*cos(radians(start_angle+arc_angle)),
-                                    R*sin(radians(start_angle+arc_angle)))),
-        'r=start+arc_angle':  Line(center, center +
-                                   point(R, 0)).set_linestyle('dashed'),
-        'radius': Distance_wText(center, a(0), 'radius', text_spacing=1/40.),
-        'center': Text('center', center-point(radius/10., radius/10.)),
-        }
-    for dimension in a.dimensions:
-        dim = a.dimensions[dimension]
-        dim.set_linestyle('dashed')
-        dim.set_linewidth(1)
-        dim.set_linecolor('black')
-
-    a.draw_dimensions()
-    drawing_tool.display('Arc')
-    drawing_tool.savefig('tmp_Arc')
-
-
-def test_Spring():
-    L = 5.0
-    W = 2.0
-
-    drawing_tool.set_coordinate_system(
-        xmin=0, xmax=7*W, ymin=-L/2, ymax=1.5*L,
-        axis=True, instruction_file='tmp_Spring.py')
-    drawing_tool.set_linecolor('blue')
-    drawing_tool.set_grid(True)
-
-    xpos = W
-    s1 = Spring((W,0), L, teeth=True)
-    s1_title = Text('Default Spring',
-                    s1.geometric_features()['end'] + point(0,L/10))
-    s1.draw()
-    s1_title.draw()
-    #s1.draw_dimensions()
-    xpos += 3*W
-    s2 = Spring(start=(xpos,0), length=L, width=W/2.,
-                bar_length=L/6., teeth=False)
-    s2.draw()
-    s2.draw_dimensions()
-    drawing_tool.display('Spring')
-    drawing_tool.savefig('tmp_Spring')
-
-
-def test_Dashpot():
-    L = 5.0
-    W = 2.0
-    xpos = 0
-
-    drawing_tool.set_coordinate_system(
-        xmin=xpos, xmax=xpos+5.5*W, ymin=-L/2, ymax=1.5*L,
-        axis=True, instruction_file='tmp_Dashpot.py')
-    drawing_tool.set_linecolor('blue')
-    drawing_tool.set_grid(True)
-
-    # Default (simple) dashpot
-    xpos = 1.5
-    d1 = Dashpot(start=(xpos,0), total_length=L)
-    d1_title = Text('Dashpot (default)',
-                    d1.geometric_features()['end'] + point(0,L/10))
-    d1.draw()
-    d1_title.draw()
-
-    # Dashpot for animation with fixed bar_length, dashpot_length and
-    # prescribed piston_pos
-    xpos += 2.5*W
-    d2 = Dashpot(start=(xpos,0), total_length=1.2*L, width=W/2,
-                 bar_length=W, dashpot_length=L/2, piston_pos=2*W)
-    d2.draw()
-    d2.draw_dimensions()
-
-    drawing_tool.display('Dashpot')
-    drawing_tool.savefig('tmp_Dashpot')
-
-def test_Wavy():
-    drawing_tool.set_coordinate_system(xmin=0, xmax=1.5,
-                                       ymin=-0.5, ymax=5,
-                                       axis=True,
-                                       instruction_file='tmp_Wavy.py')
-    w = Wavy(main_curve=lambda x: 1 + sin(2*x),
-             interval=[0,1.5],
-             wavelength_of_perturbations=0.3,
-             amplitude_of_perturbations=0.1,
-             smoothness=0.05)
-    w.draw()
-    drawing_tool.display('Wavy')
-    drawing_tool.savefig('tmp_Wavy')
-
-def diff_files(files1, files2, mode='HTML'):
-    import difflib, time
-    n = 3
-    for fromfile, tofile in zip(files1, files2):
-        fromdate = time.ctime(os.stat(fromfile).st_mtime)
-        todate = time.ctime(os.stat(tofile).st_mtime)
-        fromlines = open(fromfile, 'U').readlines()
-        tolines = open(tofile, 'U').readlines()
-        diff_html = difflib.HtmlDiff().\
-                    make_file(fromlines,tolines,
-                              fromfile,tofile,context=True,numlines=n)
-        diff_plain = difflib.unified_diff(fromlines, tolines, fromfile, tofile, fromdate, todate, n=n)
-        filename_plain = fromfile + '.diff.txt'
-        filename_html = fromfile + '.diff.html'
-        if os.path.isfile(filename_plain):
-            os.remove(filename_plain)
-        if os.path.isfile(filename_html):
-            os.remove(filename_html)
-        f = open(filename_plain, 'w')
-        f.writelines(diff_plain)
-        f.close()
-        size = os.path.getsize(filename_plain)
-        if size > 4:
-            print 'found differences:', fromfile, tofile
-            f = open(filename_html, 'w')
-            f.writelines(diff_html)
-            f.close()
-
-
-def test_test():
-    os.chdir('test')
-    funcs = [name for name in globals() if name.startswith('test_') and callable(globals()[name])]
-    funcs.remove('test_test')
-    new_files = []
-    res_files = []
-    for func in funcs:
-        mplfile = func.replace('test_', 'tmp_') + '.py'
-        #exec(func + '()')
-        new_files.append(mplfile)
-        resfile = mplfile.replace('tmp_', 'res_')
-        res_files.append(resfile)
-    diff_files(new_files, res_files)
-
 
 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)
@@ -2770,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)
@@ -2826,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()
@@ -2847,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()
 
 
@@ -2866,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__':
@@ -2882,4 +2639,4 @@ if __name__ == '__main__':
         ]
     for func in funcs:
         func()
-        raw_input('Type Return: ')
+        input('Type Return: ')

+ 2 - 0
pysketcher/tests/clean.sh

@@ -0,0 +1,2 @@
+#!/bin/sh
+rm -rf __pycache__ tmp*

+ 448 - 0
pysketcher/tests/test_pysketcher.py

@@ -0,0 +1,448 @@
+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 zip
+from builtins import *
+from past.utils import old_div
+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,
+        instruction_file='tmp_Axis.py')
+    # Draw normal x and y axis with origin at (7.5, 2)
+    # in the coordinate system of the sketch: [0,15]x[-7,8]
+    x_axis = Axis((7.5,2), 5, 'x', rotation_angle=0)
+    y_axis = Axis((7.5,2), 5, 'y', rotation_angle=90)
+    system = Composition({'x axis': x_axis, 'y axis': y_axis})
+    system.draw()
+    drawing_tool.display()
+
+    # Rotate this system 40 degrees counter clockwise
+    # and draw it with dashed lines
+    system.set_linestyle('dashed')
+    system.rotate(40, (7.5,2))
+    system.draw()
+    drawing_tool.display()
+
+    # Rotate this system another 220 degrees and show
+    # with dotted lines
+    system.set_linestyle('dotted')
+    system.rotate(220, (7.5,2))
+    system.draw()
+    drawing_tool.display()
+
+    drawing_tool.display('Axis')
+    drawing_tool.savefig('tmp_Axis')
+    expected = {
+        'x axis': {
+            'arrow': {
+                'head left': {'line': "2 (x,y) coords linestyle='dotted'",},
+                'line': {'line': "2 (x,y) coords linestyle='dotted'",},
+                'head right': {'line': "2 (x,y) coords linestyle='dotted'",},},
+            'label': "Text at (6.57388,-3.25231)",},
+        'y axis': {
+            'arrow': {
+                'head left': {'line': "2 (x,y) coords linestyle='dotted'",},
+                'line': {'line': "2 (x,y) coords linestyle='dotted'",},
+                'head right': {'line': "2 (x,y) coords linestyle='dotted'",},},
+            'label': "Text at (12.7523,1.07388)",},}
+    computed = eval(repr(system))
+    msg = 'expected=%s, computed=%s' % (expected, computed)
+    assert equal_dict(computed, expected), msg
+
+
+def test_Distance_wText():
+    drawing_tool.set_coordinate_system(
+        xmin=0, xmax=10, ymin=0, ymax=6,
+        axis=True, instruction_file='tmp_Distance_wText.py')
+
+    fontsize=14
+    t = r'$ 2\pi R^2 $'  # sample text
+    examples = Composition({
+        'a0': Distance_wText((4,5), (8, 5), t, fontsize),
+        'a6': Distance_wText((4,5), (4, 4), t, fontsize),
+        'a1': Distance_wText((0,2), (2, 4.5), t, fontsize),
+        'a2': Distance_wText((0,2), (2, 0), t, fontsize),
+        'a3': Distance_wText((2,4.5), (0, 5.5), t, fontsize),
+        'a4': Distance_wText((8,4), (10, 3), t, fontsize,
+                             text_spacing=old_div(-1.,60)),
+        'a5': Distance_wText((8,2), (10, 1), t, fontsize,
+                             text_spacing=old_div(-1.,40), alignment='right'),
+        'c1': Text_wArrow('text_spacing=-1./60',
+                          (4, 3.5), (9, 3.2),
+                          fontsize=10, alignment='left'),
+        'c2': Text_wArrow('text_spacing=-1./40, alignment="right"',
+                          (4, 0.5), (9, 1.2),
+                          fontsize=10, alignment='left'),
+        })
+    examples.draw()
+    drawing_tool.display('Distance_wText and text positioning')
+    drawing_tool.savefig('tmp_Distance_wText')
+
+    expected = {
+        'a1': {
+            'text': "Text at (1.13014,3.14588)",
+            'arrow': {
+                'arrow': {
+                    'line': "2 (x,y) coords linecolor='k' linewidth=1 arrow='<->'",},},},
+        'a0': {
+            'text': "Text at (6,5.16667)",
+            'arrow': {'arrow': {
+                'line': "2 (x,y) coords linecolor='k' linewidth=1 arrow='<->'",},},},
+        'a3': {
+            'text': "Text at (1.07454,5.14907)",
+            'arrow': {'arrow': {
+                'line': "2 (x,y) coords linecolor='k' linewidth=1 arrow='<->'",},},},
+        'a2': {
+            'text': "Text at (1.11785,1.11785)",
+            'arrow': {'arrow': {
+                'line': "2 (x,y) coords linecolor='k' linewidth=1 arrow='<->'",},},},
+        'a5': {
+            'text': "Text at (8.8882,1.27639)",
+            'arrow': {'arrow': {
+                'line': "2 (x,y) coords linecolor='k' linewidth=1 arrow='<->'",},},},
+        'a4': {
+            'text': "Text at (8.92546,3.35093)",
+            'arrow': {'arrow': {
+                'line': "2 (x,y) coords linecolor='k' linewidth=1 arrow='<->'",},},},
+        'a6': {
+            'text': "Text at (4.16667,4.5)",
+            'arrow': {'arrow': {
+                'line': "2 (x,y) coords linecolor='k' linewidth=1 arrow='<->'",},},},
+        'c2': "Text_wArrow at (4,0.5)",
+        'c1': "Text_wArrow at (4,3.5)",
+        }
+    computed = eval(repr(examples))
+    msg = 'expected=%s, computed=%s' % (expected, computed)
+    assert equal_dict(computed, expected), msg
+
+
+def test_Rectangle():
+    L = 3.0
+    W = 4.0
+
+    drawing_tool.set_coordinate_system(
+        xmin=0, xmax=2*W, ymin=old_div(-L,2), ymax=2*L,
+        axis=True, instruction_file='tmp_Rectangle.py')
+    drawing_tool.set_linecolor('blue')
+    drawing_tool.set_grid(True)
+
+    xpos = old_div(W,2)
+    r = Rectangle(lower_left_corner=(xpos,0), width=W, height=L)
+    r.draw()
+    r.draw_dimensions()
+    drawing_tool.display('Rectangle')
+    drawing_tool.savefig('tmp_Rectangle')
+
+    expected = {'rectangle': "5 (x,y) coords",}
+    computed = eval(repr(r))
+    msg = 'expected=%s, computed=%s' % (expected, computed)
+    assert equal_dict(computed, expected), msg
+
+def test_Triangle():
+    L = 3.0
+    W = 4.0
+
+    drawing_tool.set_coordinate_system(
+        xmin=0, xmax=2*W, ymin=old_div(-L,2), ymax=1.2*L,
+        axis=True, instruction_file='tmp_Triangle.py')
+    drawing_tool.set_linecolor('blue')
+    drawing_tool.set_grid(True)
+
+    xpos = 1
+    t = Triangle(p1=(old_div(W,2),0), p2=(3*W/2,old_div(W,2)), p3=(4*W/5.,L))
+    t.draw()
+    t.draw_dimensions()
+    drawing_tool.display('Triangle')
+    drawing_tool.savefig('tmp_Triangle')
+
+    expected = {'triangle': "4 (x,y) coords",}
+    computed = eval(repr(t))
+    msg = 'expected=%s, computed=%s' % (expected, computed)
+    assert equal_dict(computed, expected), msg
+
+
+def test_Arc():
+    L = 4.0
+    W = 4.0
+
+    drawing_tool.set_coordinate_system(
+        xmin=old_div(-W,2), xmax=W, ymin=old_div(-L,2), ymax=1.5*L,
+        axis=True, instruction_file='tmp_Arc.py')
+    drawing_tool.set_linecolor('blue')
+    drawing_tool.set_grid(True)
+
+    center = point(0,0)
+    radius = old_div(L,2)
+    start_angle = 60
+    arc_angle = 45
+    a = Arc(center, radius, start_angle, arc_angle)
+    a.draw()
+
+    R1 = 1.25*radius
+    R2 = 1.5*radius
+    R = 2*radius
+    a.dimensions = {
+        'start_angle':
+        Arc_wText(
+            'start_angle', center, R1, start_angle=0,
+            arc_angle=start_angle, text_spacing=old_div(1,10.)),
+        'arc_angle':
+        Arc_wText(
+            'arc_angle', center, R2, start_angle=start_angle,
+            arc_angle=arc_angle, text_spacing=old_div(1,20.)),
+        'r=0':
+        Line(center, center +
+             point(R*cos(radians(start_angle)),
+                   R*sin(radians(start_angle)))),
+        'r=start_angle':
+        Line(center, center +
+             point(R*cos(radians(start_angle+arc_angle)),
+                   R*sin(radians(start_angle+arc_angle)))),
+        'r=start+arc_angle':
+        Line(center, center +
+             point(R, 0)).set_linestyle('dashed'),
+        'radius': Distance_wText(center, a(0), 'radius', text_spacing=old_div(1,40.)),
+        'center': Text('center', center-point(old_div(radius,10.), old_div(radius,10.))),
+        }
+    for dimension in a.dimensions:
+        if dimension.startswith('r='):
+            dim = a.dimensions[dimension]
+            dim.set_linestyle('dashed')
+            dim.set_linewidth(1)
+            dim.set_linecolor('black')
+
+    a.draw_dimensions()
+    drawing_tool.display('Arc')
+    drawing_tool.savefig('tmp_Arc')
+
+    expected = {'arc': "181 (x,y) coords"}
+    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)",
+                        'arc': {'arc': "181 (x,y) coords",},},
+        'r=start+arc_angle': {
+            'line': "2 (x,y) coords linecolor='k' linewidth=1 linestyle='dashed'",},
+        'r=0': {'line': "2 (x,y) coords linecolor='k' linewidth=1 linestyle='dashed'",},
+        'radius': {'text': "Text at (0.629904,0.791025)",
+                   'arrow': {'arrow': {
+                       'line': "2 (x,y) coords linecolor='k' linewidth=1 arrow='<->'",},},},
+        'r=start_angle': {'line': "2 (x,y) coords linecolor='k' linewidth=1 linestyle='dashed'",},
+        'arc_angle': {'text': "Text at (0.430736,3.27177)",
+                      'arc': {'arc': "181 (x,y) coords",},}
+        }
+    computed = eval(repr(a.dimensions))
+    msg = 'expected=%s, computed=%s' % (expected, computed)
+    assert equal_dict(computed, expected), msg
+
+def test_Spring():
+    L = 5.0
+    W = 2.0
+
+    drawing_tool.set_coordinate_system(
+        xmin=0, xmax=7*W, ymin=old_div(-L,2), ymax=1.5*L,
+        axis=True, instruction_file='tmp_Spring.py')
+    drawing_tool.set_linecolor('blue')
+    drawing_tool.set_grid(True)
+
+    xpos = W
+    s1 = Spring((W,0), L, teeth=True)
+    s1_title = Text('Default Spring',
+                    s1.geometric_features()['end'] + point(0,old_div(L,10)))
+    s1.draw()
+    s1_title.draw()
+    #s1.draw_dimensions()
+    xpos += 3*W
+    s2 = Spring(start=(xpos,0), length=L, width=old_div(W,2.),
+                bar_length=old_div(L,6.), teeth=False)
+    s2.draw()
+    s2.draw_dimensions()
+    drawing_tool.display('Spring')
+    drawing_tool.savefig('tmp_Spring')
+
+    # Check s1 and s1.dimensions
+    expected = {
+        'bar1': {'line': "2 (x,y) coords",},
+        'bar2': {'line': "2 (x,y) coords",},
+        'spiral': "45 (x,y) coords",}
+    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)",
+                        'arrow': {'arrow': {
+                            'line': "2 (x,y) coords linecolor='k' linewidth=1 arrow='<->'",},},},
+        'bar_length2': {'text': "Text_wArrow at (-1.5,5.5)",
+                        'arrow': {'arrow': {
+                            'line': "2 (x,y) coords linecolor='k' linewidth=1 arrow='<->'",},},},
+        'width': {'text': "Text at (2,-1.51667)",
+                  'arrow': {'arrow': {
+                      'line': "2 (x,y) coords linecolor='k' linewidth=1 arrow='<->'",},},},
+        'start': 'annotation "start" at (1.25,-0.75) with arrow to (2,0)',
+        'length': {'text': "Text at (3.73333,2.5)",
+                   'arrow': {'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)'
+        }
+    computed = eval(repr(s1.dimensions))
+    msg = 'expected=%s, computed=%s' % (expected, computed)
+    assert equal_dict(computed, expected), msg
+
+
+def test_Dashpot():
+    L = 5.0
+    W = 2.0
+    xpos = 0
+
+    drawing_tool.set_coordinate_system(
+        xmin=xpos, xmax=xpos+5.5*W, ymin=old_div(-L,2), ymax=1.5*L,
+        axis=True, instruction_file='tmp_Dashpot.py')
+    drawing_tool.set_linecolor('blue')
+    drawing_tool.set_grid(True)
+
+    # Default (simple) dashpot
+    xpos = 1.5
+    d1 = Dashpot(start=(xpos,0), total_length=L)
+    d1_title = Text('Dashpot (default)',
+                    d1.geometric_features()['end'] + point(0,old_div(L,10)))
+    d1.draw()
+    d1_title.draw()
+
+    # Dashpot for animation with fixed bar_length, dashpot_length and
+    # prescribed piston_pos
+    xpos += 2.5*W
+    d2 = Dashpot(start=(xpos,0), total_length=1.2*L, width=old_div(W,2),
+                 bar_length=W, dashpot_length=old_div(L,2), piston_pos=2*W)
+    d2.draw()
+    d2.draw_dimensions()
+
+    drawing_tool.display('Dashpot')
+    drawing_tool.savefig('tmp_Dashpot')
+
+    expected = {
+        'line start': {'line': "2 (x,y) coords",},
+        'piston': {
+            'line': {'line': "2 (x,y) coords",},
+            'rectangle': {
+                'rectangle': "5 (x,y) coords fillcolor='' fillpattern='X'",},},
+        'pot': "4 (x,y) coords",
+        }
+    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)",
+                  'arrow': {'arrow': {
+                      'line': "2 (x,y) coords linecolor='k' linewidth=1 arrow='<->'",},},},
+        'start': 'annotation "start" at (5.75,-0.75) with arrow to (6.5,0)',
+        'bar_length': {'text': "Text_wArrow at (3.5,1.5)",
+                       'arrow': {'arrow': {
+                           'line': "2 (x,y) coords linecolor='k' linewidth=1 arrow='<->'",},},},
+        'total_length': {'text': "Text_wArrow at (8.75,5)",
+                         'arrow': {'arrow': {
+                             'line': "2 (x,y) coords linecolor='k' linewidth=1 arrow='<->'",},},},
+        'dashpot_length': {'text': "Text_wArrow at (7,-0.5)",
+                           'arrow': {'arrow': {
+                               'line': "2 (x,y) coords linecolor='k' linewidth=1 arrow='<->'",},},},
+        'piston_pos': {'text': "Text_wArrow at (3.5,3.6875)",
+                       'arrow': {'arrow': {
+                           'line': "2 (x,y) coords linecolor='k' linewidth=1 arrow='<->'",},},}
+        }
+    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,
+                                       ymin=-0.5, ymax=5,
+                                       axis=True,
+                                       instruction_file='tmp_Wavy.py')
+    w = Wavy(main_curve=lambda x: 1 + sin(2*x),
+             interval=[0,1.5],
+             wavelength_of_perturbations=0.3,
+             amplitude_of_perturbations=0.1,
+             smoothness=0.05)
+    w.draw()
+    drawing_tool.display('Wavy')
+    drawing_tool.savefig('tmp_Wavy')
+
+    expected = {'wavy': "2001 (x,y) coords",}
+    computed = eval(repr(w))
+    msg = 'expected=%s, computed=%s' % (expected, computed)
+    assert equal_dict(computed, expected), msg
+
+
+def diff_files(files1, files2, mode='HTML'):
+    import difflib, time
+    n = 3
+    for fromfile, tofile in zip(files1, files2):
+        fromdate = time.ctime(os.stat(fromfile).st_mtime)
+        todate = time.ctime(os.stat(tofile).st_mtime)
+        fromlines = open(fromfile, 'U').readlines()
+        tolines = open(tofile, 'U').readlines()
+        diff_html = difflib.HtmlDiff().\
+                    make_file(fromlines,tolines,
+                              fromfile,tofile,context=True,numlines=n)
+        diff_plain = difflib.unified_diff(fromlines, tolines, fromfile, tofile, fromdate, todate, n=n)
+        filename_plain = fromfile + '.diff.txt'
+        filename_html = fromfile + '.diff.html'
+        if os.path.isfile(filename_plain):
+            os.remove(filename_plain)
+        if os.path.isfile(filename_html):
+            os.remove(filename_html)
+        f = open(filename_plain, 'w')
+        f.writelines(diff_plain)
+        f.close()
+        size = os.path.getsize(filename_plain)
+        if size > 4:
+            print('found differences:', fromfile, tofile)
+            f = open(filename_html, 'w')
+            f.writelines(diff_html)
+            f.close()
+
+def _test_test():
+    """Compare files."""
+    # Does not work yet.
+    os.chdir('test')
+    funcs = [name for name in globals() if name.startswith('test_') and callable(globals()[name])]
+    funcs.remove('test_test')
+    new_files = []
+    res_files = []
+    for func in funcs:
+        mplfile = func.replace('test_', 'tmp_') + '.py'
+        #exec(func + '()')
+        new_files.append(mplfile)
+        resfile = mplfile.replace('tmp_', 'res_')
+        res_files.append(resfile)
+    diff_files(new_files, res_files)
+
+test_Arc()