Ver código fonte

First commit

Hans Petter Langtangen 13 anos atrás
commit
896cd26ac7

+ 0 - 0
.nojekyll


+ 0 - 0
README


BIN
doc/src/figs-pysketcher/wheel_on_inclined_plane.png


+ 92 - 0
examples/wheel_on_inclined_plane.py

@@ -0,0 +1,92 @@
+from shapes import *
+
+print dir()
+print 'drawin_tool' in dir()
+
+def inclined_plane():
+    drawing_tool.set_coordinate_system(xmin=0, xmax=15,
+                                       ymin=-1, ymax=10,
+                                       #axis=True,
+                                       )
+    #drawing_tool.set_grid(True)
+    fontsize = 18
+    from math import tan, radians
+
+    theta = 30.
+    L = 10.
+    a = 1.
+    B = point(a+L, 0)
+    A = point(a, tan(radians(theta))*L)
+
+    wall = CurveWall(x=[A[0], B[0]], y=[A[1], B[1]], thickness=-0.25)
+
+    angle = ArcSymbol(r'$\theta$', center=B, radius=3,
+                      start_degrees=180-theta, opening_degrees=theta,
+                      fontsize=fontsize)
+    angle.set_linecolor('black')
+    angle.set_linewidth(1)
+
+    ground = Line((B[0]-L/10., 0), (B[0]-L/2.,0))
+    ground.set_linecolor('black')
+    ground.set_linestyle('dashed')
+    ground.set_linewidth(1)
+
+    r = 1  # radius of wheel
+    help_line = Line(A, B)
+    x = a + 3*L/10.; y = help_line(x=x)
+    contact = point(x, y)
+    normal_vec = point(sin(radians(theta)), cos(radians(theta)))
+    c = contact + r*normal_vec
+    outer_wheel = Circle(c, r)
+    outer_wheel.set_linecolor('blue')
+    outer_wheel.set_filled_curves('blue')
+    hole = Circle(c, r/2.)
+    hole.set_linecolor('blue')
+    hole.set_filled_curves('white')
+
+    wheel = Compose({'outer': outer_wheel, 'inner': hole})
+
+    drawing_tool.set_linecolor('black')
+    N_arr = Arrow3((4,2), 2)
+    N_arr.rotate(-theta, (4,4))
+    N_text = Text('$N$', (3.7,2.6), fontsize=fontsize)
+    N_force = Compose({'arrow': N_arr, 'symbol': N_text})
+
+    g = Gravity(c, 2.5)
+
+    x_const = Line(contact, contact + point(0,4))
+    x_const.set_linestyle('dotted')
+    x_const.rotate(-theta, contact)
+    x_axis_start = point(5.5, x_const(x=5.5))
+    x_axis = Axis(x_axis_start, 2*L/5, '$x$', rotation_angle=-theta)
+
+    body  = Compose({'wheel': wheel, 'N force': N_force, 'g': g})
+    fixed = Compose({'angle': angle, 'inclined wall': wall,
+                     'wheel': wheel, 'ground': ground,
+                     'x start': x_const, 'x axis': x_axis})
+
+    fig = Compose({'body': body, 'fixed elements': fixed})
+
+    #import copy
+    #body2 = copy.deepcopy(body)
+    #body2.translate(3, 0)
+    #body2.draw()
+
+    fig.draw()
+    drawing_tool.savefig('tmp.png')
+    drawing_tool.display()
+    import time
+    time.sleep(1)
+    tangent_vec = point(normal_vec[1], -normal_vec[0])
+    print 'loop'
+    for t in range(7):
+        drawing_tool.erase()
+        body.translate(0.2*t*tangent_vec)
+        time.sleep(0.5)
+        fig.draw()
+        drawing_tool.display()
+    print str(fig)
+    print repr(fig)
+
+inclined_plane()
+raw_input()

+ 131 - 0
misc/GnuplotDraw.py

@@ -0,0 +1,131 @@
+import os
+
+class GnuplotDraw:
+    line_colors = {'red': 1, 'green': 2, 'blue': 3, 'cyan': 5,
+                   'magenta': 4, 'aqua': 5, 'purple': 4,
+                   'yellow': 7, 'black': -1}
+
+    def __init__(self, xmin, xmax, ymin, ymax, axis=False,
+                 instruction_file=None):
+        """
+        Define the drawing area [xmin,xmax]x[ymin,ymax].
+        axis: None or False means that axes with tickmarks
+        are not drawn.
+        instruction_file: name of file where all the instructions
+        are recorded.
+        """
+        self.xmin, self.xmax, self.ymin, self.ymax = \
+             float(xmin), float(xmax), float(ymin), float(ymax)
+        self.axis = axis
+
+        # 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)
+        self.xsize = 500
+        self.ysize = 500*ratio
+        geometry = '%dx%d' % (self.xsize, self.ysize)
+        self.g = os.popen('gnuplot -geometry %s -persist' % geometry, 'w')
+
+        if instruction_file is not None:
+            # Collect all commands in __call__ in a file
+            self.instruction_file = open(instruction_file, 'w')
+        else:
+            self.instruction_file = None
+
+        if not axis:
+            self("unset xtics; unset ytics\n")
+
+        self("set xrange[%g:%g]; set yrange[%s:%s]\n" %
+             (xmin, xmax, ymin, ymax))
+        self("set size square")  # equal aspect ratio
+
+        self.erase()
+        self.file_counter = 0
+
+        self.set_linecolor('red')
+        self.set_linewidth(2)
+        self.filled_curves(False)
+
+    def set_linecolor(self, color):
+        """Change the color of lines."""
+        self.linecolor = GnuplotDraw.line_colors[color]
+
+    def set_linewidth(self, width):
+        """Change the line width (int, starts at 1)."""
+        self.linewidth = width
+
+    def filled_curves(self, on=True):
+        """Fill area inside curves with current line color."""
+        self.filledcurves = on
+
+    def erase(self):
+        """Erase the current figure."""
+        # Don't set self.counter=0 here because that will overwrite
+        # older files being plotted by Gnuplot.
+        self.plot_commands = []
+
+    def define_curve(self, x, y):
+        """Define a curve with coordinates x and y (arrays)."""
+        self.file_counter += 1
+        filename = '.tmp_%04d' % self.file_counter
+        f = open(filename, 'w')
+        for xi, yi in zip(x, y):
+            f.write('%g %g\n' % (xi, yi))
+        f.close()
+        if self.filledcurves:
+            with_value = 'filledcurves'
+        else:
+            with_value = 'lines'
+        self.plot_commands.append('"%s" with %s title "" lt %s lw %s' %
+            (filename, with_value, self.linecolor, self.linewidth))
+
+    def display(self):
+        """Display the figure."""
+        if not self.plot_commands:
+            return
+
+        # The set output command is important because if hardcopy is
+        # called and output is set to a file, a new set term x11 will
+        # overwrite the file with empty content (see the resulting
+        # set of commands to realize how PNG and x11 plots are made
+        # in sequence).
+        plotcommand = 'set output; set term x11; plot ' + \
+                      ', '.join(self.plot_commands) + '\n'
+        self(plotcommand)
+        #self('pause 2\n')
+
+    def hardcopy(self, name):
+        """Save figure in PNG file name.png."""
+        # Ratio is preserved by setting size 10cm,10cm for example
+        # (at least this works for postscript)
+
+        # Important: just running replot will not work if not display
+        # is called so we make the full plot command here.
+        plotcommand = 'plot ' + ', '.join(self.plot_commands) + '\n'
+
+        self("""
+set term png small size %d,%d
+set output "%s.png"
+%s
+"""% (self.xsize, self.ysize, name, plotcommand))
+
+    def text(self, text, position, alignment='center', fontsize=18):
+        """
+        Write text at a position (centered, left, right - according
+        to the alignment string). position is a 2-tuple.
+        """
+        self('set label "%s" at %s,%s %s font "Helvetica,%d" front; replot\n' %
+             (text, position[0], position[1], alignment, fontsize))
+
+    def length_symbol(self, start, stop, symbol):
+        #Divide arrow into two pieces, a textbox in the middle.
+        #set arrow from 0,0 to 2,3 linewidth 1 heads
+        #set label "..." at x,y center rotate by <degrees> font "Times,18" front
+        pass
+
+    def __call__(self, command):
+        if self.instruction_file is not None:
+            self.instruction_file.write(command)
+        self.g.write(command)
+        self.g.flush()
+

