Gilbert Brault vor 5 Jahren
Ursprung
Commit
a32d92c248
27 geänderte Dateien mit 3879 neuen und 4 gelöschten Zeilen
  1. 3 0
      .vscode/settings.json
  2. BIN
      2017_Book_FiniteDifferenceComputingWithP.pdf
  3. 33 0
      examples/.ipynb_checkpoints/beam1-checkpoint.py
  4. 114 0
      examples/.ipynb_checkpoints/beam2-checkpoint.py
  5. 60 0
      examples/.ipynb_checkpoints/flow_over_gaussian-checkpoint.py
  6. 16 0
      examples/.ipynb_checkpoints/hello_world-checkpoint.py
  7. 84 0
      examples/.ipynb_checkpoints/oscillator_sketch1-checkpoint.py
  8. 117 0
      examples/.ipynb_checkpoints/pendulum1-checkpoint.py
  9. 100 0
      examples/.ipynb_checkpoints/wheel_on_inclined_plane-checkpoint.py
  10. 105 0
      notebooks/.ipynb_checkpoints/Introduction-checkpoint.ipynb
  11. 126 0
      notebooks/.ipynb_checkpoints/Oscillator1-checkpoint.ipynb
  12. 100 0
      notebooks/.ipynb_checkpoints/beam1-checkpoint.ipynb
  13. 195 0
      notebooks/.ipynb_checkpoints/beam2-checkpoint.ipynb
  14. 121 0
      notebooks/.ipynb_checkpoints/flowovergaussian-checkpoint.ipynb
  15. 209 0
      notebooks/.ipynb_checkpoints/pendulum1-checkpoint.ipynb
  16. 217 0
      notebooks/.ipynb_checkpoints/test-checkpoint.ipynb
  17. 321 0
      notebooks/.ipynb_checkpoints/wheelonInclinedPlane-checkpoint.ipynb
  18. 105 0
      notebooks/Introduction.ipynb
  19. 126 0
      notebooks/Oscillator1.ipynb
  20. 100 0
      notebooks/beam1.ipynb
  21. 195 0
      notebooks/beam2.ipynb
  22. 128 0
      notebooks/flowovergaussian.ipynb
  23. 209 0
      notebooks/pendulum1.ipynb
  24. 217 0
      notebooks/test.ipynb
  25. 299 0
      notebooks/wheelonInclinedPlane.ipynb
  26. 574 0
      pysketcher/.ipynb_checkpoints/MatplotlibDraw-checkpoint.py
  27. 5 4
      pysketcher/MatplotlibDraw.py

+ 3 - 0
.vscode/settings.json

@@ -0,0 +1,3 @@
+{
+    "python.pythonPath": "C:\\Python38\\python.exe"
+}

BIN
2017_Book_FiniteDifferenceComputingWithP.pdf


+ 33 - 0
examples/.ipynb_checkpoints/beam1-checkpoint.py

@@ -0,0 +1,33 @@
+"""A very simple beam."""
+from pysketcher import *
+
+L = 8.0
+H = 1.0
+xpos = 2.0
+ypos = 3.0
+
+drawing_tool.set_coordinate_system(xmin=0, xmax=xpos+1.2*L,
+                                   ymin=0, ymax=ypos+5*H,
+                                   axis=True)
+drawing_tool.set_linecolor('blue')
+drawing_tool.set_grid(True)
+drawing_tool.set_fontsize(22)
+
+P0 = point(xpos,ypos)
+main = Rectangle(P0, L, H)
+h = L/16  # size of support, clamped wall etc
+support = SimplySupportedBeam(P0, h)
+clamped = Rectangle(P0 + point(L, 0) - point(0,2*h), h, 6*h).set_filled_curves(pattern='/')
+F_pt = point(P0[0]+L/2, P0[1]+H)
+force = Force(F_pt + point(0,2*H), F_pt, '$F$').set_linewidth(3)
+L_dim = Distance_wText((xpos,P0[1]-3*h), (xpos+L,P0[1]-3*h), '$L$')
+beam = Composition({'main': main, 'simply supported end': support,
+                    'clamped end': clamped, 'force': force,
+                    'L': L_dim})
+beam.draw()
+beam.draw_dimensions()
+drawing_tool.display()
+
+#test_Dashpot(xpos+2*W)
+
+input()

+ 114 - 0
examples/.ipynb_checkpoints/beam2-checkpoint.py

@@ -0,0 +1,114 @@
+"""A more sophisticated beam than in beam1.py."""
+from pysketcher import *
+
+def beam():
+    L = 8.0
+    a = 3*L/4
+    b = L - a
+    H = 1.0
+    xpos = 0.0
+    ypos = 3.0
+
+    drawing_tool.set_coordinate_system(
+        xmin=-3, xmax=xpos+1.5*L,
+        ymin=0, ymax=ypos+5*H,
+        axis=False)
+    drawing_tool.set_linecolor('blue')
+    #drawing_tool.set_grid(True)
+    drawing_tool.set_fontsize(16)
+
+    A = point(xpos,ypos)
+
+    beam = Rectangle(A, L, H)
+
+    h = L/16  # size of support, clamped wall etc
+
+    clamped = Rectangle(A - point(h,0) - point(0,2*h), h,
+                        6*h).set_filled_curves(pattern='/')
+
+    load = ConstantBeamLoad(A + point(0,H), L, H)
+    load.set_linewidth(1).set_linecolor('black')
+    load_text = Text('$w$',
+                     load.geometric_features()['mid_top'] +
+                     point(0,h/2.))
+
+    B = A + point(a, 0)
+    C = B + point(b, 0)
+
+    support = SimplySupportedBeam(B, h)  # pt B is simply supported
+
+
+    R1 = Force(A-point(0,2*H), A, '$R_1$', text_spacing=1./50)
+    R1.set_linewidth(3).set_linecolor('black')
+    R2 = Force(B-point(0,2*H),
+               support.geometric_features()['mid_support'],
+               '$R_2$', text_spacing=1./50)
+    R2.set_linewidth(3).set_linecolor('black')
+    M1 = Moment('$M_1$', center=A + point(-H, H/2), radius=H/2,
+                left=True, text_spacing=1/30.)
+    M1.set_linecolor('black')
+
+    ab_level = point(0, 3*h)
+    a_dim = Distance_wText(A - ab_level, B - ab_level, '$a$')
+    b_dim = Distance_wText(B - ab_level, C - ab_level, '$b$')
+    dims = Composition({'a': a_dim, 'b': b_dim})
+    symbols = Composition(
+        {'R1': R1, 'R2': R2, 'M1': M1,
+         'w': load, 'w text': load_text,
+         'A': Text('$A$', A+point(0.7*h,-0.9*h)),
+         'B': Text('$B$',
+                   support.geometric_features()['mid_support']-
+                   point(1.25*h,0)),
+         'C': Text('$C$', C+point(h/2,-h/2))})
+
+    x_axis = Axis(A + point(L+h, H/2), 2*H, '$x$',).\
+             set_linecolor('black')
+    y_axis = Axis(A + point(0,H/2), 3.5*H, '$y$',
+                  label_alignment='left',
+                  rotation_angle=90).set_linecolor('black')
+    axes = Composition({'x axis': x_axis, 'y axis': y_axis})
+
+    annotations = Composition({'dims': dims, 'symbols': symbols,
+                               'axes': axes})
+    beam = Composition({'beam': beam, 'support': support,
+                        'clamped end': clamped, 'load': load})
+
+    def deflection(x, a, b, w):
+        import numpy as np
+        R1 = 5./8*w*a - 3*w*b**2/(4*a)
+        R2 = 3./8*w*a + w*b + 3*w*b**2/(4*a)
+        M1 = R1*a/3 - w*a**2/12
+        y = -(M1/2.)*x**2 + 1./6*R1*x**3 - w/24.*x**4 + \
+            1./6*R2*np.where(x > a, 1, 0)*(x-a)**3
+        return y
+
+    x = linspace(0, L, 101)
+    y = deflection(x, a, b, w=1.0)
+    y /= abs(y.max() - y.min())
+    y += ypos + H/2
+
+    elastic_line = Curve(x, y).\
+                   set_linecolor('red').\
+                   set_linestyle('dashed').\
+                   set_linewidth(3)
+
+    beam.draw()
+    drawing_tool.display()
+    drawing_tool.savefig('tmp_beam2_1')
+
+    import time
+    time.sleep(1.5)
+
+    annotations.draw()
+    drawing_tool.display()
+    drawing_tool.savefig('tmp_beam2_2')
+    time.sleep(1.5)
+
+    elastic_line.draw()
+    drawing_tool.display()
+    drawing_tool.savefig('tmp_beam2_3')
+    #beam.draw_dimensions()
+    #test_Dashpot(xpos+2*W)
+
+beam()
+input()

+ 60 - 0
examples/.ipynb_checkpoints/flow_over_gaussian-checkpoint.py

@@ -0,0 +1,60 @@
+from pysketcher import *
+from numpy import exp, linspace
+
+W = 5    # upstream area
+L = 10   # downstread area
+H = 4    # height
+sigma = 2
+alpha = 2
+
+drawing_tool.set_coordinate_system(xmin=0, xmax=W+L+1,
+                                   ymin=-2, ymax=H+1,
+                                   axis=True)
+drawing_tool.set_linecolor('blue')
+
+# Create bottom
+
+def gaussian(x):
+    return alpha*exp(-(x-W)**2/(0.5*sigma**2))
+
+x = linspace(0, W+L, 51)
+y = gaussian(x)
+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]
+
+inlet_profile = VelocityProfile((0,0), H, velprofile, 5)
+symmetry_line = Line((0,H), (W+L,H))
+symmetry_line.set_linestyle('dashed')
+outlet = Line((W+L,0), (W+L,H))
+outlet.set_linestyle('dashed')
+
+fig = Composition({
+    'bottom': wall,
+    'inlet': inlet_profile,
+    'symmetry line': symmetry_line,
+    'outlet': outlet,
+    })
+
+fig.draw()  # send all figures to plotting backend
+
+vx, vy = velprofile(H/2.)
+symbols = {
+    'alpha': Distance_wText((W,0), (W,alpha), r'$\alpha$'),
+    'W': Distance_wText((0,-0.5), (W,-0.5), r'$W$',
+                          text_spacing=-1./30),
+    'L': Distance_wText((W,-0.5), (W+L,-0.5), r'$L$',
+                          text_spacing=-1./30),
+    'v(y)': Text('$v(y)$', (H/2., vx)),
+    'dashed line': Line((W-2.5*sigma,0), (W+2.5*sigma,0)).\
+                   set_linestyle('dotted').set_linecolor('black'),
+    }
+symbols = Composition(symbols)
+symbols.draw()
+
+drawing_tool.display()
+drawing_tool.savefig('tmp1')
+
+input()

+ 16 - 0
examples/.ipynb_checkpoints/hello_world-checkpoint.py

@@ -0,0 +1,16 @@
+"""Minimialistic pysketcher example."""
+from pysketcher import *
+
+drawing_tool.set_coordinate_system(
+    xmin=0, xmax=5, ymin=0, ymax=3, axis=False)
+drawing_tool.set_linecolor('black')
+
+code = Text("print 'Hello, World!'",
+            (2.5,1.5), fontsize=24,
+            fgcolor='red', bgcolor='gray', fontfamily='monospace')
+fig = Composition(dict(text=code))
+
+fig.draw()
+drawing_tool.display()
+drawing_tool.savefig('tmp1')
+input()

+ 84 - 0
examples/.ipynb_checkpoints/oscillator_sketch1-checkpoint.py

@@ -0,0 +1,84 @@
+"""Draw mechanical vibration system."""
+
+from pysketcher import *
+
+L = 12.
+H = L/6
+W = L/6
+
+xmax = L
+drawing_tool.set_coordinate_system(xmin=-L, xmax=xmax,
+                                   ymin=-1, ymax=L+H,
+                                   axis=False,
+                                   instruction_file='tmp_mpl.py')
+x = 0
+drawing_tool.set_linecolor('black')
+
+def make_dashpot(x):
+    d_start = (-L,2*H)
+    d = Dashpot(start=d_start, total_length=L+x, width=W,
+                bar_length=3*H/2, dashpot_length=L/2, piston_pos=H+x)
+    d.rotate(-90, d_start)
+    return d
+
+def make_spring(x):
+    s_start = (-L,4*H)
+    s = Spring(start=s_start, length=L+x, bar_length=3*H/2, teeth=True)
+    s.rotate(-90, s_start)
+    return s
+
+d = make_dashpot(0)
+s = make_spring(0)
+
+M = Rectangle((0,H), 4*H, 4*H).set_linewidth(4)
+left_wall = Rectangle((-L,0),H/10,L).set_filled_curves(pattern='/')
+ground = Wall(x=[-L/2,L], y=[0,0], thickness=-H/10)
+wheel1 = Circle((H,H/2), H/2)
+wheel2 = wheel1.copy()
+wheel2.translate(point(2*H, 0))
+
+fontsize = 18
+text_m = Text('$m$', (2*H, H+2*H), fontsize=fontsize)
+text_ku = Text('$ku$', (-L/2, H+4*H), fontsize=fontsize)
+text_bv = Text("$bu'$", (-L/2, H), fontsize=fontsize)
+x_axis = Axis((2*H, L), H, '$u(t)$', fontsize=fontsize,
+              label_spacing=(0.04, -0.01))
+x_axis_start = Line((2*H, L-H/4), (2*H, L+H/4)).set_linewidth(4)
+
+fig = Composition({
+    'spring': s, 'mass': M, 'left wall': left_wall,
+    'ground': ground, 'wheel1': wheel1, 'wheel2': wheel2,
+    'text_m': text_m, 'text_ku': text_ku,
+    'x_axis': x_axis, 'x_axis_start': x_axis_start})
+
+fig.draw()
+drawing_tool.display()
+drawing_tool.savefig('tmp_oscillator_spring')
+
+drawing_tool.erase()
+
+fig['dashpot'] = d
+fig['text_bv'] = text_bv
+
+# or fig = Composition(dict(fig=fig, dashpot=d, text_bv=text_bv))
+fig.draw()
+
+drawing_tool.display()
+drawing_tool.savefig('tmp_oscillator')
+
+drawing_tool.erase()
+
+text_ku = Text('$ku$', (-L/2, H+4*H), fontsize=fontsize)
+text_bv = Text("$bu'$", (-L/2, H), fontsize=fontsize)
+x_axis = Axis((2*H, L), H, '$u(t)$', fontsize=fontsize,
+              label_spacing=(0.04, -0.01))
+F_force = Force((4*H, H+2*H), (4*H+H, H+2*H), '$F(t)$',
+                text_spacing=(0.057, -0.007), text_alignment='left', fontsize=fontsize)
+fig['text_ku'] = text_ku
+fig['text_bv'] = text_bv
+fig['x_axis'] = x_axis
+fig['F_force'] = F_force
+fig.draw()
+drawing_tool.savefig('tmp_oscillator_general')
+
+raw_input()