+ 330 - 0
pysketcher/MatplotlibDraw.py

@@ -0,0 +1,330 @@
+import os
+import matplotlib
+matplotlib.use('TkAgg')
+import matplotlib.pyplot as mpl
+import numpy as np
+
+class MatplotlibDraw:
+    line_colors = {'red': 'r', 'green': 'g', 'blue': 'b', 'cyan': 'c',
+                   'magenta': 'm', 'purple': 'p',
+                   'yellow': 'y', 'black': 'k', 'white': 'w', '': ''}
+    def __init__(self):
+        self.instruction_file = None
+
+    def set_instruction_file(self, filename='tmp_mpl.py'):
+        """
+        instruction_file: name of file where all the instructions
+        are recorded.
+        """
+        self.instruction_file = filename
+
+    def set_coordinate_system(self, xmin, xmax, ymin, ymax, axis=False):
+        """
+        Define the drawing area [xmin,xmax]x[ymin,ymax].
+        axis: None or False means that axes with tickmarks
+        are not drawn.
+        """
+        self.mpl = mpl
+        self.xmin, self.xmax, self.ymin, self.ymax = \
+             float(xmin), float(xmax), float(ymin), float(ymax)
+        self.xrange = self.xmax - self.xmin
+        self.yrange = self.ymax - self.ymin
+        self.axis = axis
+        if self.instruction_file:
+            self.instruction_file = open(self.instruction_file, 'w')
+        else:
+            self.instruction_file = None
+
+        # 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)
+        self.xsize = 800  # pixel size
+        self.ysize = self.xsize*ratio
+        geometry = '%dx%d' % (self.xsize, self.ysize)
+        # See http://stackoverflow.com/questions/7449585/how-do-you-set-the-absolute-position-of-figure-windows-with-matplotlib
+
+        self.mpl.ion()  # important for interactive drawing and animation
+        if self.instruction_file is not None:
+            self.instruction_file.write("""\
+import matplotlib.pyplot as mpl
+
+mpl.ion()  # for interactive drawing
+""")
+        self._make_axes(new_figure=True)
+
+        manager = self.mpl.get_current_fig_manager()
+        manager.window.wm_geometry(geometry)
+
+        self.set_linecolor('red')
+        self.set_linewidth(2)
+        self.set_linestyle('solid')
+        self.set_filled_curves()
+
+    def _make_axes(self, new_figure=False):
+        if new_figure:
+            self.fig = self.mpl.figure()
+        self.ax = self.fig.gca()
+        self.ax.set_xlim(self.xmin, self.xmax)
+        self.ax.set_ylim(self.ymin, self.ymax)
+        self.ax.set_aspect('equal')  # extent of 1 unit is the same on the axes
+
+        if not self.axis:
+            self.mpl.axis('off')
+            axis_cmd = "mpl.axis('off')  # do not show axes with tickmarks\n"
+        else:
+            axis_cmd = ''
+
+        if self.instruction_file is not None:
+            fig = 'fig = mpl.figure()\n' if new_figure else ''
+            self.instruction_file.write("""\
+%s
+ax = fig.gca()
+xmin, xmax, ymin, ymax = %s, %s, %s, %s
+ax.set_xlim(xmin, xmax)
+ax.set_ylim(ymin, ymax)
+ax.set_aspect('equal')
+%s
+
+""" % (fig, self.xmin, self.xmax, self.ymin, self.ymax, axis_cmd))
+
+    def set_linecolor(self, color):
+        """Change the color of lines."""
+        self.linecolor = MatplotlibDraw.line_colors[color]
+
+    def set_linestyle(self, style):
+        """Change line style: 'solid', 'dashed', 'dashdot', 'dotted'."""
+        if not style in ('solid', 'dashed', 'dashdot', 'dotted'):
+            raise ValueError('Illegal line style: %s' % style)
+        self.linestyle = style
+
+    def set_linewidth(self, width):
+        """Change the line width (int, starts at 1)."""
+        self.linewidth = width
+
+    def set_filled_curves(self, color='', hatch=''):
+        """Fill area inside curves with current line color."""
+        if color is False:
+            self.fillcolor = ''
+            self.fillhatch = ''
+        else:
+            self.fillcolor = color if len(color) == 1 else \
+                         MatplotlibDraw.line_colors[color]
+            self.fillhatch = hatch
+
+    def set_grid(self, on=False):
+        self.mpl.grid(on)
+        if self.instruction_file is not None:
+            self.instruction_file.write("\nmpl.grid(%s)\n" % str(on))
+
+    def erase(self):
+        """Erase the current figure."""
+        self.mpl.delaxes()
+        if self.instruction_file is not None:
+            self.instruction_file.write("\nmpl.delaxes()  # erase\n")
+
+        self._make_axes(new_figure=False)
+
+    def define_curve(self, x, y,
+                     linestyle=None, linewidth=None,
+                     linecolor=None, arrow=None,
+                     fillcolor=None, fillhatch=None):
+        """Define a curve with coordinates x and y (arrays)."""
+        self.xdata = np.asarray(x, dtype=np.float)
+        self.ydata = np.asarray(y, dtype=np.float)
+
+        if linestyle is None:
+            # use "global" linestyle
+            linestyle = self.linestyle
+        if linecolor is None:
+            linecolor = self.linecolor
+        if linewidth is None:
+            linewidth = self.linewidth
+        if fillcolor is None:
+            fillcolor = self.fillcolor
+        if fillhatch is None:
+            fillhatch = self.fillhatch
+
+        if self.instruction_file is not None:
+            import pprint
+            self.instruction_file.write('x = %s\n' % \
+                                        pprint.pformat(self.xdata.tolist()))
+            self.instruction_file.write('y = %s\n' % \
+                                        pprint.pformat(self.ydata.tolist()))
+
+        if fillcolor or fillhatch:
+            self.ax.fill(x, y, fillcolor, edgecolor=linecolor,
+                         hatch=fillhatch)
+            if self.instruction_file is not None:
+                self.instruction_file.write("ax.fill(x, y, '%s', edgecolor='%s', hatch='%s')\n" % (linecolor, fillcolor, fillhatch))
+        else:
+            self.ax.plot(x, y, linecolor, linewidth=linewidth,
+                         linestyle=linestyle)
+            if self.instruction_file is not None:
+                self.instruction_file.write("ax.plot(x, y, '%s', linewidth=%d, linestyle='%s')\n" % (linecolor, linewidth, linestyle))
+        if arrow:
+            if not arrow in ('start', 'end', 'both'):
+                raise ValueError("arrow argument must be 'start', 'end', or 'both', not %s" % repr(arrow))
+
+            # Add arrow to first and/or last segment
+            start = arrow == 'start' or arrow == 'both'
+            end = arrow == 'end' or arrow == 'both'
+            if start:
+                x_s, y_s = x[1], y[1]
+                dx_s, dy_s = x[0]-x[1], y[0]-y[1]
+                self.arrow(x_s, y_s, dx_s, dy_s)
+            if end:
+                x_e, y_e = x[-2], y[-2]
+                dx_e, dy_e = x[-1]-x[-2], y[-1]-y[-2]
+                self.arrow(x_e, y_e, dx_e, dy_e)
+
+    def display(self):
+        """Display the figure. Last possible command."""
+        self.mpl.draw()
+        if self.instruction_file is not None:
+            self.instruction_file.write('mpl.draw()\n')
+
+    def savefig(self, filename):
+        """Save figure in file."""
+        self.mpl.savefig(filename)
+        if self.instruction_file is not None:
+            self.instruction_file.write('mpl.savefig(%s)\n' % filename)
+
+    def text(self, text, position, alignment='center', fontsize=18,
+             arrow_tip=None):
+        """
+        Write text at a position (centered, left, right - according
+        to the alignment string). position is a 2-tuple.
+        arrow+tip != None draws an arrow from the text to a point
+        (on a curve, for instance). The arrow_tip argument is then
+        the (x,y) coordinates for the arrow tip.
+        """
+        x, y = position
+        if arrow_tip is None:
+            self.ax.text(x, y, text, horizontalalignment=alignment,
+                         fontsize=fontsize)
+            if self.instruction_file is not None:
+                self.instruction_file.write("""\
+ax.text(%g, %g, %s,
+        horizontalalignment=%s, fontsize=%d)
+""" % (x, y, repr(text), repr(alignment), fontsize))
+        else:
+            if not len(arrow_tip) == 2:
+                raise ValueError('arrow_tip=%s must be (x,y) pt.' % arrow)
+            pt = arrow_tip
+            self.ax.annotate(text, xy=pt, xycoords='data',
+                             textcoords='data', xytext=position,
+                             horizontalalignment=alignment,
+                             verticalalignment='top',
+                             fontsize=fontsize,
+                             arrowprops=dict(arrowstyle='->',
+                                             facecolor='black',
+                                             #linewidth=2,
+                                             linewidth=1,
+                                             shrinkA=5,
+                                             shrinkB=5))
+            if self.instruction_file is not None:
+                self.instruction_file.write("""\
+ax.annotate('%s', xy=%s, xycoords='data',
+            textcoords='data', xytext=%s,
+            horizontalalignment='%s',
+            verticalalignment='top',
+            fontsize=%d,
+            arrowprops=dict(arrowstyle='->',
+                            facecolor='black',
+                            linewidth=2,
+                            shrinkA=5,
+                            shrinkB=5))
+""" % (text, pt, position, alignment, fontsize))
+
+# Drawing annotations with arrows:
+#http://matplotlib.sourceforge.net/users/annotations_intro.html
+#http://matplotlib.sourceforge.net/mpl_examples/pylab_examples/annotation_demo2.py
+#http://matplotlib.sourceforge.net/users/annotations_intro.html
+#http://matplotlib.sourceforge.net/users/annotations_guide.html#plotting-guide-annotation
+
+    def arrow(self, x, y, dx, dy, style='->',
+              linestyle=None, linewidth=None, linecolor=None):
+        """Draw arrow (dx,dy) at (x,y). `style` is '->', '<-' or '<->'."""
+        if linestyle is None:
+            # use "global" linestyle
+            linestyle = self.linestyle
+        if linecolor is None:
+            linecolor = self.linecolor
+        if linewidth is None:
+            linewidth = self.linewidth
+
+        if style == '->' or style == '<->':
+            self.mpl.arrow(x, y, dx, dy, hold=True,
+                           facecolor=linecolor,
+                           edgecolor=linecolor,
+                           linewidth=linewidth,
+                           head_width=0.1,
+                           #width=1,
+                           length_includes_head=True,
+                           shape='full')
+            if self.instruction_file is not None:
+                self.instruction_file.write("""\
+mpl.arrow(x=%g, y=%g, dx=%g, dy=%g,
+          facecolor='%s', edgecolor='%s',
+          linewidth=%g, head_width=0.1,
+          length_includes_head=True,
+          shape='full')
+""" % (x, y, dx, dy, linecolor, linecolor, linewidth))
+        if style == '<-' or style == '<->':
+            self.mpl.arrow(x+dx, y+dy, -dx, -dy, hold=True,
+                           facecolor=linecolor,
+                           edgecolor=linecolor,
+                           linewidth=linewidth,
+                           head_width=0.1,
+                           #width=1,
+                           length_includes_head=True,
+                           shape='full')
+            if self.instruction_file is not None:
+                self.instruction_file.write("""\
+mpl.arrow(x=%g, y=%g, dx=%g, dy=%g,
+          facecolor='%s', edgecolor='%s',
+          linewidth=%g, head_width=0.1,
+          length_includes_head=True,
+          shape='full')
+""" % (x+dx, y+dy, -dx, -dy, linecolor, linecolor, linewidth))
+
+    def arrow2(self, x, y, dx, dy, style='->'):
+        """Draw arrow (dx,dy) at (x,y). `style` is '->', '<-' or '<->'."""
+        self.ax.annotate('', xy=(x+dx,y+dy) , xytext=(x,y),
+                         arrowprops=dict(arrowstyle=style,
+                                         facecolor='black',
+                                         linewidth=1,
+                                         shrinkA=0,
+                                         shrinkB=0))
+        if self.instruction_file is not None:
+            self.instruction_file.write("")
+
+
+
+def _test():
+    d = MatplotlibDraw(0, 10, 0, 5, instruction_file='tmp3.py', axis=True)
+    d.set_linecolor('magenta')
+    d.set_linewidth(6)
+    # triangle
+    x = np.array([1, 4, 1, 1]);  y = np.array([1, 1, 4, 1])
+    d.set_filled_curves('magenta')
+    d.define_curve(x, y)
+    d.set_filled_curves(False)
+    d.define_curve(x+4, y)
+    d.text('some text1', position=(8,4), arrow_tip=(6, 1), alignment='left',
+           fontsize=18)
+    pos = np.array((7,4.5))  # numpy points work fine
+    d.text('some text2', position=pos, arrow_tip=(6, 1), alignment='center',
+           fontsize=12)
+    d.set_linewidth(2)
+    d.arrow(0.25, 0.25, 0.45, 0.45)
+    d.arrow(0.25, 0.25, 0.25, 4, style='<->')
+    d.arrow2(4.5, 0, 0, 3, style='<->')
+    x = np.linspace(0, 9, 201)
+    y = 4.5 + 0.45*np.cos(0.5*np.pi*x)
+    d.define_curve(x, y, arrow='end')
+    d.display()
+    raw_input()
+
+if __name__ == '__main__':
+    _test()