+ 117 - 0
examples/.ipynb_checkpoints/pendulum1-checkpoint.py

@@ -0,0 +1,117 @@
+from pysketcher import *
+
+H = 7.
+W = 6.
+
+drawing_tool.set_coordinate_system(xmin=0, xmax=W,
+                                   ymin=0, ymax=H,
+                                   axis=False)
+#drawing_tool.set_grid(True)
+drawing_tool.set_linecolor('blue')
+
+L = 5*H/7          # length
+P = (W/6, 0.85*H)  # rotation point
+a = 40             # angle
+
+vertical = Line(P, P-point(0,L))
+path = Arc(P, L, -90, a)
+angle = Arc_wText(r'$\theta$', P, L/4, -90, a, text_spacing=1/30.)
+
+rod = Line(P, P + L*point(sin(radians(a)), -L*cos(radians(a))))
+# or shorter (and more reliable)
+mass_pt = path.geometric_features()['end']
+rod = Line(P, mass_pt)
+
+mass = Circle(center=mass_pt, radius=L/20.)
+mass.set_filled_curves(color='blue')
+rod_vec = rod.geometric_features()['end'] - \
+          rod.geometric_features()['start']
+unit_rod_vec = unit_vec(rod_vec)
+mass_symbol = Text('$m$', mass_pt + L/10*unit_rod_vec)
+
+length = Distance_wText(P, mass_pt, '$L$')
+# Displace length indication
+length.translate(L/15*point(cos(radians(a)), sin(radians(a))))
+gravity = Gravity(start=P+point(0.8*L,0), length=L/3)
+
+def set_dashed_thin_blackline(*objects):
+    """Set linestyle of objects to dashed, black, width=1."""
+    for obj in objects:
+        obj.set_linestyle('dashed')
+        obj.set_linecolor('black')
+        obj.set_linewidth(1)
+
+set_dashed_thin_blackline(vertical, path)
+
+fig = Composition(
+    {'body': mass, 'rod': rod,
+     'vertical': vertical, 'theta': angle, 'path': path,
+     'g': gravity, 'L': length, 'm': mass_symbol})
+
+fig.draw()
+drawing_tool.display()
+drawing_tool.savefig('tmp_pendulum1')
+
+# Draw free body diagram in several different versions
+# (note that we build body_diagram, erase and draw,
+# add elements to body_diagram, erase and draw, and so on)
+input('Press Return to make free body diagram: ')
+
+drawing_tool.erase()
+
+drawing_tool.set_linecolor('black')
+
+rod_start = rod.geometric_features()['start']  # Point P
+vertical2 = Line(rod_start, rod_start + point(0,-L/3))
+set_dashed_thin_blackline(vertical2)
+set_dashed_thin_blackline(rod)
+angle2 = Arc_wText(r'$\theta$', rod_start, L/6, -90, a,
+                   text_spacing=1/30.)
+
+mg_force  = Force(mass_pt, mass_pt + L/5*point(0,-1),
+                  '$mg$', text_pos='end')
+rod_force = Force(mass_pt, mass_pt - L/3*unit_vec(rod_vec),
+                  '$S$', text_pos='end',
+                  text_spacing=(0.03, 0.01))
+air_force = Force(mass_pt, mass_pt -
+                  L/6*unit_vec((rod_vec[1], -rod_vec[0])),
+                  '$\sim|v|v$', text_pos='end',
+                  text_spacing=(0.04,0.005))
+
+body_diagram = Composition(
+    {'mg': mg_force, 'S': rod_force, 'rod': rod,
+     'vertical': vertical2, 'theta': angle2,
+     'body': mass, 'm': mass_symbol})
+
+body_diagram.draw()
+#drawing_tool.display('Free body diagram')
+drawing_tool.savefig('tmp_pendulum2')
+
+drawing_tool.adjust_coordinate_system(body_diagram.minmax_coordinates(), 90)
+#drawing_tool.display('Free body diagram')
+drawing_tool.savefig('tmp_pendulum3')
+
+drawing_tool.erase()
+body_diagram['air'] = air_force
+body_diagram.draw()
+#drawing_tool.display('Free body diagram')
+drawing_tool.savefig('tmp_pendulum4')
+
+drawing_tool.erase()
+x0y0 = Text('$(x_0,y_0)$', P + point(-0.4,-0.1))
+ir = Force(P, P + L/10*unit_vec(rod_vec),
+           r'$\boldsymbol{i}_r$', text_pos='end',
+           text_spacing=(0.015,0))
+ith = Force(P, P + L/10*unit_vec((-rod_vec[1], rod_vec[0])),
+           r'$\boldsymbol{i}_{\theta}$', text_pos='end',
+            text_spacing=(0.02,0.005))
+
+body_diagram['ir'] = ir
+body_diagram['ith'] = ith
+body_diagram['origin'] = x0y0
+
+body_diagram.draw()
+#drawing_tool.display('Free body diagram')
+drawing_tool.savefig('tmp_pendulum5')
+
+input()

+ 100 - 0
examples/.ipynb_checkpoints/wheel_on_inclined_plane-checkpoint.py