+ 893 - 0
pysketcher/shapes.py

@@ -0,0 +1,893 @@
+from numpy import linspace, sin, cos, pi, array, asarray, ndarray, sqrt, abs
+import pprint
+
+from MatplotlibDraw import MatplotlibDraw
+drawing_tool = MatplotlibDraw()
+
+def point(x, y):
+    return array((x, y), dtype=float)
+
+
+class Shape:
+    """
+    Superclass for drawing different geometric shapes.
+    Subclasses define shapes, but drawing, rotation, translation,
+    etc. are done in generic functions in this superclass.
+    """
+    def __init__(self):
+        """
+        Until new version of shapes.py is ready:
+        Never to be called from subclasses.
+        """
+        raise NotImplementedError(
+            'class %s must implement __init__,\nwhich defines '
+            'self.shapes as a list of Shape objects\n'
+            '(and preferably self._repr string).\n'
+            'Do not call Shape.__init__!' % \
+            self.__class__.__name__)
+
+    def __iter__(self):
+        # We iterate over self.shapes many places, and will
+        # get here if self.shapes is just a Shape object and
+        # not the assumed list.
+        print 'Warning: class %s does not define self.shapes\n'\
+              'as a *list* of Shape objects'
+        return [self]  # Make the iteration work
+
+    def for_all_shapes(self, func, *args, **kwargs):
+        if not hasattr(self, 'shapes'):
+            # When self.shapes is lacking, we either come to
+            # a special implementation of func or we come here
+            # because Shape.func is just inherited. This is
+            # an error if the class is not Curve or Point
+            if isinstance(self, (Curve, Point)):
+                return  # ok: no shapes and no func
+            else:
+                raise AttributeError('class %s has no shapes attribute!' %
+                                     self.__class__.__name__)
+
+        is_dict = True if isinstance(self.shapes, dict) else False
+        for shape in self.shapes:
+            if is_dict:
+                shape = self.shapes[shape]
+            getattr(shape, func)(*args, **kwargs)
+
+    def draw(self):
+        self.for_all_shapes('draw')
+
+    def rotate(self, angle, center=point(0,0)):
+        self.for_all_shapes('rotate', angle, center)
+
+    def translate(self, vec):
+        self.for_all_shapes('translate', vec)
+
+    def scale(self, factor):
+        self.for_all_shapes('scale', factor)
+
+    def set_linestyle(self, style):
+        self.for_all_shapes('set_linestyle', style)
+
+    def set_linewidth(self, width):
+        self.for_all_shapes('set_linewidth', width)
+
+    def set_linecolor(self, color):
+        self.for_all_shapes('set_linecolor', color)
+
+    def set_arrow(self, style):
+        self.for_all_shapes('set_arrow', style)
+
+    def set_name(self, name):
+        self.for_all_shapes('set_name', name)
+
+    def set_filled_curves(self, fillcolor='', fillhatch=''):
+        self.for_all_shapes('set_filled_curves', fillcolor, fillhatch)
+
+    def __str__(self):
+        return self.__class__.__name__
+
+    def __repr__(self):
+        #print 'repr in class', self.__class__.__name__
+        return pprint.pformat(self.shapes)
+
+
+class Curve(Shape):
+    """General curve as a sequence of (x,y) coordintes."""
+    def __init__(self, x, y):
+        """
+        `x`, `y`: arrays holding the coordinates of the curve.
+        """
+        self.x, self.y = x, y
+        # Turn to numpy arrays
+        self.x = asarray(self.x, dtype=float)
+        self.y = asarray(self.y, dtype=float)
+        #self.shapes must not be defined in this class
+        #as self.shapes holds children objects:
+        #Curve has no children (end leaf of self.shapes tree)
+
+        self.linestyle = None
+        self.linewidth = None
+        self.linecolor = None
+        self.fillcolor = None
+        self.fillhatch = None
+        self.arrow = None
+        self.name = None
+
+    def inside_plot_area(self, verbose=True):
+        """Check that all coordinates are within drawing_tool's area."""
+        xmin, xmax = self.x.min(), self.x.max()
+        ymin, ymax = self.y.min(), self.y.max()
+        t = drawing_tool
+        inside = True
+        if xmin < t.xmin:
+            inside = False
+            if verbose:
+                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)
+        if ymin < t.ymin:
+            inside = False
+            if verbose:
+                print 'y_min=%g < plot area y_min=%g' % (ymin, t.ymin)
+        if xmax > t.xmax:
+            inside = False
+            if verbose:
+                print 'y_max=%g > plot area y_max=%g' % (ymax, t.ymax)
+        return inside
+
+    def draw(self):
+        self.inside_plot_area()
+        drawing_tool.define_curve(
+            self.x, self.y,
+            self.linestyle, self.linewidth, self.linecolor,
+            self.arrow, self.fillcolor, self.fillhatch)
+
+    def rotate(self, angle, center=point(0,0)):
+        """
+        Rotate all coordinates: `angle` is measured in degrees and
+        (`x`,`y`) is the "origin" of the rotation.
+        """
+        angle = angle*pi/180
+        x, y = center
+        c = cos(angle);  s = sin(angle)
+        xnew = x + (self.x - x)*c - (self.y - y)*s
+        ynew = y + (self.x - x)*s + (self.y - y)*c
+        self.x = xnew
+        self.y = ynew
+
+    def scale(self, factor):
+        """Scale all coordinates by `factor`: ``x = factor*x``, etc."""
+        self.x = factor*self.x
+        self.y = factor*self.y
+
+    def translate(self, vec):
+        """Translate all coordinates by a vector `vec`."""
+        self.x += vec[0]
+        self.y += vec[1]
+
+    def set_linecolor(self, color):
+        self.linecolor = color
+
+    def set_linewidth(self, width):
+        self.linewidth = width
+
+    def set_linestyle(self, style):
+        self.linestyle = style
+
+    def set_arrow(self, style=None):
+        styles = ('->', '<-', '<->')
+        if not style in styles:
+            raise ValueError('style=%s must be in %s' % (style, styles))
+        self.arrow = style
+
+    def set_name(self, name):
+        self.name = name
+
+    def set_filled_curves(self, fillcolor='', fillhatch=''):
+        self.fillcolor = fillcolor
+        self.fillhatch = fillhatch
+
+    def __str__(self):
+        s = '%d (x,y) coordinates' % self.x.size
+        if not self.inside_plot_area(verbose=False):
+            s += ', some coordinates are outside plotting area!\n'
+        props = ('linecolor', 'linewidth', 'linestyle', 'arrow', 'name',
+                 'fillcolor', 'fillhatch')
+        for prop in props:
+            value = getattr(self, prop)
+            if value is not None:
+                s += ' %s: "%s"' % (prop, value)
+        return s
+
+    def __repr__(self):
+        return str(self)
+
+
+class Point(Shape):
+    """A point (x,y) which can be rotated, translated, and scaled."""
+    def __init__(self, x, y):
+        self.x, self.y = x, y
+        #self.shapes is not needed in this class
+
+    def __add__(self, other):
+        if isinstance(other, (list,tuple)):
+            other = Point(other)
+        return Point(self.x+other.x, self.y+other.y)
+
+    # class Point is an abstract class - only subclasses are useful
+    # and must implement draw
+    def draw(self):
+        raise NotImplementedError(
+            'class %s must implement the draw method' %
+            self.__class__.__name__)
+
+    def rotate(self, angle, center=point(0,0)):
+        """Rotate point an `angle` (in degrees) around (`x`,`y`)."""
+        angle = angle*pi/180
+        x, y = center
+        c = cos(angle);  s = sin(angle)
+        xnew = x + (self.x - x)*c - (self.y - y)*s
+        ynew = y + (self.x - x)*s + (self.y - y)*c
+        self.x = xnew
+        self.y = ynew
+
+    def scale(self, factor):
+        """Scale point coordinates by `factor`: ``x = factor*x``, etc."""
+        self.x = factor*self.x
+        self.y = factor*self.y
+
+    def translate(self, vec):
+        """Translate point by a vector `vec`."""
+        self.x += vec[0]
+        self.y += vec[1]
+
+
+# no need to store input data as they are invalid after rotations etc.
+class Rectangle(Shape):
+    def __init__(self, lower_left_corner, width, height):
+        ll = lower_left_corner  # short form
+        x = [ll[0], ll[0] + width,
+             ll[0] + width, ll[0], ll[0]]
+        y = [ll[1], ll[1], ll[1] + height,
+             ll[1] + height, ll[1]]
+        self.shapes = {'rectangle': Curve(x,y)}
+
+class Triangle(Shape):
+    """Triangle defined by its three vertices p1, p2, and p3."""
+    def __init__(self, p1, p2, p3):
+        x = [p1[0], p2[0], p3[0], p1[0]]
+        y = [p1[1], p2[1], p3[1], p1[1]]
+        self.shapes = {'triangle': Curve(x,y)}
+
+
+class Line(Shape):
+    def __init__(self, start, stop):
+        x = [start[0], stop[0]]
+        y = [start[1], stop[1]]
+        self.shapes = {'line': Curve(x, y)}
+        self.compute_formulas()
+
+    def compute_formulas(self):
+        x, y = self.shapes['line'].x, self.shapes['line'].y
+
+        # Define equations for line:
+        # y = a*x + b,  x = c*y + d
+        try:
+            self.a = (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
+            self.a = None
+            self.b = None
+        try:
+            if self.a is None:
+                self.c = 0
+            else:
+                self.c = 1/float(self.a)
+            if self.b is None:
+                self.d = x[1]
+        except ZeroDivisionError:
+            # Horizontal line, x is not a function of y
+            self.c = None
+            self.d = None
+
+    def __call__(self, x=None, y=None):
+        """Given x, return y on the line, or given y, return x."""
+        self.compute_formulas()
+        if x is not None and self.a is not None:
+            return self.a*x + self.b
+        elif y is not None and self.c is not None:
+            return self.c*y + self.d
+        else:
+            raise ValueError(
+                'Line.__call__(x=%s, y=%s) not meaningful' % \
+                (x, y))
+
+# First implementation of class Circle
+class Circle(Shape):
+    def __init__(self, center, radius, resolution=180):
+        self.center, self.radius = center, radius
+        self.resolution = resolution
+
+        t = linspace(0, 2*pi, resolution+1)
+        x0 = center[0];  y0 = center[1]
+        R = radius
+        x = x0 + R*cos(t)
+        y = y0 + R*sin(t)
+        self.shapes = {'circle': Curve(x, y)}
+
+    def __call__(self, theta):
+        """Return (x, y) point corresponding to theta."""
+        return self.center[0] + self.radius*cos(theta), \
+               self.center[1] + self.radius*sin(theta)
+
+
+class Arc(Shape):
+    def __init__(self, center, radius,
+                 start_degrees, opening_degrees,
+                 resolution=180):
+        self.center = center
+        self.radius = radius
+        self.start_degrees = start_degrees*pi/180  # radians
+        self.opening_degrees = opening_degrees*pi/180
+        self.resolution = resolution
+
+        t = linspace(self.start_degrees,
+                     self.start_degrees + self.opening_degrees,
+                     resolution+1)
+        x0 = center[0];  y0 = center[1]
+        R = radius
+        x = x0 + R*cos(t)
+        y = y0 + R*sin(t)
+        self.shapes = {'arc': Curve(x, y)}
+
+    def __call__(self, theta):
+        """Return (x,y) point at start_degrees + theta."""
+        theta = theta*pi/180
+        t = self.start_degrees + theta
+        x0 = self.center[0]
+        y0 = self.center[1]
+        R = self.radius
+        x = x0 + R*cos(t)
+        y = y0 + R*sin(t)
+        return (x, y)
+
+# Alternative for small arcs: Parabola
+
+class Parabola(Shape):
+    def __init__(self, start, mid, stop, resolution=21):
+        self.p1, self.p2, self.p3 = start, mid, stop
+
+        # y as function of x? (no point on line x=const?)
+        tol = 1E-14
+        if abs(self.p1[0] - self.p2[0]) > 1E-14 and \
+           abs(self.p2[0] - self.p3[0]) > 1E-14 and \
+           abs(self.p3[0] - self.p1[0]) > 1E-14:
+            self.y_of_x = True
+        else:
+            self.y_of_x = False
+        # x as function of y? (no point on line y=const?)
+        tol = 1E-14
+        if abs(self.p1[1] - self.p2[1]) > 1E-14 and \
+           abs(self.p2[1] - self.p3[1]) > 1E-14 and \
+           abs(self.p3[1] - self.p1[1]) > 1E-14:
+            self.x_of_y = True
+        else:
+            self.x_of_y = False
+
+        if self.y_of_x:
+            x = linspace(start[0], end[0], resolution)
+            y = self(x=x)
+        elif self.x_of_y:
+            y = linspace(start[1], end[1], resolution)
+            x = self(y=y)
+        else:
+            raise ValueError(
+                'Parabola: two or more points lie on x=const '
+                'or y=const - not allowed')
+        self.shapes = {'parabola': Curve(x, y)}
+
+    def __call__(self, x=None, y=None):
+        if x is not None and self.y_of_x:
+            return self._L2x(self.p1, self.p2)*self.p3[1] + \
+                   self._L2x(self.p2, self.p3)*self.p1[1] + \
+                   self._L2x(self.p3, self.p1)*self.p2[1]
+        elif y is not None and self.x_of_y:
+            return self._L2y(self.p1, self.p2)*self.p3[0] + \
+                   self._L2y(self.p2, self.p3)*self.p1[0] + \
+                   self._L2y(self.p3, self.p1)*self.p2[0]
+        else:
+            raise ValueError(
+                'Parabola.__call__(x=%s, y=%s) not meaningful' % \
+                (x, y))
+
+    def _L2x(self, x, pi, pj, pk):
+        return (x - pi[0])*(x - pj[0])/((pk[0] - pi[0])*(pk[0] - pj[0]))
+
+    def _L2y(self, y, pi, pj, pk):
+        return (y - pi[1])*(y - pj[1])/((pk[1] - pi[1])*(pk[1] - pj[1]))
+
+
+class Circle(Arc):
+    def __init__(self, center, radius, resolution=180):
+        Arc.__init__(self, center, radius, 0, 360, resolution)
+
+# class Wall: horizontal Line with many small Lines 45 degrees
+class XWall(Shape):
+    def __init__(start, length, dx, below=True):
+        n = int(round(length/float(dx)))  # no of intervals
+        x = linspace(start[0], start[0] + length, n+1)
+        y = start[1]
+        dy = dx
+        if below:
+            taps = [Line((xi,y-dy), (xi+dx, y)) for xi in x[:-1]]
+        else:
+            taps = [Line((xi,y), (xi+dx, y+dy)) for xi in x[:-1]]
+        self.shapes = [Line(start, (start[0]+length, start[1]))] + taps
+
+class Wall(Shape):
+    def __init__(self, start, length, thickness, rotation_angle=0):
+        p1 = asarray(start)
+        p2 = p1 + asarray([length, 0])
+        p3 = p2 + asarray([0, thickness])
+        p4 = p1 + asarray([0, thickness])
+        p5 = p1
+        x = [p[0] for p in p1, p2, p3, p4, p5]
+        y = [p[1] for p in p1, p2, p3, p4, p5]
+        wall = Curve(x, y)
+        wall.set_filled_curves('white', '/')
+        wall.rotate(rotation_angle, start)
+        self.shapes = {'wall': wall}
+
+"""
+    def draw(self):
+        x = self.shapes['wall'].x
+        y = self.shapes['wall'].y
+        drawing_tool.ax.fill(x, y, 'w',
+                             edgecolor=drawing_tool.linecolor,
+                             hatch='/')
+"""
+
+class CurveWall(Shape):
+    def __init__(self, x, y, thickness):
+        x1 = asarray(x, float)
+        y1 = asarray(y, float)
+        x2 = x1
+        y2 = y1 + thickness
+        from numpy import concatenate
+        # x1/y1 + reversed x2/y2
+        x = concatenate((x1, x2[-1::-1]))
+        y = concatenate((y1, y2[-1::-1]))
+        wall = Curve(x, y)
+        wall.set_filled_curves('white', '/')
+        self.shapes = {'wall': wall}
+
+"""
+    def draw(self):
+        x = self.shapes['wall'].x
+        y = self.shapes['wall'].y
+        drawing_tool.ax.fill(x, y, 'w',
+                             edgecolor=drawing_tool.linecolor,
+                             hatch='/')
+"""
+
+
+class Text(Point):
+    def __init__(self, text, position, alignment='center', fontsize=18):
+        self.text = text
+        self.alignment, self.fontsize = alignment, fontsize
+        is_sequence(position, length=2, can_be_None=True)
+        Point.__init__(self, position[0], position[1])
+        #no need for self.shapes here
+
+    def draw(self):
+        drawing_tool.text(self.text, (self.x, self.y),
+                          self.alignment, self.fontsize)
+
+    def __str__(self):
+        return 'text "%s" at (%g,%g)' % (self.text, self.x, self.y)
+
+    def __repr__(self):
+        return str(self)
+
+
+class Text_wArrow(Text):
+    def __init__(self, text, position, arrow_tip,
+                 alignment='center', fontsize=18):
+        is_sequence(arrow_tip, length=2, can_be_None=True)
+        self.arrow_tip = arrow_tip
+        Text.__init__(self, text, position, alignment, fontsize)
+
+    def draw(self):
+        drawing_tool.text(self.text, self.position,
+                          self.alignment, self.fontsize,
+                          self.arrow_tip)
+    def __str__(self):
+        return 'annotation "%s" at (%g,%g) with arrow to (%g,%g)' % \
+               (self.text, self.x, self.y,
+                self.arrow_tip[0], self.arrow_tip[1])
+
+    def __repr__(self):
+        return str(self)
+
+
+class Axis(Shape):
+    def __init__(self, bottom_point, length, label, below=True,
+                 rotation_angle=0, label_spacing=1./25):
+        """
+        Draw axis from bottom_point with `length` to the right
+        (x axis). Place label below (True) or above (False) axis.
+        Then return `rotation_angle` (in degrees).
+        To make a standard x axis, call with ``below=True`` and
+        ``rotation_angle=0``. To make a standard y axis, call with
+        ``below=False`` and ``rotation_angle=90``.
+        A tilted axis can also be drawn.
+        The `label_spacing` denotes the space between the symbol
+        and the arrow tip as a fraction of the length of the plot
+        in x direction.
+        """
+        # Arrow is vertical arrow, make it horizontal
+        arrow = Arrow(bottom_point, length, rotation_angle=-90)
+        arrow.rotate(rotation_angle, bottom_point)
+        spacing = drawing_tool.xrange*label_spacing
+        if below:
+            spacing = - spacing
+        label_pos = [bottom_point[0] + length, bottom_point[1] + spacing]
+        symbol = Text(label, position=label_pos)
+        symbol.rotate(rotation_angle, bottom_point)
+        self.shapes = {'arrow': arrow, 'symbol': symbol}
+
+class Gravity(Axis):
+    """Downward-pointing gravity arrow with the symbol g."""
+    def __init__(self, start, length):
+        Axis.__init__(self, start, length, '$g$', below=False,
+                      rotation_angle=-90, label_spacing=1./30)
+
+def test_Axis():
+    set_coordinate_system(xmin=0, xmax=15, ymin=0, ymax=15, axis=True)
+    x_axis = Axis((7.5,2), 5, 'x', rotation_angle=0)
+    y_axis = Axis((7.5,2), 5, 'y', below=False, rotation_angle=90)
+    system = Compose({'x axis': x_axis, 'y axis': y_axis})
+    system.draw()
+    drawing_tool.display()
+    set_linestyle('dashed')
+    system.shapes['x axis'].rotate(40, (7.5, 2))
+    system.shapes['y axis'].rotate(40, (7.5, 2))
+    system.draw()
+    drawing_tool.display()
+    print repr(system)
+
+
+class DistanceSymbol(Shape):
+    """
+    Arrow with symbol at the midpoint,
+    for identifying a distance with a symbol.
+    """
+    def __init__(self, start, end, symbol, fontsize=14):
+        start = asarray(start, float)
+        end = asarray(end, float)
+        mid = 0.5*(start + end)  # midpoint of start-end line
+        tangent = end - start
+        normal = asarray([-tangent[1], tangent[0]])/\
+                 sqrt(tangent[0]**2 + tangent[1]**2)
+        symbol_pos = mid + normal*drawing_tool.xrange/60.
+        self.shapes = {'arrow': Arrow1(start, end, style='<->'),
+                       'symbol': Text(symbol, symbol_pos, fontsize=fontsize)}
+
+
+class ArcSymbol(Shape):
+    def __init__(self, symbol, center, radius,
+                 start_degrees, opening_degrees,
+                 resolution=180, fontsize=14):
+        arc = Arc(center, radius, start_degrees, opening_degrees,
+                  resolution)
+        mid = asarray(arc(opening_degrees/2.))
+        normal = mid - asarray(center, float)
+        normal = normal/sqrt(normal[0]**2 + normal[1]**2)
+        symbol_pos = mid + normal*drawing_tool.xrange/60.
+        self.shapes = {'arc': arc,
+                       'symbol': Text(symbol, symbol_pos, fontsize=fontsize)}
+
+class Compose(Shape):
+    def __init__(self, shapes):
+        """shapes: list or dict of Shape objects."""
+        self.shapes = shapes
+
+
+# can make help methods: Line.midpoint, Line.normal(pt, dir='left') -> (x,y)
+
+# list annotations in each class? contains extra annotations for explaining
+# important parameters to the constructor, e.g., Line.annotations holds
+# start and end as Text objects. Shape.demo calls shape.draw and
+# for annotation in self.demo: annotation.draw() YES!
+# Can make overall demo of classes by making objects and calling demo
+# Could include demo fig in each constructor
+
+
+class Arrow1(Shape):
+    """Draw an arrow as Line with arrow."""
+    def __init__(self, start, end, style='->'):
+        self.start, self.end, self.style = start, end, style
+        self.shapes = {'arrow': Line(start, end, arrow=style)}
+
+class Arrow3(Shape):
+    """Draw a vertical line and arrow head. Then rotate `rotation_angle`."""
+    def __init__(self, bottom_point, length, rotation_angle=0):
+        self.bottom = bottom_point
+        self.length = length
+        self.angle = rotation_angle
+
+        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_degrees = 30*pi/180
+        head_left_pt = (top[0] - head_length*sin(head_degrees),
+                        top[1] - head_length*cos(head_degrees))
+        head_right_pt = (top[0] + head_length*sin(head_degrees),
+                         top[1] - head_length*cos(head_degrees))
+        head_left = Line(head_left_pt, top)
+        head_right = Line(head_right_pt, top)
+        head_left.set_linestyle('solid')
+        head_right.set_linestyle('solid')
+        self.shapes = {'line': main, 'head left': head_left,
+                       'head right': head_right}
+
+        # rotate goes through self.shapes so this must be initialized first
+        self.rotate(rotation_angle, bottom_point)
+
+Arrow = Arrow3  # backward compatibility
+
+
+class Wheel(Shape):
+    def __init__(self, center, radius, inner_radius=None, nlines=10):
+        self.center = center
+        self.radius = radius
+        if inner_radius is None:
+            self.inner_radius = radius/5.0
+        else:
+            self.inner_radius = inner_radius
+        self.nlines = nlines
+
+        outer = Circle(self.center, self.radius)
+        inner = Circle(self.center, self.inner_radius)
+        lines = []
+        # Draw nlines+1 since the first and last coincide
+        # (then nlines lines will be visible)
+        t = linspace(0, 2*pi, self.nlines+1)
+
+        Ri = self.inner_radius;  Ro = self.radius
+        x0 = self.center[0];  y0 = self.center[1]
+        xinner = x0 + Ri*cos(t)
+        yinner = y0 + Ri*sin(t)
+        xouter = x0 + Ro*cos(t)
+        youter = y0 + Ro*sin(t)
+        lines = [Line((xi,yi),(xo,yo)) for xi, yi, xo, yo in \
+                 zip(xinner, yinner, xouter, youter)]
+        self.shapes = [outer, inner] + lines
+
+class Wave(Shape):
+    def __init__(self, xstart, xstop,
+                 wavelength, amplitude, mean_level):
+        self.xstart = xstart
+        self.xstop = xstop
+        self.wavelength = wavelength
+        self.amplitude = amplitude
+        self.mean_level = mean_level
+
+        npoints = (self.xstop - self.xstart)/(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)
+        self.shapes = {'waves': Curve(x,y)}
+
+
+
+# make a version of Spring using Point class
+
+
+class Spring(Shape):
+    def __init__(self, bottom_point, length, tagwidth, ntags=4):
+        """
+        Specify a vertical spring, starting at bottom_point and
+        having a specified lengths. In the middle third of the
+        spring there are ntags tags.
+        """
+        self.B = bottom_point
+        self.n = ntags - 1  # n counts tag intervals
+        # n must be odd:
+        if self.n % 2 == 0:
+            self.n = self.n+1
+        self.L = length
+        self.w = tagwidth
+
+        B, L, n, w = self.B, self.L, self.n, self.w  # short forms
+        t = L/(3.0*n)  # must be better worked out
+        P0 = (B[0], B[1]+L/3.0)
+        P1 = (B[0], B[1]+L/3.0+t/2.0)
+        P2 = (B[0], B[1]+L*2/3.0)
+        P3 = (B[0], B[1]+L)
+        line1 = Line(B, P1)
+        lines = [line1]
+        #line2 = Line(P2, P3)
+        T1 = P1
+        T2 = (T1[0] + w, T1[1] + t/2.0)
+        lines.append(Line(T1,T2))
+        T1 = (T2[0], T2[1])
+        for i in range(n):
+            T2 = (T1[0] + (-1)**(i+1)*2*w, T1[1] + t/2.0)
+            lines.append(Line(T1, T2))
+            T1 = (T2[0], T2[1])
+        T2 = (T1[0] + w, T1[1] + t/2.0)
+        lines.append(Line(T1,T2))
+
+        #print P2, T2
+        lines.append(Line(T2, P3))
+        self.shapes = lines
+
+
+# COMPOSITE types:
+# MassSpringForce: Line(horizontal), Spring, Rectangle, Arrow/Line(w/arrow)
+# must be easy to find the tip of the arrow
+# Maybe extra dict: self.name['mass'] = Rectangle object - YES!
+
+def _test1():
+    set_coordinate_system(xmin=0, xmax=10, ymin=0, ymax=10)
+    l1 = Line((0,0), (1,1))
+    l1.draw()
+    input(': ')
+    c1 = Circle((5,2), 1)
+    c2 = Circle((6,2), 1)
+    w1 = Wheel((7,2), 1)
+    c1.draw()
+    c2.draw()
+    w1.draw()
+    hardcopy()
+    display()  # show the plot
+
+def _test2():
+    set_coordinate_system(xmin=0, xmax=10, ymin=0, ymax=10)
+    l1 = Line((0,0), (1,1))
+    l1.draw()
+    input(': ')
+    c1 = Circle((5,2), 1)
+    c2 = Circle((6,2), 1)
+    w1 = Wheel((7,2), 1)
+    filled_curves(True)
+    set_linecolor('blue')
+    c1.draw()
+    set_linecolor('aqua')
+    c2.draw()
+    filled_curves(False)
+    set_linecolor('red')
+    w1.draw()
+    hardcopy()
+    display()  # show the plot
+
+def _test3():
+    """Test example from the book."""
+    set_coordinate_system(xmin=0, xmax=10, ymin=0, ymax=10)
+    l1 = Line(start=(0,0), stop=(1,1))  # define line
+    l1.draw()        # make plot data
+    r1 = Rectangle(lower_left_corner=(0,1), width=3, height=5)
+    r1.draw()
+    Circle(center=(5,7), radius=1).draw()
+    Wheel(center=(6,2), radius=2, inner_radius=0.5, nlines=7).draw()
+    hardcopy()
+    display()
+
+def _test4():
+    """Second example from the book."""
+    set_coordinate_system(xmin=0, xmax=10, ymin=0, ymax=10)
+    r1 = Rectangle(lower_left_corner=(0,1), width=3, height=5)
+    c1 = Circle(center=(5,7), radius=1)
+    w1 = Wheel(center=(6,2), radius=2, inner_radius=0.5, nlines=7)
+    c2 = Circle(center=(7,7), radius=1)
+    filled_curves(True)
+    c1.draw()
+    set_linecolor('blue')
+    r1.draw()
+    set_linecolor('aqua')
+    c2.draw()
+    # Add thick aqua line around rectangle:
+    filled_curves(False)
+    set_linewidth(4)
+    r1.draw()
+    set_linecolor('red')
+    # Draw wheel with thick lines:
+    w1.draw()
+    hardcopy('tmp_colors')
+    display()
+
+
+def _test5():
+    set_coordinate_system(xmin=0, xmax=10, ymin=0, ymax=10)
+    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))
+    linecolor('blue')
+    filled_curves(True)
+    r1.draw()
+    linecolor('aqua')
+    filled_curves(False)
+    l1.draw()
+    hardcopy()
+    display()  # show the plot
+
+def rolling_wheel(total_rotation_angle):
+    """Animation of a rotating wheel."""
+    set_coordinate_system(xmin=0, xmax=10, ymin=0, ymax=10)
+
+    import time
+    center = (6,2)
+    radius = 2.0
+    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)):
+        w1.draw()
+        print 'XXXXXXXXXXXXXXXXXXXXXX BIG PROBLEM WITH ANIMATE!!!'
+        display()
+
+
+        filename = 'tmp_%03d' % i
+        pngfiles.append(filename + '.png')
+        hardcopy(filename)
+        time.sleep(0.3)  # pause
+
+        L = radius*angle*pi/180  # translation = arc length
+        w1.rotate(angle, center)
+        w1.translate((-L, 0))
+        center = (center[0] - L, center[1])
+
+        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
+
+def is_sequence(seq, length=None,
+                can_be_None=False, error_message=True):
+    if can_be_None:
+        legal_types = (list,tuple,ndarray,None)
+    else:
+        legal_types = (list,tuple,ndarray)
+
+    if isinstance(seq, legal_types):
+        if length is not None:
+            if length == len(seq):
+                return True
+            elif error_message:
+                raise TypeError('%s is %s; must be %s of length %d' %
+                            (str(point), type(point),
+                            ', '.join([str(t) for t in legal_types]),
+                             len(seq)))
+            else:
+                return False
+        else:
+            return True
+    elif error_message:
+        raise TypeError('%s is %s; must be %s' %
+                        str(point), type(point),
+                        ', '.join([str(t) for t in legal_types]))
+    else:
+        return False
+
+if __name__ == '__main__':
+    #rolling_wheel(40)
+    #_test1()
+    #_test3()
+    funcs = [
+        #test_Axis,
+        test_inclined_plane,
+        ]
+    for func in funcs:
+        func()
+        raw_input('Type Return: ')
+
+