@@ -0,0 +1,100 @@
+from pysketcher import *
+
+def inclined_plane():
+    theta = 30.
+    L = 10.
+    a = 1.
+    xmin = 0
+    ymin = -3
+
+    drawing_tool.set_coordinate_system(xmin=xmin, xmax=xmin+1.5*L,
+                                       ymin=ymin, ymax=ymin+L,
+                                       #axis=True,
+                                       instruction_file='tmp_mpl.py'
+                                       )
+    #drawing_tool.set_grid(True)
+    fontsize = 18
+    from math import tan, radians
+
+    B = point(a+L, 0)
+    A = point(a, tan(radians(theta))*L)
+
+    wall = Wall(x=[A[0], B[0]], y=[A[1], B[1]], thickness=-0.25,
+                transparent=False)
+
+    angle = Arc_wText(r'$\theta$', center=B, radius=3,
+                      start_angle=180-theta, arc_angle=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 = Composition({'outer': outer_wheel, 'inner': hole})
+    wheel.set_shadow(4)
+
+    drawing_tool.set_linecolor('black')
+    N = Force(contact - 2*r*normal_vec, contact, r'$N$', text_pos='start')
+              #text_alignment='left')
+    mg = Gravity(c, 3*r, text='$Mg$')
+
+    x_const = Line(contact, contact + point(0,4))
+    x_const.set_linestyle('dotted')
+    x_const.rotate(-theta, contact)
+    # or x_const = Line(contact-2*r*normal_vec, contact+4*r*normal_vec).set_linestyle('dotted')
+    x_axis = Axis(start=contact+ 3*r*normal_vec, length=4*r,
+                  label='$x$', rotation_angle=-theta)
+
+    body  = Composition({'wheel': wheel, 'N': N, 'mg': mg})
+    fixed = Composition({'angle': angle, 'inclined wall': wall,
+                         'wheel': wheel, 'ground': ground,
+                         'x start': x_const, 'x axis': x_axis})
+
+    fig = Composition({'body': body, 'fixed elements': fixed})
+
+    fig.draw()
+    drawing_tool.savefig('tmp.png')
+    drawing_tool.savefig('tmp.pdf')
+    drawing_tool.display()
+    import time
+    time.sleep(1)
+    tangent_vec = point(normal_vec[1], -normal_vec[0])
+
+    import numpy
+    time_points = numpy.linspace(0, 1, 31)
+
+    def position(t):
+        """Position of center point of wheel."""
+        return c + 7*t**2*tangent_vec
+
+    def move(t, fig, dt=None):
+        x = position(t)
+        x0 = position(t-dt)
+        displacement = x - x0
+        fig['body'].translate(displacement)
+
+
+    animate(fig, time_points, move, pause_per_frame=0,
+            dt=time_points[1]-time_points[0])
+
+    print(str(fig))
+    print(repr(fig))
+
+inclined_plane()
+input()

+ 105 - 0
notebooks/.ipynb_checkpoints/Introduction-checkpoint.ipynb

@@ -0,0 +1,105 @@
+{
+ "cells": [
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "# Car Example"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 1,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "%matplotlib widget"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 2,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "application/vnd.jupyter.widget-view+json": {
+       "model_id": "cdd175d1ec004dbc872477df56bde58f",
+       "version_major": 2,
+       "version_minor": 0
+      },
+      "text/plain": [
+       "Canvas(toolbar=Toolbar(toolitems=[('Home', 'Reset original view', 'home', 'home'), ('Back', 'Back to previous …"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "from pysketcher import *\n",
+    "\n",
+    "R = 1    # radius of wheel\n",
+    "L = 4    # distance between wheels\n",
+    "H = 2    # height of vehicle body\n",
+    "w_1 = 5  # position of front wheel\n",
+    "\n",
+    "xmax = w_1 + 2*L + 3*R\n",
+    "drawing_tool.set_coordinate_system(xmin=0, xmax=xmax,\n",
+    "                                   ymin=-1, ymax=2*R + 3*H,\n",
+    "                                   axis=False)\n",
+    "\n",
+    "wheel1 = Composition({\n",
+    "    'wheel': Circle(center=(w_1, R), radius=R),\n",
+    "    'cross': Composition({'cross1': Line((w_1,0),   (w_1,2*R)),\n",
+    "                          'cross2': Line((w_1-R,R), (w_1+R,R))})})\n",
+    "wheel2 = wheel1.copy()\n",
+    "wheel2.translate((L,0))\n",
+    "\n",
+    "under = Rectangle(lower_left_corner=(w_1-2*R, 2*R),\n",
+    "                  width=2*R + L + 2*R, height=H)\n",
+    "over  = Rectangle(lower_left_corner=(w_1, 2*R + H),\n",
+    "                  width=2.5*R, height=1.25*H)\n",
+    "\n",
+    "wheels = Composition({'wheel1': wheel1, 'wheel2': wheel2})\n",
+    "body = Composition({'under': under, 'over': over})\n",
+    "\n",
+    "vehicle = Composition({'wheels': wheels, 'body': body})\n",
+    "ground = Wall(x=[R, xmax], y=[0, 0], thickness=-0.3*R)\n",
+    "\n",
+    "fig = Composition({'vehicle': vehicle, 'ground': ground})\n",
+    "fig.draw()  # send all figures to plotting backend\n",
+    "\n",
+    "drawing_tool.display()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": []
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python 3",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.8.5"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}

+ 126 - 0
notebooks/.ipynb_checkpoints/Oscillator1-checkpoint.ipynb

@@ -0,0 +1,126 @@
+{
+ "cells": [
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "# Oscillator Sketch1"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 1,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "%matplotlib widget"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 2,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "application/vnd.jupyter.widget-view+json": {
+       "model_id": "6ec245ab35354ff8895a58636abc2368",
+       "version_major": 2,
+       "version_minor": 0
+      },
+      "text/plain": [
+       "Canvas(toolbar=Toolbar(toolitems=[('Home', 'Reset original view', 'home', 'home'), ('Back', 'Back to previous …"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "\"\"\"Draw mechanical vibration system.\"\"\"\n",
+    "\n",
+    "from pysketcher import *\n",
+    "\n",
+    "L = 12.\n",
+    "H = L/6\n",
+    "W = L/6\n",
+    "\n",
+    "xmax = L\n",
+    "drawing_tool.set_coordinate_system(xmin=-L, xmax=xmax,\n",
+    "                                   ymin=-1, ymax=L+H,\n",
+    "                                   axis=False,\n",
+    "                                   instruction_file='tmp_mpl.py')\n",
+    "x = 0\n",
+    "drawing_tool.set_linecolor('black')\n",
+    "\n",
+    "def make_dashpot(x):\n",
+    "    d_start = (-L,2*H)\n",
+    "    d = Dashpot(start=d_start, total_length=L+x, width=W,\n",
+    "                bar_length=3*H/2, dashpot_length=L/2, piston_pos=H+x)\n",
+    "    d.rotate(-90, d_start)\n",
+    "    return d\n",
+    "\n",
+    "def make_spring(x):\n",
+    "    s_start = (-L,4*H)\n",
+    "    s = Spring(start=s_start, length=L+x, bar_length=3*H/2, teeth=True)\n",
+    "    s.rotate(-90, s_start)\n",
+    "    return s\n",
+    "\n",
+    "d = make_dashpot(0)\n",
+    "s = make_spring(0)\n",
+    "\n",
+    "M = Rectangle((0,H), 4*H, 4*H).set_linewidth(4)\n",
+    "left_wall = Rectangle((-L,0),H/10,L).set_filled_curves(pattern='/')\n",
+    "ground = Wall(x=[-L/2,L], y=[0,0], thickness=-H/10)\n",
+    "wheel1 = Circle((H,H/2), H/2)\n",
+    "wheel2 = wheel1.copy()\n",
+    "wheel2.translate(point(2*H, 0))\n",
+    "\n",
+    "fontsize = 18\n",
+    "text_m = Text('$m$', (2*H, H+2*H), fontsize=fontsize)\n",
+    "text_ku = Text('$ku$', (-L/2, H+4*H), fontsize=fontsize)\n",
+    "text_bv = Text(\"$bu'$\", (-L/2, H), fontsize=fontsize)\n",
+    "x_axis = Axis((2*H, L), H, '$u(t)$', fontsize=fontsize,\n",
+    "              label_spacing=(0.04, -0.01))\n",
+    "x_axis_start = Line((2*H, L-H/4), (2*H, L+H/4)).set_linewidth(4)\n",
+    "\n",
+    "fig = Composition({\n",
+    "    'spring': s, 'mass': M, 'left wall': left_wall,\n",
+    "    'ground': ground, 'wheel1': wheel1, 'wheel2': wheel2,\n",
+    "    'text_m': text_m, 'text_ku': text_ku,\n",
+    "    'x_axis': x_axis, 'x_axis_start': x_axis_start})\n",
+    "\n",
+    "fig.draw()\n",
+    "drawing_tool.display()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": []
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python 3",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.8.5"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}

+ 100 - 0
notebooks/.ipynb_checkpoints/beam1-checkpoint.ipynb

@@ -0,0 +1,100 @@
+{
+ "cells": [
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "# Beam"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 1,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "%matplotlib widget"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 2,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "application/vnd.jupyter.widget-view+json": {
+       "model_id": "89f7ba3c96cc4fc7b2734667b00b9ba4",
+       "version_major": 2,
+       "version_minor": 0
+      },
+      "text/plain": [
+       "Canvas(toolbar=Toolbar(toolitems=[('Home', 'Reset original view', 'home', 'home'), ('Back', 'Back to previous …"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "\"\"\"A very simple beam.\"\"\"\n",
+    "from pysketcher import *\n",
+    "\n",
+    "L = 8.0\n",
+    "H = 1.0\n",
+    "xpos = 2.0\n",
+    "ypos = 3.0\n",
+    "\n",
+    "drawing_tool.set_coordinate_system(xmin=0, xmax=xpos+1.2*L,\n",
+    "                                   ymin=0, ymax=ypos+5*H,\n",
+    "                                   axis=True)\n",
+    "drawing_tool.set_linecolor('blue')\n",
+    "drawing_tool.set_grid(True)\n",
+    "drawing_tool.set_fontsize(22)\n",
+    "\n",
+    "P0 = point(xpos,ypos)\n",
+    "main = Rectangle(P0, L, H)\n",
+    "h = L/16  # size of support, clamped wall etc\n",
+    "support = SimplySupportedBeam(P0, h)\n",
+    "clamped = Rectangle(P0 + point(L, 0) - point(0,2*h), h, 6*h).set_filled_curves(pattern='/')\n",
+    "F_pt = point(P0[0]+L/2, P0[1]+H)\n",
+    "force = Force(F_pt + point(0,2*H), F_pt, '$F$').set_linewidth(3)\n",
+    "L_dim = Distance_wText((xpos,P0[1]-3*h), (xpos+L,P0[1]-3*h), '$L$')\n",
+    "beam = Composition({'main': main, 'simply supported end': support,\n",
+    "                    'clamped end': clamped, 'force': force,\n",
+    "                    'L': L_dim})\n",
+    "beam.draw()\n",
+    "beam.draw_dimensions()\n",
+    "drawing_tool.display()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": []
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python 3",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.8.5"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}

+ 195 - 0
notebooks/.ipynb_checkpoints/beam2-checkpoint.ipynb

@@ -0,0 +1,195 @@
+{
+ "cells": [
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "# Beam2"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 1,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "%matplotlib widget"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 2,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from pysketcher import *"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 3,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import time"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 4,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "\"\"\"A more sophisticated beam than in beam1.py.\"\"\"\n",
+    "def beam():\n",
+    "    L = 8.0\n",
+    "    a = 3*L/4\n",
+    "    b = L - a\n",
+    "    H = 1.0\n",
+    "    xpos = 0.0\n",
+    "    ypos = 3.0\n",
+    "\n",
+    "    drawing_tool.set_coordinate_system(\n",
+    "        xmin=-3, xmax=xpos+1.5*L,\n",
+    "        ymin=0, ymax=ypos+5*H,\n",
+    "        axis=False)\n",
+    "    drawing_tool.set_linecolor('blue')\n",
+    "    #drawing_tool.set_grid(True)\n",
+    "    drawing_tool.set_fontsize(16)\n",
+    "\n",
+    "    A = point(xpos,ypos)\n",
+    "\n",
+    "    beam = Rectangle(A, L, H)\n",
+    "\n",
+    "    h = L/16  # size of support, clamped wall etc\n",
+    "\n",
+    "    clamped = Rectangle(A - point(h,0) - point(0,2*h), h,\n",
+    "                        6*h).set_filled_curves(pattern='/')\n",
+    "\n",
+    "    load = ConstantBeamLoad(A + point(0,H), L, H)\n",
+    "    load.set_linewidth(1).set_linecolor('black')\n",
+    "    load_text = Text('$w$',\n",
+    "                     load.geometric_features()['mid_top'] +\n",
+    "                     point(0,h/2.))\n",
+    "\n",
+    "    B = A + point(a, 0)\n",
+    "    C = B + point(b, 0)\n",
+    "\n",
+    "    support = SimplySupportedBeam(B, h)  # pt B is simply supported\n",
+    "\n",
+    "\n",
+    "    R1 = Force(A-point(0,2*H), A, '$R_1$', text_spacing=1./50)\n",
+    "    R1.set_linewidth(3).set_linecolor('black')\n",
+    "    R2 = Force(B-point(0,2*H),\n",
+    "               support.geometric_features()['mid_support'],\n",
+    "               '$R_2$', text_spacing=1./50)\n",
+    "    R2.set_linewidth(3).set_linecolor('black')\n",
+    "    M1 = Moment('$M_1$', center=A + point(-H, H/2), radius=H/2,\n",
+    "                left=True, text_spacing=1/30.)\n",
+    "    M1.set_linecolor('black')\n",
+    "\n",
+    "    ab_level = point(0, 3*h)\n",
+    "    a_dim = Distance_wText(A - ab_level, B - ab_level, '$a$')\n",
+    "    b_dim = Distance_wText(B - ab_level, C - ab_level, '$b$')\n",
+    "    dims = Composition({'a': a_dim, 'b': b_dim})\n",
+    "    symbols = Composition(\n",
+    "        {'R1': R1, 'R2': R2, 'M1': M1,\n",
+    "         'w': load, 'w text': load_text,\n",
+    "         'A': Text('$A$', A+point(0.7*h,-0.9*h)),\n",
+    "         'B': Text('$B$',\n",
+    "                   support.geometric_features()['mid_support']-\n",
+    "                   point(1.25*h,0)),\n",
+    "         'C': Text('$C$', C+point(h/2,-h/2))})\n",
+    "\n",
+    "    x_axis = Axis(A + point(L+h, H/2), 2*H, '$x$',).\\\n",
+    "             set_linecolor('black')\n",
+    "    y_axis = Axis(A + point(0,H/2), 3.5*H, '$y$',\n",
+    "                  label_alignment='left',\n",
+    "                  rotation_angle=90).set_linecolor('black')\n",
+    "    axes = Composition({'x axis': x_axis, 'y axis': y_axis})\n",
+    "\n",
+    "    annotations = Composition({'dims': dims, 'symbols': symbols,\n",
+    "                               'axes': axes})\n",
+    "    beam = Composition({'beam': beam, 'support': support,\n",
+    "                        'clamped end': clamped, 'load': load})\n",
+    "\n",
+    "    def deflection(x, a, b, w):\n",
+    "        import numpy as np\n",
+    "        R1 = 5./8*w*a - 3*w*b**2/(4*a)\n",
+    "        R2 = 3./8*w*a + w*b + 3*w*b**2/(4*a)\n",
+    "        M1 = R1*a/3 - w*a**2/12\n",
+    "        y = -(M1/2.)*x**2 + 1./6*R1*x**3 - w/24.*x**4 + \\\n",
+    "            1./6*R2*np.where(x > a, 1, 0)*(x-a)**3\n",
+    "        return y\n",
+    "\n",
+    "    x = linspace(0, L, 101)\n",
+    "    y = deflection(x, a, b, w=1.0)\n",
+    "    y /= abs(y.max() - y.min())\n",
+    "    y += ypos + H/2\n",
+    "\n",
+    "    elastic_line = Curve(x, y).\\\n",
+    "                   set_linecolor('red').\\\n",
+    "                   set_linestyle('dashed').\\\n",
+    "                   set_linewidth(3)\n",
+    "\n",
+    "    beam.draw()\n",
+    "    beam.draw_dimensions()\n",
+    "    annotations.draw()\n",
+    "    elastic_line.draw()\n",
+    "    drawing_tool.display()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 5,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "application/vnd.jupyter.widget-view+json": {
+       "model_id": "8ddace09b4ed4476a8217d191eb07ce1",
+       "version_major": 2,
+       "version_minor": 0
+      },
+      "text/plain": [
+       "Canvas(toolbar=Toolbar(toolitems=[('Home', 'Reset original view', 'home', 'home'), ('Back', 'Back to previous …"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "beam()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": []
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python 3",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.8.5"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}

+ 121 - 0
notebooks/.ipynb_checkpoints/flowovergaussian-checkpoint.ipynb

@@ -0,0 +1,121 @@
+{
+ "cells": [
+  {
+   "cell_type": "code",
+   "execution_count": 1,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "%matplotlib widget"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 2,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "application/vnd.jupyter.widget-view+json": {
+       "model_id": "6c60f5bb1705487882af9db4454aa700",
+       "version_major": 2,
+       "version_minor": 0
+      },
+      "text/plain": [
+       "Canvas(toolbar=Toolbar(toolitems=[('Home', 'Reset original view', 'home', 'home'), ('Back', 'Back to previous …"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "from pysketcher import *\n",
+    "from numpy import exp, linspace\n",
+    "\n",
+    "W = 5    # upstream area\n",
+    "L = 10   # downstread area\n",
+    "H = 4    # height\n",
+    "sigma = 2\n",
+    "alpha = 2\n",
+    "\n",
+    "drawing_tool.set_coordinate_system(xmin=0, xmax=W+L+1,\n",
+    "                                   ymin=-2, ymax=H+1,\n",
+    "                                   axis=True)\n",
+    "drawing_tool.set_linecolor('blue')\n",
+    "\n",
+    "# Create bottom\n",
+    "\n",
+    "def gaussian(x):\n",
+    "    return alpha*exp(-(x-W)**2/(0.5*sigma**2))\n",
+    "\n",
+    "x = linspace(0, W+L, 51)\n",
+    "y = gaussian(x)\n",
+    "wall = Wall(x, y, thickness=-0.3, pattern='|', transparent=True).\\\n",
+    "       set_linecolor('brown')\n",
+    "wall['eraser'].set_linecolor('white')\n",
+    "def velprofile(y):\n",
+    "    return [2*y*(2*H-y)/H**2, 0]\n",
+    "\n",
+    "inlet_profile = VelocityProfile((0,0), H, velprofile, 5)\n",
+    "symmetry_line = Line((0,H), (W+L,H))\n",
+    "symmetry_line.set_linestyle('dashed')\n",
+    "outlet = Line((W+L,0), (W+L,H))\n",
+    "outlet.set_linestyle('dashed')\n",
+    "\n",
+    "fig = Composition({\n",
+    "    'bottom': wall,\n",
+    "    'inlet': inlet_profile,\n",
+    "    'symmetry line': symmetry_line,\n",
+    "    'outlet': outlet,\n",
+    "    })\n",
+    "\n",
+    "fig.draw()  # send all figures to plotting backend\n",
+    "\n",
+    "vx, vy = velprofile(H/2.)\n",
+    "symbols = {\n",
+    "    'alpha': Distance_wText((W,0), (W,alpha), r'$\\alpha$'),\n",
+    "    'W': Distance_wText((0,-0.5), (W,-0.5), r'$W$',\n",
+    "                          text_spacing=-1./30),\n",
+    "    'L': Distance_wText((W,-0.5), (W+L,-0.5), r'$L$',\n",
+    "                          text_spacing=-1./30),\n",
+    "    'v(y)': Text('$v(y)$', (H/2., vx)),\n",
+    "    'dashed line': Line((W-2.5*sigma,0), (W+2.5*sigma,0)).\\\n",
+    "                   set_linestyle('dotted').set_linecolor('black'),\n",
+    "    }\n",
+    "symbols = Composition(symbols)\n",
+    "symbols.draw()\n",
+    "\n",
+    "drawing_tool.display()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": []
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python 3",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.8.5"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}

+ 209 - 0
notebooks/.ipynb_checkpoints/pendulum1-checkpoint.ipynb

@@ -0,0 +1,209 @@
+{
+ "cells": [
+  {
+   "cell_type": "code",
+   "execution_count": 1,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "%matplotlib widget"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 2,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "application/vnd.jupyter.widget-view+json": {
+       "model_id": "e995955f250e4a19b807f8758599660c",
+       "version_major": 2,
+       "version_minor": 0
+      },
+      "text/plain": [
+       "Canvas(toolbar=Toolbar(toolitems=[('Home', 'Reset original view', 'home', 'home'), ('Back', 'Back to previous …"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "from pysketcher import *\n",
+    "\n",
+    "H = 7.\n",
+    "W = 6.\n",
+    "\n",
+    "drawing_tool.set_coordinate_system(xmin=0, xmax=W,\n",
+    "                                   ymin=0, ymax=H,\n",
+    "                                   axis=False)\n",
+    "#drawing_tool.set_grid(True)\n",
+    "drawing_tool.set_linecolor('blue')\n",
+    "\n",
+    "L = 5*H/7          # length\n",
+    "P = (W/6, 0.85*H)  # rotation point\n",
+    "a = 40             # angle\n",
+    "\n",
+    "vertical = Line(P, P-point(0,L))\n",
+    "path = Arc(P, L, -90, a)\n",
+    "angle = Arc_wText(r'$\\theta$', P, L/4, -90, a, text_spacing=1/30.)\n",
+    "\n",
+    "rod = Line(P, P + L*point(sin(radians(a)), -L*cos(radians(a))))\n",
+    "# or shorter (and more reliable)\n",
+    "mass_pt = path.geometric_features()['end']\n",
+    "rod = Line(P, mass_pt)\n",
+    "\n",
+    "mass = Circle(center=mass_pt, radius=L/20.)\n",
+    "mass.set_filled_curves(color='blue')\n",
+    "rod_vec = rod.geometric_features()['end'] - \\\n",
+    "          rod.geometric_features()['start']\n",
+    "unit_rod_vec = unit_vec(rod_vec)\n",
+    "mass_symbol = Text('$m$', mass_pt + L/10*unit_rod_vec)\n",
+    "\n",
+    "length = Distance_wText(P, mass_pt, '$L$')\n",
+    "# Displace length indication\n",
+    "length.translate(L/15*point(cos(radians(a)), sin(radians(a))))\n",
+    "gravity = Gravity(start=P+point(0.8*L,0), length=L/3)\n",
+    "\n",
+    "def set_dashed_thin_blackline(*objects):\n",
+    "    \"\"\"Set linestyle of objects to dashed, black, width=1.\"\"\"\n",
+    "    for obj in objects:\n",
+    "        obj.set_linestyle('dashed')\n",
+    "        obj.set_linecolor('black')\n",
+    "        obj.set_linewidth(1)\n",
+    "\n",
+    "set_dashed_thin_blackline(vertical, path)\n",
+    "\n",
+    "fig = Composition(\n",
+    "    {'body': mass, 'rod': rod,\n",
+    "     'vertical': vertical, 'theta': angle, 'path': path,\n",
+    "     'g': gravity, 'L': length, 'm': mass_symbol})\n",
+    "\n",
+    "fig.draw()\n",
+    "drawing_tool.display()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 3,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdin",
+     "output_type": "stream",
+     "text": [
+      "Press Return to make free body diagram:  \n"
+     ]
+    }
+   ],
+   "source": [
+    "# Draw free body diagram in several different versions\n",
+    "# (note that we build body_diagram, erase and draw,\n",
+    "# add elements to body_diagram, erase and draw, and so on)\n",
+    "\n",
+    "drawing_tool.erase()\n",
+    "\n",
+    "drawing_tool.set_linecolor('black')\n",
+    "\n",
+    "rod_start = rod.geometric_features()['start']  # Point P\n",
+    "vertical2 = Line(rod_start, rod_start + point(0,-L/3))\n",
+    "set_dashed_thin_blackline(vertical2)\n",
+    "set_dashed_thin_blackline(rod)\n",
+    "angle2 = Arc_wText(r'$\\theta$', rod_start, L/6, -90, a,\n",
+    "                   text_spacing=1/30.)\n",
+    "\n",
+    "mg_force  = Force(mass_pt, mass_pt + L/5*point(0,-1),\n",
+    "                  '$mg$', text_pos='end')\n",
+    "rod_force = Force(mass_pt, mass_pt - L/3*unit_vec(rod_vec),\n",
+    "                  '$S$', text_pos='end',\n",
+    "                  text_spacing=(0.03, 0.01))\n",
+    "air_force = Force(mass_pt, mass_pt -\n",
+    "                  L/6*unit_vec((rod_vec[1], -rod_vec[0])),\n",
+    "                  '$\\sim|v|v$', text_pos='end',\n",
+    "                  text_spacing=(0.04,0.005))\n",
+    "\n",
+    "body_diagram = Composition(\n",
+    "    {'mg': mg_force, 'S': rod_force, 'rod': rod,\n",
+    "     'vertical': vertical2, 'theta': angle2,\n",
+    "     'body': mass, 'm': mass_symbol})\n",
+    "\n",
+    "body_diagram.draw()\n",
+    "drawing_tool.display('Free body diagram')"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 4,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "drawing_tool.adjust_coordinate_system(body_diagram.minmax_coordinates(), 90)\n",
+    "drawing_tool.display('Free body diagram')"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 5,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "drawing_tool.erase()\n",
+    "body_diagram['air'] = air_force\n",
+    "body_diagram.draw()\n",
+    "drawing_tool.display('Free body diagram')"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 6,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "drawing_tool.erase()\n",
+    "x0y0 = Text('$(x_0,y_0)$', P + point(-0.4,-0.1))\n",
+    "ir = Force(P, P + L/10*unit_vec(rod_vec),\n",
+    "           r'$\\boldsymbol{i}_r$', text_pos='end',\n",
+    "           text_spacing=(0.015,0))\n",
+    "ith = Force(P, P + L/10*unit_vec((-rod_vec[1], rod_vec[0])),\n",
+    "           r'$\\boldsymbol{i}_{\\theta}$', text_pos='end',\n",
+    "            text_spacing=(0.02,0.005))\n",
+    "\n",
+    "body_diagram['ir'] = ir\n",
+    "body_diagram['ith'] = ith\n",
+    "body_diagram['origin'] = x0y0\n",
+    "\n",
+    "body_diagram.draw()\n",
+    "drawing_tool.display('Free body diagram')"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": []
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python 3",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.8.5"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}

+ 217 - 0
notebooks/.ipynb_checkpoints/test-checkpoint.ipynb

@@ -0,0 +1,217 @@
+{
+ "cells": [
+  {
+   "cell_type": "code",
+   "execution_count": 1,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "%matplotlib widget"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 2,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import matplotlib.pyplot as plt"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 4,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import numpy as np"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 3,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "application/vnd.jupyter.widget-view+json": {
+       "model_id": "6f26029d289240278d8e71fc761421c7",
+       "version_major": 2,
+       "version_minor": 0
+      },
+      "text/plain": [
+       "Canvas(toolbar=Toolbar(toolitems=[('Home', 'Reset original view', 'home', 'home'), ('Back', 'Back to previous …"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/plain": [
+       "[<matplotlib.lines.Line2D at 0x202d4cad9a0>]"
+      ]
+     },
+     "execution_count": 3,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "fig, ax = plt.subplots()\n",
+    "fig.canvas.width = '7in'\n",
+    "fig.canvas.height= '5in'\n",
+    "\n",
+    "# if I hide the header here, I get a libpng error\n",
+    "# fig.canvas.header_visible = False\n",
+    "\n",
+    "ax.plot([1,2,3], [4,5,3])"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 4,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# hiding after rendering works\n",
+    "fig.canvas.header_visible = False"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 5,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "application/vnd.jupyter.widget-view+json": {
+       "model_id": "e0b21d300cff4df3b616fdae74984486",
+       "version_major": 2,
+       "version_minor": 0
+      },
+      "text/plain": [
+       "Canvas(toolbar=Toolbar(toolitems=[('Home', 'Reset original view', 'home', 'home'), ('Back', 'Back to previous …"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/plain": [
+       "[<matplotlib.lines.Line2D at 0x202d6feb730>]"
+      ]
+     },
+     "execution_count": 5,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "# hiding together with calls to toolbar options, work.\n",
+    "fig, ax = plt.subplots()\n",
+    "fig.canvas.width = '7in'\n",
+    "fig.canvas.height= '5in'\n",
+    "\n",
+    "fig.canvas.toolbar_visible = False\n",
+    "fig.canvas.header_visible = False\n",
+    "\n",
+    "ax.plot([1,2,3], [4,5,3])"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 5,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "application/vnd.jupyter.widget-view+json": {
+       "model_id": "7012e8fcbca04b808f0da70dbe73c223",
+       "version_major": 2,
+       "version_minor": 0
+      },
+      "text/plain": [
+       "AppLayout(children=(FloatSlider(value=1.0, description='Factor:', layout=Layout(grid_area='footer', margin='0p…"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "# When using the `widget` backend from ipympl,\n",
+    "# fig.canvas is a proper Jupyter interactive widget, which can be embedded in\n",
+    "# an ipywidgets layout. See https://ipywidgets.readthedocs.io/en/stable/examples/Layout%20Templates.html\n",
+    "\n",
+    "# One can bound figure attributes to other widget values.\n",
+    "from ipywidgets import AppLayout, FloatSlider\n",
+    "\n",
+    "plt.ioff()\n",
+    "\n",
+    "slider = FloatSlider(\n",
+    "    orientation='horizontal',\n",
+    "    description='Factor:',\n",
+    "    value=1.0,\n",
+    "    min=0.02,\n",
+    "    max=2.0\n",
+    ")\n",
+    "\n",
+    "slider.layout.margin = '0px 30% 0px 30%'\n",
+    "slider.layout.width = '40%'\n",
+    "\n",
+    "fig = plt.figure()\n",
+    "fig.canvas.header_visible = False\n",
+    "fig.canvas.layout.min_height = '400px'\n",
+    "plt.title('Plotting: y=sin({} * x)'.format(slider.value))\n",
+    "\n",
+    "x = np.linspace(0, 20, 500)\n",
+    "\n",
+    "lines = plt.plot(x, np.sin(slider.value * x))\n",
+    "\n",
+    "def update_lines(change):\n",
+    "    plt.title('Plotting: y=sin({} * x)'.format(change.new))\n",
+    "    lines[0].set_data(x, np.sin(change.new * x))\n",
+    "    fig.canvas.draw()\n",
+    "    fig.canvas.flush_events()\n",
+    "\n",
+    "slider.observe(update_lines, names='value')\n",
+    "\n",
+    "AppLayout(\n",
+    "    center=fig.canvas,\n",
+    "    footer=slider,\n",
+    "    pane_heights=[0, 6, 1]\n",
+    ")"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": []
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python 3",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.8.5"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}

+ 321 - 0
notebooks/.ipynb_checkpoints/wheelonInclinedPlane-checkpoint.ipynb

@@ -0,0 +1,321 @@
+{
+ "cells": [
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "# Wheel on Inclined Plane"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 1,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "%matplotlib widget"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 2,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from ipywidgets import FloatSlider"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 3,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import numpy"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 4,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from pysketcher import *\n",
+    "\n",
+    "def inclined_plane():\n",
+    "    theta = 30.\n",
+    "    L = 10.\n",
+    "    a = 1.\n",
+    "    xmin = 0\n",
+    "    ymin = -3\n",
+    "\n",
+    "    drawing_tool.set_coordinate_system(xmin=xmin, xmax=xmin+1.5*L,\n",
+    "                                       ymin=ymin, ymax=ymin+L+1,\n",
+    "                                       #axis=True,\n",
+    "                                       instruction_file='tmp_mpl.py'\n",
+    "                                       )\n",
+    "    #drawing_tool.set_grid(True)\n",
+    "    fontsize = 18\n",
+    "    from math import tan, radians\n",
+    "\n",
+    "    B = point(a+L, 0)\n",
+    "    A = point(a, tan(radians(theta))*L)\n",
+    "\n",
+    "    wall = Wall(x=[A[0], B[0]], y=[A[1], B[1]], thickness=-0.25,\n",
+    "                transparent=False)\n",
+    "\n",
+    "    angle = Arc_wText(r'$\\theta$', center=B, radius=3,\n",
+    "                      start_angle=180-theta, arc_angle=theta,\n",
+    "                      fontsize=fontsize)\n",
+    "    angle.set_linecolor('black')\n",
+    "    angle.set_linewidth(1)\n",
+    "\n",
+    "    ground = Line((B[0]-L/10., 0), (B[0]-L/2.,0))\n",
+    "    ground.set_linecolor('black')\n",
+    "    ground.set_linestyle('dashed')\n",
+    "    ground.set_linewidth(1)\n",
+    "\n",
+    "    r = 1  # radius of wheel\n",
+    "    help_line = Line(A, B)\n",
+    "    x = a + 3*L/10.; y = help_line(x=x)\n",
+    "    contact = point(x, y)\n",
+    "    normal_vec = point(sin(radians(theta)), cos(radians(theta)))\n",
+    "    tangent_vec = point(cos(radians(theta)), -sin(radians(theta)))\n",
+    "    c = contact + r*normal_vec\n",
+    "    outer_wheel = Circle(c, r)\n",
+    "    outer_wheel.set_linecolor('blue')\n",
+    "    outer_wheel.set_filled_curves('blue')\n",
+    "    hole = Circle(c, r/2.)\n",
+    "    hole.set_linecolor('blue')\n",
+    "    hole.set_filled_curves('white')\n",
+    "\n",
+    "    wheel = Composition({'outer': outer_wheel, 'inner': hole})\n",
+    "    wheel.set_shadow(4)\n",
+    "\n",
+    "    drawing_tool.set_linecolor('black')\n",
+    "    N = Force(contact - 2*r*normal_vec, contact, r'$N$', text_pos='start')\n",
+    "    V = Force(c, c + 0.001*tangent_vec, r'$V$', text_pos='end')\n",
+    "    V.set_linecolor('red')\n",
+    "    mg = Gravity(c, 3*r, text='$Mg$')\n",
+    "\n",
+    "    x_const = Line(contact, contact + point(0,4))\n",
+    "    x_const.set_linestyle('dotted')\n",
+    "    x_const.rotate(-theta, contact)\n",
+    "    # or x_const = Line(contact-2*r*normal_vec, contact+4*r*normal_vec).set_linestyle('dotted')\n",
+    "    x_axis = Axis(start=contact+ 3*r*normal_vec, length=4*r,\n",
+    "                  label='$x$', rotation_angle=-theta)\n",
+    "\n",
+    "    body  = Composition({'wheel': wheel, 'N': N, 'mg': mg})\n",
+    "    fixed = Composition({'angle': angle, 'inclined wall': wall,\n",
+    "                         'wheel': wheel, 'V': V, 'ground': ground,\n",
+    "                         'x start': x_const, 'x axis': x_axis})\n",
+    "\n",
+    "    fig = Composition({'body': body, 'fixed elements': fixed})\n",
+    "    fig.draw()\n",
+    "    return (fig,normal_vec,tangent_vec,c)\n",
+    "\n",
+    "def position(t):\n",
+    "    \"\"\"Position of center point of wheel.\"\"\"\n",
+    "    global tangent_vec,c\n",
+    "    return c + 7*t**2*tangent_vec\n",
+    "\n",
+    "def speed(t):\n",
+    "    global tangent_vec\n",
+    "    return 0.14*t*tangent_vec    \n",
+    "\n",
+    "t = 0\n",
+    "\n",
+    "def move(change):\n",
+    "    global fig,t\n",
+    "    dt = change.new - t \n",
+    "    t = change.new\n",
+    "    drawing_tool.erase()\n",
+    "    x = position(t)\n",
+    "    x0 = position(t-dt)\n",
+    "    displacement = x - x0\n",
+    "    fig['fixed elements']['V']['arrow']['line'] = Line(x,x+speed(t))\n",
+    "    fig['body'].translate(displacement)\n",
+    "    fig.draw()\n",
+    "    drawing_tool.display()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 5,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "slider = FloatSlider(\n",
+    "    orientation='horizontal',\n",
+    "    description='Time:',\n",
+    "    value=0.0,\n",
+    "    min=0.0,\n",
+    "    max=1.0,\n",
+    "    step = 1.0 / 30\n",
+    ")\n",
+    "slider.observe(move, 'value')"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 6,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "application/vnd.jupyter.widget-view+json": {
+       "model_id": "1af7c7deda7b418a8a170ec0b34505ec",
+       "version_major": 2,
+       "version_minor": 0
+      },
+      "text/plain": [
+       "FloatSlider(value=0.0, description='Time:', max=1.0, step=0.03333333333333333)"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "slider"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 7,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "application/vnd.jupyter.widget-view+json": {
+       "model_id": "da53e1cea5d447b5af22e27766617152",
+       "version_major": 2,
+       "version_minor": 0
+      },
+      "text/plain": [
+       "Canvas(toolbar=Toolbar(toolitems=[('Home', 'Reset original view', 'home', 'home'), ('Back', 'Back to previous …"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "fig,normal_vec,tangent_vec,c = inclined_plane()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "speed(0)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "position(0)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 8,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "array([4.5       , 4.90747729])"
+      ]
+     },
+     "execution_count": 8,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "fig['fixed elements']['V']['arrow']['line'].x"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 9,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "array([4.5       , 4.90747729])"
+      ]
+     },
+     "execution_count": 9,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "fig['fixed elements']['V']['arrow']['line'].y"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "speed(0.3)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "t"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "position(t)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": []
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python 3",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.8.5"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}

+ 105 - 0
notebooks/Introduction.ipynb

@@ -0,0 +1,105 @@
+{
+ "cells": [
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "# Car Example"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 1,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "%matplotlib widget"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 2,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "application/vnd.jupyter.widget-view+json": {
+       "model_id": "cdd175d1ec004dbc872477df56bde58f",
+       "version_major": 2,
+       "version_minor": 0
+      },
+      "text/plain": [
+       "Canvas(toolbar=Toolbar(toolitems=[('Home', 'Reset original view', 'home', 'home'), ('Back', 'Back to previous …"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "from pysketcher import *\n",
+    "\n",
+    "R = 1    # radius of wheel\n",
+    "L = 4    # distance between wheels\n",
+    "H = 2    # height of vehicle body\n",
+    "w_1 = 5  # position of front wheel\n",
+    "\n",
+    "xmax = w_1 + 2*L + 3*R\n",
+    "drawing_tool.set_coordinate_system(xmin=0, xmax=xmax,\n",
+    "                                   ymin=-1, ymax=2*R + 3*H,\n",
+    "                                   axis=False)\n",
+    "\n",
+    "wheel1 = Composition({\n",
+    "    'wheel': Circle(center=(w_1, R), radius=R),\n",
+    "    'cross': Composition({'cross1': Line((w_1,0),   (w_1,2*R)),\n",
+    "                          'cross2': Line((w_1-R,R), (w_1+R,R))})})\n",
+    "wheel2 = wheel1.copy()\n",
+    "wheel2.translate((L,0))\n",
+    "\n",
+    "under = Rectangle(lower_left_corner=(w_1-2*R, 2*R),\n",
+    "                  width=2*R + L + 2*R, height=H)\n",
+    "over  = Rectangle(lower_left_corner=(w_1, 2*R + H),\n",
+    "                  width=2.5*R, height=1.25*H)\n",
+    "\n",
+    "wheels = Composition({'wheel1': wheel1, 'wheel2': wheel2})\n",
+    "body = Composition({'under': under, 'over': over})\n",
+    "\n",
+    "vehicle = Composition({'wheels': wheels, 'body': body})\n",
+    "ground = Wall(x=[R, xmax], y=[0, 0], thickness=-0.3*R)\n",
+    "\n",
+    "fig = Composition({'vehicle': vehicle, 'ground': ground})\n",
+    "fig.draw()  # send all figures to plotting backend\n",
+    "\n",
+    "drawing_tool.display()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": []
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python 3",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.8.5"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}

+ 126 - 0
notebooks/Oscillator1.ipynb

@@ -0,0 +1,126 @@
+{
+ "cells": [
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "# Oscillator Sketch1"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 1,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "%matplotlib widget"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 2,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "application/vnd.jupyter.widget-view+json": {
+       "model_id": "6ec245ab35354ff8895a58636abc2368",
+       "version_major": 2,
+       "version_minor": 0
+      },
+      "text/plain": [
+       "Canvas(toolbar=Toolbar(toolitems=[('Home', 'Reset original view', 'home', 'home'), ('Back', 'Back to previous …"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "\"\"\"Draw mechanical vibration system.\"\"\"\n",
+    "\n",
+    "from pysketcher import *\n",
+    "\n",
+    "L = 12.\n",
+    "H = L/6\n",
+    "W = L/6\n",
+    "\n",
+    "xmax = L\n",
+    "drawing_tool.set_coordinate_system(xmin=-L, xmax=xmax,\n",
+    "                                   ymin=-1, ymax=L+H,\n",
+    "                                   axis=False,\n",
+    "                                   instruction_file='tmp_mpl.py')\n",
+    "x = 0\n",
+    "drawing_tool.set_linecolor('black')\n",
+    "\n",
+    "def make_dashpot(x):\n",
+    "    d_start = (-L,2*H)\n",
+    "    d = Dashpot(start=d_start, total_length=L+x, width=W,\n",
+    "                bar_length=3*H/2, dashpot_length=L/2, piston_pos=H+x)\n",
+    "    d.rotate(-90, d_start)\n",
+    "    return d\n",
+    "\n",
+    "def make_spring(x):\n",
+    "    s_start = (-L,4*H)\n",
+    "    s = Spring(start=s_start, length=L+x, bar_length=3*H/2, teeth=True)\n",
+    "    s.rotate(-90, s_start)\n",
+    "    return s\n",
+    "\n",
+    "d = make_dashpot(0)\n",
+    "s = make_spring(0)\n",
+    "\n",
+    "M = Rectangle((0,H), 4*H, 4*H).set_linewidth(4)\n",
+    "left_wall = Rectangle((-L,0),H/10,L).set_filled_curves(pattern='/')\n",
+    "ground = Wall(x=[-L/2,L], y=[0,0], thickness=-H/10)\n",
+    "wheel1 = Circle((H,H/2), H/2)\n",
+    "wheel2 = wheel1.copy()\n",
+    "wheel2.translate(point(2*H, 0))\n",
+    "\n",
+    "fontsize = 18\n",
+    "text_m = Text('$m$', (2*H, H+2*H), fontsize=fontsize)\n",
+    "text_ku = Text('$ku$', (-L/2, H+4*H), fontsize=fontsize)\n",
+    "text_bv = Text(\"$bu'$\", (-L/2, H), fontsize=fontsize)\n",
+    "x_axis = Axis((2*H, L), H, '$u(t)$', fontsize=fontsize,\n",
+    "              label_spacing=(0.04, -0.01))\n",
+    "x_axis_start = Line((2*H, L-H/4), (2*H, L+H/4)).set_linewidth(4)\n",
+    "\n",
+    "fig = Composition({\n",
+    "    'spring': s, 'mass': M, 'left wall': left_wall,\n",
+    "    'ground': ground, 'wheel1': wheel1, 'wheel2': wheel2,\n",
+    "    'text_m': text_m, 'text_ku': text_ku,\n",
+    "    'x_axis': x_axis, 'x_axis_start': x_axis_start})\n",
+    "\n",
+    "fig.draw()\n",
+    "drawing_tool.display()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": []
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python 3",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.8.5"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}

+ 100 - 0
notebooks/beam1.ipynb

@@ -0,0 +1,100 @@
+{
+ "cells": [
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "# Beam1"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 1,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "%matplotlib widget"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 2,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "application/vnd.jupyter.widget-view+json": {
+       "model_id": "803251c3e4b1454682f45545d26b341c",
+       "version_major": 2,
+       "version_minor": 0
+      },
+      "text/plain": [
+       "Canvas(toolbar=Toolbar(toolitems=[('Home', 'Reset original view', 'home', 'home'), ('Back', 'Back to previous …"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "\"\"\"A very simple beam.\"\"\"\n",
+    "from pysketcher import *\n",
+    "\n",
+    "L = 8.0\n",
+    "H = 1.0\n",
+    "xpos = 2.0\n",
+    "ypos = 3.0\n",
+    "\n",
+    "drawing_tool.set_coordinate_system(xmin=0, xmax=xpos+1.2*L,\n",
+    "                                   ymin=0, ymax=ypos+5*H,\n",
+    "                                   axis=True)\n",
+    "drawing_tool.set_linecolor('blue')\n",
+    "drawing_tool.set_grid(True)\n",
+    "drawing_tool.set_fontsize(22)\n",
+    "\n",
+    "P0 = point(xpos,ypos)\n",
+    "main = Rectangle(P0, L, H)\n",
+    "h = L/16  # size of support, clamped wall etc\n",
+    "support = SimplySupportedBeam(P0, h)\n",
+    "clamped = Rectangle(P0 + point(L, 0) - point(0,2*h), h, 6*h).set_filled_curves(pattern='/')\n",
+    "F_pt = point(P0[0]+L/2, P0[1]+H)\n",
+    "force = Force(F_pt + point(0,2*H), F_pt, '$F$').set_linewidth(3)\n",
+    "L_dim = Distance_wText((xpos,P0[1]-3*h), (xpos+L,P0[1]-3*h), '$L$')\n",
+    "beam = Composition({'main': main, 'simply supported end': support,\n",
+    "                    'clamped end': clamped, 'force': force,\n",
+    "                    'L': L_dim})\n",
+    "beam.draw()\n",
+    "beam.draw_dimensions()\n",
+    "drawing_tool.display()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": []
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python 3",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.8.5"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}

+ 195 - 0
notebooks/beam2.ipynb

@@ -0,0 +1,195 @@
+{
+ "cells": [
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "# Beam2"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 1,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "%matplotlib widget"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 2,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from pysketcher import *"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 3,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import time"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 4,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "\"\"\"A more sophisticated beam than in beam1.py.\"\"\"\n",
+    "def beam():\n",
+    "    L = 8.0\n",
+    "    a = 3*L/4\n",
+    "    b = L - a\n",
+    "    H = 1.0\n",
+    "    xpos = 0.0\n",
+    "    ypos = 3.0\n",
+    "\n",
+    "    drawing_tool.set_coordinate_system(\n",
+    "        xmin=-3, xmax=xpos+1.5*L,\n",
+    "        ymin=0, ymax=ypos+5*H,\n",
+    "        axis=False)\n",
+    "    drawing_tool.set_linecolor('blue')\n",
+    "    #drawing_tool.set_grid(True)\n",
+    "    drawing_tool.set_fontsize(16)\n",
+    "\n",
+    "    A = point(xpos,ypos)\n",
+    "\n",
+    "    beam = Rectangle(A, L, H)\n",
+    "\n",
+    "    h = L/16  # size of support, clamped wall etc\n",
+    "\n",
+    "    clamped = Rectangle(A - point(h,0) - point(0,2*h), h,\n",
+    "                        6*h).set_filled_curves(pattern='/')\n",
+    "\n",
+    "    load = ConstantBeamLoad(A + point(0,H), L, H)\n",
+    "    load.set_linewidth(1).set_linecolor('black')\n",
+    "    load_text = Text('$w$',\n",
+    "                     load.geometric_features()['mid_top'] +\n",
+    "                     point(0,h/2.))\n",
+    "\n",
+    "    B = A + point(a, 0)\n",
+    "    C = B + point(b, 0)\n",
+    "\n",
+    "    support = SimplySupportedBeam(B, h)  # pt B is simply supported\n",
+    "\n",
+    "\n",
+    "    R1 = Force(A-point(0,2*H), A, '$R_1$', text_spacing=1./50)\n",
+    "    R1.set_linewidth(3).set_linecolor('black')\n",
+    "    R2 = Force(B-point(0,2*H),\n",
+    "               support.geometric_features()['mid_support'],\n",
+    "               '$R_2$', text_spacing=1./50)\n",
+    "    R2.set_linewidth(3).set_linecolor('black')\n",
+    "    M1 = Moment('$M_1$', center=A + point(-H, H/2), radius=H/2,\n",
+    "                left=True, text_spacing=1/30.)\n",
+    "    M1.set_linecolor('black')\n",
+    "\n",
+    "    ab_level = point(0, 3*h)\n",
+    "    a_dim = Distance_wText(A - ab_level, B - ab_level, '$a$')\n",
+    "    b_dim = Distance_wText(B - ab_level, C - ab_level, '$b$')\n",
+    "    dims = Composition({'a': a_dim, 'b': b_dim})\n",
+    "    symbols = Composition(\n",
+    "        {'R1': R1, 'R2': R2, 'M1': M1,\n",
+    "         'w': load, 'w text': load_text,\n",
+    "         'A': Text('$A$', A+point(0.7*h,-0.9*h)),\n",
+    "         'B': Text('$B$',\n",
+    "                   support.geometric_features()['mid_support']-\n",
+    "                   point(1.25*h,0)),\n",
+    "         'C': Text('$C$', C+point(h/2,-h/2))})\n",
+    "\n",
+    "    x_axis = Axis(A + point(L+h, H/2), 2*H, '$x$',).\\\n",
+    "             set_linecolor('black')\n",
+    "    y_axis = Axis(A + point(0,H/2), 3.5*H, '$y$',\n",
+    "                  label_alignment='left',\n",
+    "                  rotation_angle=90).set_linecolor('black')\n",
+    "    axes = Composition({'x axis': x_axis, 'y axis': y_axis})\n",
+    "\n",
+    "    annotations = Composition({'dims': dims, 'symbols': symbols,\n",
+    "                               'axes': axes})\n",
+    "    beam = Composition({'beam': beam, 'support': support,\n",
+    "                        'clamped end': clamped, 'load': load})\n",
+    "\n",
+    "    def deflection(x, a, b, w):\n",
+    "        import numpy as np\n",
+    "        R1 = 5./8*w*a - 3*w*b**2/(4*a)\n",
+    "        R2 = 3./8*w*a + w*b + 3*w*b**2/(4*a)\n",
+    "        M1 = R1*a/3 - w*a**2/12\n",
+    "        y = -(M1/2.)*x**2 + 1./6*R1*x**3 - w/24.*x**4 + \\\n",
+    "            1./6*R2*np.where(x > a, 1, 0)*(x-a)**3\n",
+    "        return y\n",
+    "\n",
+    "    x = linspace(0, L, 101)\n",
+    "    y = deflection(x, a, b, w=1.0)\n",
+    "    y /= abs(y.max() - y.min())\n",
+    "    y += ypos + H/2\n",
+    "\n",
+    "    elastic_line = Curve(x, y).\\\n",
+    "                   set_linecolor('red').\\\n",
+    "                   set_linestyle('dashed').\\\n",
+    "                   set_linewidth(3)\n",
+    "\n",
+    "    beam.draw()\n",
+    "    beam.draw_dimensions()\n",
+    "    annotations.draw()\n",
+    "    elastic_line.draw()\n",
+    "    drawing_tool.display()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 5,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "application/vnd.jupyter.widget-view+json": {
+       "model_id": "8ddace09b4ed4476a8217d191eb07ce1",
+       "version_major": 2,
+       "version_minor": 0
+      },
+      "text/plain": [
+       "Canvas(toolbar=Toolbar(toolitems=[('Home', 'Reset original view', 'home', 'home'), ('Back', 'Back to previous …"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "beam()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": []
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python 3",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.8.5"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}

+ 128 - 0
notebooks/flowovergaussian.ipynb

@@ -0,0 +1,128 @@
+{
+ "cells": [
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "# Flow over Gaussian"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 3,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "%matplotlib widget"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 2,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "application/vnd.jupyter.widget-view+json": {
+       "model_id": "6c60f5bb1705487882af9db4454aa700",
+       "version_major": 2,
+       "version_minor": 0
+      },
+      "text/plain": [
+       "Canvas(toolbar=Toolbar(toolitems=[('Home', 'Reset original view', 'home', 'home'), ('Back', 'Back to previous …"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "from pysketcher import *\n",
+    "from numpy import exp, linspace\n",
+    "\n",
+    "W = 5    # upstream area\n",
+    "L = 10   # downstread area\n",
+    "H = 4    # height\n",
+    "sigma = 2\n",
+    "alpha = 2\n",
+    "\n",
+    "drawing_tool.set_coordinate_system(xmin=0, xmax=W+L+1,\n",
+    "                                   ymin=-2, ymax=H+1,\n",
+    "                                   axis=True)\n",
+    "drawing_tool.set_linecolor('blue')\n",
+    "\n",
+    "# Create bottom\n",
+    "\n",
+    "def gaussian(x):\n",
+    "    return alpha*exp(-(x-W)**2/(0.5*sigma**2))\n",
+    "\n",
+    "x = linspace(0, W+L, 51)\n",
+    "y = gaussian(x)\n",
+    "wall = Wall(x, y, thickness=-0.3, pattern='|', transparent=True).\\\n",
+    "       set_linecolor('brown')\n",
+    "wall['eraser'].set_linecolor('white')\n",
+    "def velprofile(y):\n",
+    "    return [2*y*(2*H-y)/H**2, 0]\n",
+    "\n",
+    "inlet_profile = VelocityProfile((0,0), H, velprofile, 5)\n",
+    "symmetry_line = Line((0,H), (W+L,H))\n",
+    "symmetry_line.set_linestyle('dashed')\n",
+    "outlet = Line((W+L,0), (W+L,H))\n",
+    "outlet.set_linestyle('dashed')\n",
+    "\n",
+    "fig = Composition({\n",
+    "    'bottom': wall,\n",
+    "    'inlet': inlet_profile,\n",
+    "    'symmetry line': symmetry_line,\n",
+    "    'outlet': outlet,\n",
+    "    })\n",
+    "\n",
+    "fig.draw()  # send all figures to plotting backend\n",
+    "\n",
+    "vx, vy = velprofile(H/2.)\n",
+    "symbols = {\n",
+    "    'alpha': Distance_wText((W,0), (W,alpha), r'$\\alpha$'),\n",
+    "    'W': Distance_wText((0,-0.5), (W,-0.5), r'$W$',\n",
+    "                          text_spacing=-1./30),\n",
+    "    'L': Distance_wText((W,-0.5), (W+L,-0.5), r'$L$',\n",
+    "                          text_spacing=-1./30),\n",
+    "    'v(y)': Text('$v(y)$', (H/2., vx)),\n",
+    "    'dashed line': Line((W-2.5*sigma,0), (W+2.5*sigma,0)).\\\n",
+    "                   set_linestyle('dotted').set_linecolor('black'),\n",
+    "    }\n",
+    "symbols = Composition(symbols)\n",
+    "symbols.draw()\n",
+    "\n",
+    "drawing_tool.display()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": []
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python 3",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.8.5"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}

+ 209 - 0
notebooks/pendulum1.ipynb

@@ -0,0 +1,209 @@
+{
+ "cells": [
+  {
+   "cell_type": "code",
+   "execution_count": 1,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "%matplotlib widget"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 2,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "application/vnd.jupyter.widget-view+json": {
+       "model_id": "e995955f250e4a19b807f8758599660c",
+       "version_major": 2,
+       "version_minor": 0
+      },
+      "text/plain": [
+       "Canvas(toolbar=Toolbar(toolitems=[('Home', 'Reset original view', 'home', 'home'), ('Back', 'Back to previous …"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "from pysketcher import *\n",
+    "\n",
+    "H = 7.\n",
+    "W = 6.\n",
+    "\n",
+    "drawing_tool.set_coordinate_system(xmin=0, xmax=W,\n",
+    "                                   ymin=0, ymax=H,\n",
+    "                                   axis=False)\n",
+    "#drawing_tool.set_grid(True)\n",
+    "drawing_tool.set_linecolor('blue')\n",
+    "\n",
+    "L = 5*H/7          # length\n",
+    "P = (W/6, 0.85*H)  # rotation point\n",
+    "a = 40             # angle\n",
+    "\n",
+    "vertical = Line(P, P-point(0,L))\n",
+    "path = Arc(P, L, -90, a)\n",
+    "angle = Arc_wText(r'$\\theta$', P, L/4, -90, a, text_spacing=1/30.)\n",
+    "\n",
+    "rod = Line(P, P + L*point(sin(radians(a)), -L*cos(radians(a))))\n",
+    "# or shorter (and more reliable)\n",
+    "mass_pt = path.geometric_features()['end']\n",
+    "rod = Line(P, mass_pt)\n",
+    "\n",
+    "mass = Circle(center=mass_pt, radius=L/20.)\n",
+    "mass.set_filled_curves(color='blue')\n",
+    "rod_vec = rod.geometric_features()['end'] - \\\n",
+    "          rod.geometric_features()['start']\n",
+    "unit_rod_vec = unit_vec(rod_vec)\n",
+    "mass_symbol = Text('$m$', mass_pt + L/10*unit_rod_vec)\n",
+    "\n",
+    "length = Distance_wText(P, mass_pt, '$L$')\n",
+    "# Displace length indication\n",
+    "length.translate(L/15*point(cos(radians(a)), sin(radians(a))))\n",
+    "gravity = Gravity(start=P+point(0.8*L,0), length=L/3)\n",
+    "\n",
+    "def set_dashed_thin_blackline(*objects):\n",
+    "    \"\"\"Set linestyle of objects to dashed, black, width=1.\"\"\"\n",
+    "    for obj in objects:\n",
+    "        obj.set_linestyle('dashed')\n",
+    "        obj.set_linecolor('black')\n",
+    "        obj.set_linewidth(1)\n",
+    "\n",
+    "set_dashed_thin_blackline(vertical, path)\n",
+    "\n",
+    "fig = Composition(\n",
+    "    {'body': mass, 'rod': rod,\n",
+    "     'vertical': vertical, 'theta': angle, 'path': path,\n",
+    "     'g': gravity, 'L': length, 'm': mass_symbol})\n",
+    "\n",
+    "fig.draw()\n",
+    "drawing_tool.display()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 3,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdin",
+     "output_type": "stream",
+     "text": [
+      "Press Return to make free body diagram:  \n"
+     ]
+    }
+   ],
+   "source": [
+    "# Draw free body diagram in several different versions\n",
+    "# (note that we build body_diagram, erase and draw,\n",
+    "# add elements to body_diagram, erase and draw, and so on)\n",
+    "\n",
+    "drawing_tool.erase()\n",
+    "\n",
+    "drawing_tool.set_linecolor('black')\n",
+    "\n",
+    "rod_start = rod.geometric_features()['start']  # Point P\n",
+    "vertical2 = Line(rod_start, rod_start + point(0,-L/3))\n",
+    "set_dashed_thin_blackline(vertical2)\n",
+    "set_dashed_thin_blackline(rod)\n",
+    "angle2 = Arc_wText(r'$\\theta$', rod_start, L/6, -90, a,\n",
+    "                   text_spacing=1/30.)\n",
+    "\n",
+    "mg_force  = Force(mass_pt, mass_pt + L/5*point(0,-1),\n",
+    "                  '$mg$', text_pos='end')\n",
+    "rod_force = Force(mass_pt, mass_pt - L/3*unit_vec(rod_vec),\n",
+    "                  '$S$', text_pos='end',\n",
+    "                  text_spacing=(0.03, 0.01))\n",
+    "air_force = Force(mass_pt, mass_pt -\n",
+    "                  L/6*unit_vec((rod_vec[1], -rod_vec[0])),\n",
+    "                  '$\\sim|v|v$', text_pos='end',\n",
+    "                  text_spacing=(0.04,0.005))\n",
+    "\n",
+    "body_diagram = Composition(\n",
+    "    {'mg': mg_force, 'S': rod_force, 'rod': rod,\n",
+    "     'vertical': vertical2, 'theta': angle2,\n",
+    "     'body': mass, 'm': mass_symbol})\n",
+    "\n",
+    "body_diagram.draw()\n",
+    "drawing_tool.display('Free body diagram')"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 4,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "drawing_tool.adjust_coordinate_system(body_diagram.minmax_coordinates(), 90)\n",
+    "drawing_tool.display('Free body diagram')"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 5,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "drawing_tool.erase()\n",
+    "body_diagram['air'] = air_force\n",
+    "body_diagram.draw()\n",
+    "drawing_tool.display('Free body diagram')"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 6,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "drawing_tool.erase()\n",
+    "x0y0 = Text('$(x_0,y_0)$', P + point(-0.4,-0.1))\n",
+    "ir = Force(P, P + L/10*unit_vec(rod_vec),\n",
+    "           r'$\\boldsymbol{i}_r$', text_pos='end',\n",
+    "           text_spacing=(0.015,0))\n",
+    "ith = Force(P, P + L/10*unit_vec((-rod_vec[1], rod_vec[0])),\n",
+    "           r'$\\boldsymbol{i}_{\\theta}$', text_pos='end',\n",
+    "            text_spacing=(0.02,0.005))\n",
+    "\n",
+    "body_diagram['ir'] = ir\n",
+    "body_diagram['ith'] = ith\n",
+    "body_diagram['origin'] = x0y0\n",
+    "\n",
+    "body_diagram.draw()\n",
+    "drawing_tool.display('Free body diagram')"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": []
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python 3",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.8.5"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}

+ 217 - 0
notebooks/test.ipynb

@@ -0,0 +1,217 @@
+{
+ "cells": [
+  {
+   "cell_type": "code",
+   "execution_count": 1,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "%matplotlib widget"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 3,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import matplotlib.pyplot as plt"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 5,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import numpy as np"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 4,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "application/vnd.jupyter.widget-view+json": {
+       "model_id": "5f55bdbff37043bb8e9f05835be4c6e3",
+       "version_major": 2,
+       "version_minor": 0
+      },
+      "text/plain": [
+       "Canvas(toolbar=Toolbar(toolitems=[('Home', 'Reset original view', 'home', 'home'), ('Back', 'Back to previous …"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/plain": [
+       "[<matplotlib.lines.Line2D at 0x27b2d2bfb20>]"
+      ]
+     },
+     "execution_count": 4,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "fig, ax = plt.subplots()\n",
+    "fig.canvas.width = '7in'\n",
+    "fig.canvas.height= '5in'\n",
+    "\n",
+    "# if I hide the header here, I get a libpng error\n",
+    "# fig.canvas.header_visible = False\n",
+    "\n",
+    "ax.plot([1,2,3], [4,5,3])"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 4,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# hiding after rendering works\n",
+    "fig.canvas.header_visible = False"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 5,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "application/vnd.jupyter.widget-view+json": {
+       "model_id": "e0b21d300cff4df3b616fdae74984486",
+       "version_major": 2,
+       "version_minor": 0
+      },
+      "text/plain": [
+       "Canvas(toolbar=Toolbar(toolitems=[('Home', 'Reset original view', 'home', 'home'), ('Back', 'Back to previous …"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/plain": [
+       "[<matplotlib.lines.Line2D at 0x202d6feb730>]"
+      ]
+     },
+     "execution_count": 5,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "# hiding together with calls to toolbar options, work.\n",
+    "fig, ax = plt.subplots()\n",
+    "fig.canvas.width = '7in'\n",
+    "fig.canvas.height= '5in'\n",
+    "\n",
+    "fig.canvas.toolbar_visible = False\n",
+    "fig.canvas.header_visible = False\n",
+    "\n",
+    "ax.plot([1,2,3], [4,5,3])"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 6,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "application/vnd.jupyter.widget-view+json": {
+       "model_id": "b8cab1f8c4e544eca23e0b2fb7246dd6",
+       "version_major": 2,
+       "version_minor": 0
+      },
+      "text/plain": [
+       "AppLayout(children=(FloatSlider(value=1.0, description='Factor:', layout=Layout(grid_area='footer', margin='0p…"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "# When using the `widget` backend from ipympl,\n",
+    "# fig.canvas is a proper Jupyter interactive widget, which can be embedded in\n",
+    "# an ipywidgets layout. See https://ipywidgets.readthedocs.io/en/stable/examples/Layout%20Templates.html\n",
+    "\n",
+    "# One can bound figure attributes to other widget values.\n",
+    "from ipywidgets import AppLayout, FloatSlider\n",
+    "\n",
+    "plt.ioff()\n",
+    "\n",
+    "slider = FloatSlider(\n",
+    "    orientation='horizontal',\n",
+    "    description='Factor:',\n",
+    "    value=1.0,\n",
+    "    min=0.02,\n",
+    "    max=2.0\n",
+    ")\n",
+    "\n",
+    "slider.layout.margin = '0px 30% 0px 30%'\n",
+    "slider.layout.width = '40%'\n",
+    "\n",
+    "fig = plt.figure()\n",
+    "fig.canvas.header_visible = False\n",
+    "fig.canvas.layout.min_height = '400px'\n",
+    "plt.title('Plotting: y=sin({} * x)'.format(slider.value))\n",
+    "\n",
+    "x = np.linspace(0, 20, 500)\n",
+    "\n",
+    "lines = plt.plot(x, np.sin(slider.value * x))\n",
+    "\n",
+    "def update_lines(change):\n",
+    "    plt.title('Plotting: y=sin({} * x)'.format(change.new))\n",
+    "    lines[0].set_data(x, np.sin(change.new * x))\n",
+    "    fig.canvas.draw()\n",
+    "    fig.canvas.flush_events()\n",
+    "\n",
+    "slider.observe(update_lines, names='value')\n",
+    "\n",
+    "AppLayout(\n",
+    "    center=fig.canvas,\n",
+    "    footer=slider,\n",
+    "    pane_heights=[0, 6, 1]\n",
+    ")"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": []
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python 3",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.8.5"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}

+ 299 - 0
notebooks/wheelonInclinedPlane.ipynb

@@ -0,0 +1,299 @@
+{
+ "cells": [
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "# Wheel on Inclined Plane"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 1,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "%matplotlib widget"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 2,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from ipywidgets import FloatSlider"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 3,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import numpy"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 4,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from pysketcher import *\n",
+    "\n",
+    "def inclined_plane():\n",
+    "    theta = 30.\n",
+    "    L = 10.\n",
+    "    a = 1.\n",
+    "    xmin = 0\n",
+    "    ymin = -3\n",
+    "\n",
+    "    drawing_tool.set_coordinate_system(xmin=xmin, xmax=xmin+1.5*L,\n",
+    "                                       ymin=ymin, ymax=ymin+L+1,\n",
+    "                                       #axis=True,\n",
+    "                                       instruction_file='tmp_mpl.py'\n",
+    "                                       )\n",
+    "    #drawing_tool.set_grid(True)\n",
+    "    fontsize = 18\n",
+    "    from math import tan, radians\n",
+    "\n",
+    "    B = point(a+L, 0)\n",
+    "    A = point(a, tan(radians(theta))*L)\n",
+    "\n",
+    "    wall = Wall(x=[A[0], B[0]], y=[A[1], B[1]], thickness=-0.25,\n",
+    "                transparent=False)\n",
+    "\n",
+    "    angle = Arc_wText(r'$\\theta$', center=B, radius=3,\n",
+    "                      start_angle=180-theta, arc_angle=theta,\n",
+    "                      fontsize=fontsize)\n",
+    "    angle.set_linecolor('black')\n",
+    "    angle.set_linewidth(1)\n",
+    "\n",
+    "    ground = Line((B[0]-L/10., 0), (B[0]-L/2.,0))\n",
+    "    ground.set_linecolor('black')\n",
+    "    ground.set_linestyle('dashed')\n",
+    "    ground.set_linewidth(1)\n",
+    "\n",
+    "    r = 1  # radius of wheel\n",
+    "    help_line = Line(A, B)\n",
+    "    x = a + 3*L/10.; y = help_line(x=x)\n",
+    "    contact = point(x, y)\n",
+    "    normal_vec = point(sin(radians(theta)), cos(radians(theta)))\n",
+    "    tangent_vec = point(cos(radians(theta)), -sin(radians(theta)))\n",
+    "    c = contact + r*normal_vec\n",
+    "    outer_wheel = Circle(c, r)\n",
+    "    outer_wheel.set_linecolor('blue')\n",
+    "    outer_wheel.set_filled_curves('blue')\n",
+    "    hole = Circle(c, r/2.)\n",
+    "    hole.set_linecolor('blue')\n",
+    "    hole.set_filled_curves('white')\n",
+    "\n",
+    "    wheel = Composition({'outer': outer_wheel, 'inner': hole})\n",
+    "    wheel.set_shadow(4)\n",
+    "\n",
+    "    drawing_tool.set_linecolor('black')\n",
+    "    N = Force(contact - 2*r*normal_vec, contact, r'$N$', text_pos='start')\n",
+    "    V = Force(c, c + 0.001*tangent_vec, r'$V$', text_pos='end')\n",
+    "    V.set_linecolor('red')\n",
+    "    mg = Gravity(c, 3*r, text='$Mg$')\n",
+    "\n",
+    "    x_const = Line(contact, contact + point(0,4))\n",
+    "    x_const.set_linestyle('dotted')\n",
+    "    x_const.rotate(-theta, contact)\n",
+    "    # or x_const = Line(contact-2*r*normal_vec, contact+4*r*normal_vec).set_linestyle('dotted')\n",
+    "    x_axis = Axis(start=contact+ 3*r*normal_vec, length=4*r,\n",
+    "                  label='$x$', rotation_angle=-theta)\n",
+    "\n",
+    "    body  = Composition({'wheel': wheel, 'N': N, 'mg': mg})\n",
+    "    fixed = Composition({'angle': angle, 'inclined wall': wall,\n",
+    "                         'wheel': wheel, 'V': V, 'ground': ground,\n",
+    "                         'x start': x_const, 'x axis': x_axis})\n",
+    "\n",
+    "    fig = Composition({'body': body, 'fixed elements': fixed})\n",
+    "    fig.draw()\n",
+    "    return (fig,normal_vec,tangent_vec,c)\n",
+    "\n",
+    "def position(t):\n",
+    "    \"\"\"Position of center point of wheel.\"\"\"\n",
+    "    global tangent_vec,c\n",
+    "    return c + 7*t**2*tangent_vec\n",
+    "\n",
+    "def speed(t):\n",
+    "    global tangent_vec\n",
+    "    return 0.14*t*tangent_vec    \n",
+    "\n",
+    "t = 0\n",
+    "\n",
+    "def move(change):\n",
+    "    global fig,t\n",
+    "    dt = change.new - t \n",
+    "    t = change.new\n",
+    "    drawing_tool.erase()\n",
+    "    x = position(t)\n",
+    "    x0 = position(t-dt)\n",
+    "    displacement = x - x0\n",
+    "    fig['fixed elements']['V']['arrow']['line'] = Line(x,x+speed(t))\n",
+    "    fig['body'].translate(displacement)\n",
+    "    fig.draw()\n",
+    "    drawing_tool.display()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 5,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "slider = FloatSlider(\n",
+    "    orientation='horizontal',\n",
+    "    description='Time:',\n",
+    "    value=0.0,\n",
+    "    min=0.0,\n",
+    "    max=1.0,\n",
+    "    step = 1.0 / 30\n",
+    ")\n",
+    "slider.observe(move, 'value')"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 6,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "application/vnd.jupyter.widget-view+json": {
+       "model_id": "4c6c086038e241819081cf84d58d0331",
+       "version_major": 2,
+       "version_minor": 0
+      },
+      "text/plain": [
+       "FloatSlider(value=0.0, description='Time:', max=1.0, step=0.03333333333333333)"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "slider"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 7,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "application/vnd.jupyter.widget-view+json": {
+       "model_id": "1b7605e135de4e258d920e8c0da91831",
+       "version_major": 2,
+       "version_minor": 0
+      },
+      "text/plain": [
+       "Canvas(toolbar=Toolbar(toolitems=[('Home', 'Reset original view', 'home', 'home'), ('Back', 'Back to previous …"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "fig,normal_vec,tangent_vec,c = inclined_plane()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "speed(0)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "position(0)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "fig['fixed elements']['V']['arrow']['line'].x"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "fig['fixed elements']['V']['arrow']['line'].y"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "speed(0.3)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "t"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "position(t)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": []
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python 3",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.8.5"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}

+ 574 - 0
pysketcher/.ipynb_checkpoints/MatplotlibDraw-checkpoint.py

@@ -0,0 +1,574 @@
+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
+
+import os
+import matplotlib
+
+matplotlib.use('TkAgg')
+matplotlib.rcParams['text.latex.preamble'] = '\\usepackage{amsmath}'
+
+import matplotlib.pyplot as mpl
+import matplotlib.transforms as transforms
+import numpy as np
+
+class MatplotlibDraw(object):
+    """
+    Simple interface for plotting. This interface makes use of
+    Matplotlib for plotting.
+
+    Some attributes that must be controlled directly (no set_* method
+    since these attributes are changed quite seldom).
+
+    ========================== ============================================
+    Attribute                  Description
+    ========================== ============================================
+    allow_screen_graphics      False means that no plot is shown on
+                               the screen. (Does not work yet.)
+    arrow_head_width           Size of arrow head.
+    ========================== ============================================
+    """
+
+    line_colors = {'red': 'r', 'green': 'g', 'blue': 'b', 'cyan': 'c',
+                   'magenta': 'm', 'purple': 'p',
+                   'yellow': 'y', 'black': 'k', 'white': 'w',
+                   'brown': 'brown', '': ''}
+    def __init__(self):
+        self.instruction_file = None
+        self.allow_screen_graphics = True  # does not work yet
+
+    def __del__(self):
+        if self.instruction_file:
+            self.instruction_file.write('\nmpl.draw()\nraw_input()\n')
+            self.instruction_file.close()
+
+    def ok(self):
+        """
+        Return True if set_coordinate_system is called and
+        objects can be drawn.
+        """
+
+    def adjust_coordinate_system(self, minmax, occupation_percent=80):
+        """
+        Given a dict of xmin, xmax, ymin, ymax values, and a desired
+        filling of the plotting area of `occupation_percent` percent,
+        set new axis limits.
+        """
+        x_range = minmax['xmax'] - minmax['xmin']
+        y_range = minmax['ymax'] - minmax['ymin']
+        new_x_range = x_range*100./occupation_percent
+        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.)
+
+    def set_coordinate_system(self, xmin, xmax, ymin, ymax, axis=False,
+                              instruction_file=None, new_figure=True,
+                              xkcd=False):
+        """
+        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
+        for the plotting program are stored (useful for debugging
+        a figure or tailoring plots).
+        """
+
+        # Close file for previous figure and start new one
+        # if not the figure file is the same
+        if self.instruction_file is not None:
+            if instruction_file == self.instruction_file.name:
+                pass  # continue with same file
+            else:
+                self.instruction_file.close()  # make new py file for commands
+
+        self.mpl = mpl
+        if xkcd:
+            self.mpl.xkcd()
+        else:
+            # Allow \boldsymbol{} etc in title, labels, etc
+            matplotlib.rc('text', usetex=True)
+
+        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
+
+        # 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
+
+        if isinstance(instruction_file, str):
+            self.instruction_file = open(instruction_file, 'w')
+        else:
+            self.instruction_file = None
+
+        self.mpl.ion()  # important for interactive drawing and animation
+        if self.instruction_file:
+            self.instruction_file.write("""\
+import matplotlib
+matplotlib.use('TkAgg')
+# Allow \boldsymbol{} etc in title, labels, etc
+matplotlib.rc('text', usetex=True)
+matplotlib.rcParams['text.latex.preamble'] = '\\usepackage{amsmath}'
+import matplotlib.pyplot as mpl
+import matplotlib.transforms as transforms
+
+mpl.ion()  # for interactive drawing
+""")
+        # Default properties
+        self.set_linecolor('red')
+        self.set_linewidth(2)
+        self.set_linestyle('solid')
+        self.set_filled_curves()  # no filling
+        self.set_fontsize(14)
+        self.arrow_head_width = 0.2*self.xrange/16
+
+        self._make_axes(new_figure=new_figure)
+
+        manager = self.mpl.get_current_fig_manager()
+        manager.window.wm_geometry(geometry)
+
+    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:
+            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 inside(self, pt, exception=False):
+        """Is point pt inside the defined plotting area?"""
+        area = '[%s,%s]x[%s,%s]' % \
+               (self.xmin, self.xmax, self.ymin, self.ymax)
+        tol = 1E-14
+        pt_inside = True
+        if self.xmin - tol <= pt[0] <= self.xmax + tol:
+            pass
+        else:
+            pt_inside = False
+        if self.ymin - tol <= pt[1] <= self.ymax + tol:
+            pass
+        else:
+            pt_inside = False
+        if pt_inside:
+            return pt_inside, 'point=%s is inside plotting area %s' % \
+                   (pt, area)
+        else:
+            msg = 'point=%s is outside plotting area %s' % (pt, area)
+            if exception:
+                raise ValueError(msg)
+            return pt_inside, msg
+
+    def set_linecolor(self, color):
+        """
+        Change the color of lines. Available colors are
+        'black', 'white', 'red', 'blue', 'green', 'yellow',
+        'magenta', 'cyan'.
+        """
+        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='', pattern=''):
+        """
+        Fill area inside curves with specified color and/or pattern.
+        A common pattern is '/' (45 degree lines). Other patterns
+        include '-', '+', 'x', '\\', '*', 'o', 'O', '.'.
+        """
+        if color is False:
+            self.fillcolor = ''
+            self.fillpattern = ''
+        else:
+            self.fillcolor = color if len(color) == 1 else \
+                         MatplotlibDraw.line_colors[color]
+            self.fillpattern = pattern
+
+    def set_fontsize(self, fontsize=18):
+        """
+        Method for setting a common fontsize for text, unless
+        individually specified when calling ``text``.
+        """
+        self.fontsize = fontsize
+
+    def set_grid(self, on=False):
+        self.mpl.grid(on)
+        if self.instruction_file:
+            self.instruction_file.write("\nmpl.grid(%s)\n" % str(on))
+
+    def erase(self):
+        """Erase the current figure."""
+        self.mpl.delaxes()
+        if self.instruction_file:
+            self.instruction_file.write("\nmpl.delaxes()  # erase\n")
+
+        self._make_axes(new_figure=False)
+
+    def plot_curve(self, x, y,
+                   linestyle=None, linewidth=None,
+                   linecolor=None, arrow=None,
+                   fillcolor=None, fillpattern=None,
+                   shadow=0, name=None):
+        """Define a curve with coordinates x and y (arrays)."""
+        #if not self.allow_screen_graphics:
+        #    mpl.ioff()
+        #else:
+        #    mpl.ion()
+
+        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 fillpattern is None:
+            fillpattern = self.fillpattern
+        if shadow == 1:
+            shadow = 3   # smallest displacement that is visible
+
+        # We can plot fillcolor/fillpattern, arrow or line
+
+        if self.instruction_file:
+            import pprint
+            if name is not None:
+                self.instruction_file.write('\n# %s\n' % name)
+            if not arrow:
+                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 fillpattern:
+            if fillpattern != '':
+                fillcolor = 'white'
+            #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:
+                self.instruction_file.write("[line] = ax.fill(x, y, '%s', edgecolor='%s', linewidth=%d, hatch='%s')\n" % (fillcolor, linecolor, linewidth, fillpattern))
+
+        else:
+            # Plain line
+            [line] = self.ax.plot(x, y, linecolor, linewidth=linewidth,
+                                  linestyle=linestyle)
+            if self.instruction_file:
+                self.instruction_file.write("[line] = ax.plot(x, y, '%s', linewidth=%d, linestyle='%s')\n" % (linecolor, linewidth, linestyle))
+
+        if arrow:
+            # Note that a Matplotlib arrow is a line with the arrow tip
+            if not arrow in ('->', '<-', '<->'):
+                raise ValueError("arrow argument must be '->', '<-', or '<->', not %s" % repr(arrow))
+
+            # Add arrow to first and/or last segment
+            start = arrow == '<-' or arrow == '<->'
+            end = arrow == '->' or arrow == '<->'
+            if start:
+                x_s, y_s = x[1], y[1]
+                dx_s, dy_s = x[0]-x[1], y[0]-y[1]
+                self._plot_arrow(x_s, y_s, dx_s, dy_s, '->',
+                                 linestyle, linewidth, linecolor)
+            if end:
+                x_e, y_e = x[-2], y[-2]
+                dx_e, dy_e = x[-1]-x[-2], y[-1]-y[-2]
+                self._plot_arrow(x_e, y_e, dx_e, dy_e, '->',
+                                 linestyle, linewidth, linecolor)
+        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.
+            offset = transforms.ScaledTranslation(
+                dx, dy, self.fig.dpi_scale_trans)
+            shadow_transform = self.ax.transData + offset
+            # now plot the same data with our offset transform;
+            # use the zorder to make sure we are below the line
+            if linewidth is None:
+                linewidth = 3
+            self.ax.plot(x, y, linewidth=linewidth, color='gray',
+                         transform=shadow_transform,
+                         zorder=0.5*line.get_zorder())
+
+
+            if self.instruction_file:
+                self.instruction_file.write("""
+# Shadow effect for last ax.plot
+dx, dy = 3/72., -3/72.
+offset = matplotlib.transforms.ScaledTranslation(dx, dy, fig.dpi_scale_trans)
+shadow_transform = ax.transData + offset
+self.ax.plot(x, y, linewidth=%d, color='gray',
+             transform=shadow_transform,
+             zorder=0.5*line.get_zorder())
+""" % linewidth)
+
+
+
+    def display(self, title=None, show=True):
+        """Display the figure."""
+        if title is not None:
+            self.mpl.title(title)
+            if self.instruction_file:
+                self.instruction_file.write('mpl.title("%s")\n' % title)
+
+        if show:
+            self.mpl.draw()
+
+        if self.instruction_file:
+            self.instruction_file.write('mpl.draw()\n')
+
+    def savefig(self, filename, dpi=None, crop=True):
+        """Save figure in file. Set dpi=300 for really high resolution."""
+        # If filename is without extension, generate all important formats
+        ext = os.path.splitext(filename)[1]
+        if not ext:
+            # Create both PNG and PDF file
+            self.mpl.savefig(filename + '.png', dpi=dpi)
+            self.mpl.savefig(filename + '.pdf')
+            if crop:
+                # Crop the PNG file
+                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')
+                failure = os.system('pdfcrop %s.pdf %s.pdf' %
+                                    (filename, filename))
+                if failure:
+                    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'
+                                            % (filename, dpi))
+                self.instruction_file.write('mpl.savefig("%s.pdf")\n'
+                                            % filename)
+        else:
+            self.mpl.savefig(filename, dpi=dpi)
+            if ext == '.png':
+                if crop:
+                    failure = os.system('convert -trim %s %s' % (filename, filename))
+                    if failure:
+                        print('convert from ImageMagick is not installed - needed for cropping PNG files')
+            elif ext == '.pdf':
+                if crop:
+                    failure = os.system('pdfcrop %s %s' % (filename, filename))
+                    if failure:
+                        print('pdfcrop is not installed - needed for cropping PDF files')
+
+            if self.instruction_file:
+                self.instruction_file.write('mpl.savefig("%s", dpi=%s)\n'
+                                            % (filename, dpi))
+
+
+    def text(self, text, position, alignment='center', fontsize=0,
+             arrow_tip=None, bgcolor=None, fgcolor=None, fontfamily=None):
+        """
+        Write `text` string at a position (centered, left, right - according
+        to the `alignment` string). `position` is a point in the coordinate
+        system.
+        If ``arrow+tip != None``, an arrow is drawn 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.
+        fontsize=0 indicates use of the default font as set by
+        ``set_fontsize``.
+        """
+        if fontsize == 0:
+            if hasattr(self, 'fontsize'):
+                fontsize = self.fontsize
+            else:
+                raise AttributeError(
+                    'No self.fontsize attribute to be used when text(...)\n'
+                    'is called with fontsize=0. Call set_fontsize method.')
+
+        kwargs = {}
+        if fontfamily is not None:
+            kwargs['family'] = fontfamily
+        if bgcolor is not None:
+            kwargs['backgroundcolor'] = bgcolor
+        if fgcolor is not None:
+            kwargs['color'] = fgcolor
+
+        x, y = position
+        if arrow_tip is None:
+            self.ax.text(x, y, text, horizontalalignment=alignment,
+                         fontsize=fontsize, **kwargs)
+            if self.instruction_file:
+                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:
+                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.tolist() if isinstance(pt, np.ndarray) else 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 _plot_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,
+                           linestyle=linestyle,
+                           linewidth=linewidth,
+                           head_width=self.arrow_head_width,
+                           #head_width=0.1,
+                           #width=1,  # width of arrow body in coordinate scale
+                           length_includes_head=True,
+                           shape='full')
+            if self.instruction_file:
+                self.instruction_file.write("""\
+mpl.arrow(x=%g, y=%g, dx=%g, dy=%g,
+          facecolor='%s', edgecolor='%s',
+          linestyle='%s',
+          linewidth=%g, head_width=0.1,
+          length_includes_head=True,
+          shape='full')
+""" % (x, y, dx, dy, linecolor, linecolor, linestyle, 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:
+                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:
+            self.instruction_file.write("""
+ax.annotate('', xy=(%s,%s), xytext=(%s,%s),
+                         arrowprops=dict(arrowstyle=%s,
+                                         facecolor='black',
+                                         linewidth=1,
+                                         shrinkA=0,
+                                         shrinkB=0))
+""" % (x+dx, y+dy, x, y, style))
+
+
+
+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.plot_curve(x, y)
+    d.set_filled_curves(False)
+    d.plot_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.plot_curve(x, y, arrow='end')
+    d.display()
+    input()
+
+if __name__ == '__main__':
+    _test()

+ 5 - 4
pysketcher/MatplotlibDraw.py

@@ -12,7 +12,7 @@ from builtins import object
 import os
 import matplotlib
 
-matplotlib.use('TkAgg')
+matplotlib.use('module://ipympl.backend_nbagg')
 matplotlib.rcParams['text.latex.preamble'] = '\\usepackage{amsmath}'
 
 import matplotlib.pyplot as mpl
@@ -120,7 +120,7 @@ class MatplotlibDraw(object):
         if self.instruction_file:
             self.instruction_file.write("""\
 import matplotlib
-matplotlib.use('TkAgg')
+matplotlib.use('module://ipympl.backend_nbagg')
 # Allow \boldsymbol{} etc in title, labels, etc
 matplotlib.rc('text', usetex=True)
 matplotlib.rcParams['text.latex.preamble'] = '\\usepackage{amsmath}'
@@ -140,7 +140,7 @@ mpl.ion()  # for interactive drawing
         self._make_axes(new_figure=new_figure)
 
         manager = self.mpl.get_current_fig_manager()
-        manager.window.wm_geometry(geometry)
+        #manager.window.wm_geometry(geometry)
 
     def _make_axes(self, new_figure=False):
         if new_figure:
@@ -488,7 +488,8 @@ ax.annotate('%s', xy=%s, xycoords='data',
             linewidth = self.linewidth
 
         if style == '->' or style == '<->':
-            self.mpl.arrow(x, y, dx, dy, hold=True,
+            self.mpl.arrow(x, y, dx, dy, 
+                           #hold=True,
                            facecolor=linecolor,
                            edgecolor=linecolor,
                            linestyle=linestyle,