# #ifdef PRIMER_BOOK idx{`pysketcher`} Implementing a drawing program provides a very good example on the usefulness of object-oriented programming. In the following we shall develop the simpler parts of a relatively small and compact drawing program for making sketches of the type shown in Figure ref{sketcher:fig:inclinedplane}. This is a typical *principal sketch* of a physics problem, here involving a rolling wheel on an inclined plane. The sketch # #else 2DO: * two wheels of different radii on inclined plane coupled to correct solution * "Pygame backend": "http://inventwithpython.com/chapter17.html" ======= A First Glimpse of Pysketcher ======= Formulation of physical problems makes heavy use of *principal sketches* such as the one in Figure ref{sketcher:fig:inclinedplane}. This particular sketch illustrates the classical mechanics problem of a rolling wheel on an inclined plane. The figure # #endif is made up many individual elements: a rectangle filled with a pattern (the inclined plane), a hollow circle with color (the wheel), arrows with labels (the $N$ and $Mg$ forces, and the $x$ axis), an angle with symbol $\theta$, and a dashed line indicating the starting location of the wheel. Drawing software and plotting programs can produce such figures quite easily in principle, but the amount of details the user needs to control with the mouse can be substantial. Software more tailored to producing sketches of this type would work with more convenient abstractions, such as circle, wall, angle, force arrow, axis, and so forth. And as soon we start *programming* to construct the figure we get a range of other powerful tools at disposal. For example, we can easily translate and rotate parts of the figure and make an animation that illustrates the physics of the problem. Programming as a superior alternative to interactive drawing is the mantra of this section. FIGURE: [fig-tut/wheel_on_inclined_plane, width=400 frac=0.5] Sketch of a physics problem. label{sketcher:fig:inclinedplane} # #ifdef PRIMER_BOOK Classes are very suitable for implementing the various components that build up a sketch. In particular, we shall demonstrate that as soon as some classes are established, more are easily added. Enhanced functionality for all the classes is also easy to implement in common, generic code that can immediately be shared by all present and future classes. The fundamental data structure involved in this case study is a hierarchical tree, and much of the material on implementation issues targets how to traverse tree structures with recursive function calls. This topic is of key relevance in a wide range of other applications as well. ===== Using the Object Collection ===== We start by demonstrating a convenient user interface for making sketches of the type in Figure ref{sketcher:fig:inclinedplane}. However, it is more appropriate to start with a significantly simpler example as depicted in Figure ref{sketcher:fig:vehicle0}. This toy sketch consists of several elements: two circles, two rectangles, and a ``ground'' element. # #else ===== Basic Construction of Sketches ===== Before attacking real-life sketches as in Figure ref{sketcher:fig:inclinedplane} we focus on the significantly simpler drawing shown in Figure ref{sketcher:fig:vehicle0}. This toy sketch consists of several elements: two circles, two rectangles, and a ``ground'' element. # #endif FIGURE: [fig-tut/vehicle0_dim, width=600] Sketch of a simple figure. label{sketcher:fig:vehicle0} === Basic Drawing === A typical program creating these five elements is shown next. After importing the `pysketcher` package, the first task is always to define a coordinate system: !bc pycod from pysketcher import * drawing_tool.set_coordinate_system( xmin=0, xmax=10, ymin=-1, ymax=8) !ec Instead of working with lengths expressed by specific numbers it is highly recommended to use variables to parameterize lengths as this makes it easier to change dimensions later. Here we introduce some key lengths for the radius of the wheels, distance between the wheels, etc.: !bc pycod R = 1 # radius of wheel L = 4 # distance between wheels H = 2 # height of vehicle body w_1 = 5 # position of front wheel drawing_tool.set_coordinate_system(xmin=0, xmax=w_1 + 2*L + 3*R, ymin=-1, ymax=2*R + 3*H) !ec With the drawing area in place we can make the first `Circle` object in an intuitive fashion: !bc pycod wheel1 = Circle(center=(w_1, R), radius=R) !ec to change dimensions later. To translate the geometric information about the `wheel1` object to instructions for the plotting engine (in this case Matplotlib), one calls the `wheel1.draw()`. To display all drawn objects, one issues `drawing_tool.display()`. The typical steps are hence: !bc pycod wheel1 = Circle(center=(w_1, R), radius=R) wheel1.draw() # Define other objects and call their draw() methods drawing_tool.display() drawing_tool.savefig('tmp.png') # store picture !ec The next wheel can be made by taking a copy of `wheel1` and translating the object to the right according to a displacement vector $(L,0)$: !bc pycod wheel2 = wheel1.copy() wheel2.translate((L,0)) !ec The two rectangles are also made in an intuitive way: !bc pycod under = Rectangle(lower_left_corner=(w_1-2*R, 2*R), width=2*R + L + 2*R, height=H) over = Rectangle(lower_left_corner=(w_1, 2*R + H), width=2.5*R, height=1.25*H) !ec === Groups of Objects === Instead of calling the `draw` method of every object, we can group objects and call `draw`, or perform other operations, for the whole group. For example, we may collect the two wheels in a `wheels` group and the `over` and `under` rectangles in a `body` group. The whole vehicle is a composition of its `wheels` and `body` groups. The code goes like !bc pycod wheels = Composition({'wheel1': wheel1, 'wheel2': wheel2}) body = Composition({'under': under, 'over': over}) vehicle = Composition({'wheels': wheels, 'body': body}) !ec The ground is illustrated by an object of type `Wall`, mostly used to indicate walls in sketches of mechanical systems. A `Wall` takes the `x` and `y` coordinates of some curve, and a `thickness` parameter, and creates a thick curve filled with a simple pattern. In this case the curve is just a flat line so the construction is made of two points on the ground line ($(w_1-L,0)$ and $(w_1+3L,0)$): !bc pycod ground = Wall(x=[w_1 - L, w_1 + 3*L], y=[0, 0], thickness=-0.3*R) !ec The negative thickness makes the pattern-filled rectangle appear below the defined line, otherwise it appears above. We may now collect all the objects in a ``top'' object that contains the whole figure: !bc pycod fig = Composition({'vehicle': vehicle, 'ground': ground}) fig.draw() # send all figures to plotting backend drawing_tool.display() drawing_tool.savefig('tmp.png') !ec The `fig.draw()` call will visit all subgroups, their subgroups, and so forth in the hierarchical tree structure of figure elements, and call `draw` for every object. === Changing Line Styles and Colors === Controlling the line style, line color, and line width is fundamental when designing figures. The `pysketcher` package allows the user to control such properties in single objects, but also set global properties that are used if the object has no particular specification of the properties. Setting the global properties are done like !bc pycod drawing_tool.set_linestyle('dashed') drawing_tool.set_linecolor('black') drawing_tool.set_linewidth(4) !ec At the object level the properties are specified in a similar way: !bc pycod wheels.set_linestyle('solid') wheels.set_linecolor('red') !ec and so on. Geometric figures can be specified as *filled*, either with a color or with a special visual pattern: !bc pycod # Set filling of all curves drawing_tool.set_filled_curves(color='blue', pattern='/') # Turn off filling of all curves drawing_tool.set_filled_curves(False) # Fill the wheel with red color wheel1.set_filled_curves('red') !ec # http://packages.python.org/ete2/ for visualizing tree structures! === The Figure Composition as an Object Hierarchy === The composition of objects making up the figure is hierarchical, similar to a family, where each object has a parent and a number of children. Do a `print fig` to display the relations: !bc dat ground wall vehicle body over rectangle under rectangle wheels wheel1 arc wheel2 arc !ec The indentation reflects how deep down in the hierarchy (family) we are. This output is to be interpreted as follows: * `fig` contains two objects, `ground` and `vehicle` * `ground` contains an object `wall` * `vehicle` contains two objects, `body` and `wheels` * `body` contains two objects, `over` and `under` * `wheels` contains two objects, `wheel1` and `wheel2` In this listing there are also objects not defined by the programmer: `rectangle` and `arc`. These are of type `Curve` and automatically generated by the classes `Rectangle` and `Circle`. More detailed information can be printed by !bc pycod print fig.show_hierarchy('std') !ec yielding the output !bc dat ground (Wall): wall (Curve): 4 coords fillcolor='white' fillpattern='/' vehicle (Composition): body (Composition): over (Rectangle): rectangle (Curve): 5 coords under (Rectangle): rectangle (Curve): 5 coords wheels (Composition): wheel1 (Circle): arc (Curve): 181 coords wheel2 (Circle): arc (Curve): 181 coords !ec Here we can see the class type for each figure object, how many coordinates that are involved in basic figures (`Curve` objects), and special settings of the basic figure (fillcolor, line types, etc.). For example, `wheel2` is a `Circle` object consisting of an `arc`, which is a `Curve` object consisting of 181 coordinates (the points needed to draw a smooth circle). The `Curve` objects are the only objects that really holds specific coordinates to be drawn. The other object types are just compositions used to group parts of the complete figure. One can also get a graphical overview of the hierarchy of figure objects that build up a particular figure `fig`. Just call `fig.graphviz_dot('fig')` to produce a file `fig.dot` in the *dot format*. This file contains relations between parent and child objects in the figure and can be turned into an image, as in Figure ref{sketcher:fig:vehicle0:hier1}, by running the `dot` program: !bc sys Terminal> dot -Tpng -o fig.png fig.dot !ec FIGURE: [fig-tut/vehicle0_hier1, width=500 frac=0.8] Hierarchical relation between figure objects. label{sketcher:fig:vehicle0:hier1} The call `fig.graphviz_dot('fig', classname=True)` makes a `fig.dot` file where the class type of each object is also visible, see Figure ref{sketcher:fig:vehicle0:hier2}. The ability to write out the object hierarchy or view it graphically can be of great help when working with complex figures that involve layers of subfigures. FIGURE: [fig-tut/Vehicle0_hier2, width=500 frac=0.8] Hierarchical relation between figure objects, including their class names. label{sketcher:fig:vehicle0:hier2} Any of the objects can in the program be reached through their names, e.g., !bc pycod fig['vehicle'] fig['vehicle']['wheels'] fig['vehicle']['wheels']['wheel2'] fig['vehicle']['wheels']['wheel2']['arc'] fig['vehicle']['wheels']['wheel2']['arc'].x # x coords fig['vehicle']['wheels']['wheel2']['arc'].y # y coords fig['vehicle']['wheels']['wheel2']['arc'].linestyle fig['vehicle']['wheels']['wheel2']['arc'].linetype !ec Grabbing a part of the figure this way is handy for changing properties of that part, for example, colors, line styles (see Figure ref{sketcher:fig:vehicle0:v2}): !bc pycod fig['vehicle']['wheels'].set_filled_curves('blue') fig['vehicle']['wheels'].set_linewidth(6) fig['vehicle']['wheels'].set_linecolor('black') fig['vehicle']['body']['under'].set_filled_curves('red') fig['vehicle']['body']['over'].set_filled_curves(pattern='/') fig['vehicle']['body']['over'].set_linewidth(14) fig['vehicle']['body']['over']['rectangle'].linewidth = 4 !ec The last line accesses the `Curve` object directly, while the line above, accesses the `Rectangle` object, which will then set the linewidth of its `Curve` object, and other objects if it had any. The result of the actions above is shown in Figure ref{sketcher:fig:vehicle0:v2}. FIGURE: [fig-tut/vehicle0, width=700] Left: Basic line-based drawing. Right: Thicker lines and filled parts. label{sketcher:fig:vehicle0:v2} We can also change position of parts of the figure and thereby make animations, as shown next. === Animation: Translating the Vehicle === Can we make our little vehicle roll? A first attempt will be to fake rolling by just displacing all parts of the vehicle. The relevant parts constitute the `fig['vehicle']` object. This part of the figure can be translated, rotated, and scaled. A translation along the ground means a translation in $x$ direction, say a length $L$ to the right: !bc pycod fig['vehicle'].translate((L,0)) !ec You need to erase, draw, and display to see the movement: !bc pycod drawing_tool.erase() fig.draw() drawing_tool.display() !ec Without erasing, the old drawing of the vehicle will remain in the figure so you get two vehicles. Without `fig.draw()` the new coordinates of the vehicle will not be communicated to the drawing tool, and without calling display the updated drawing will not be visible. A figure that moves in time is conveniently realized by the function `animate`: !bc pycod animate(fig, tp, action) !ec Here, `fig` is the entire figure, `tp` is an array of time points, and `action` is a user-specified function that changes `fig` at a specific time point. Typically, `action` will move parts of `fig`. In the present case we can define the movement through a velocity function `v(t)` and displace the figure `v(t)*dt` for small time intervals `dt`. A possible velocity function is !bc pycod def v(t): return -8*R*t*(1 - t/(2*R)) !ec Our action function for horizontal displacements `v(t)*dt` becomes !bc pycod def move(t, fig): x_displacement = dt*v(t) fig['vehicle'].translate((x_displacement, 0)) !ec Since our velocity is negative for $t\in [0,2R]$ the displacement is to the left. The `animate` function will for each time point `t` in `tp` erase the drawing, call `action(t, fig)`, and show the new figure by `fig.draw()` and `drawing_tool.display()`. Here we choose a resolution of the animation corresponding to 25 time points in the time interval $[0,2R]$: !bc pycod import numpy tp = numpy.linspace(0, 2*R, 25) dt = tp[1] - tp[0] # time step animate(fig, tp, move, pause_per_frame=0.2) !ec The `pause_per_frame` adds a pause, here 0.2 seconds, between each frame in the animation. We can also ask `animate` to store each frame in a file: !bc pycod files = animate(fig, tp, move_vehicle, moviefiles=True, pause_per_frame=0.2) !ec The `files` variable, here `'tmp_frame_%04d.png'`, is the printf-specification used to generate the individual plot files. We can use this specification to make a video file via `ffmpeg` (or `avconv` on Debian-based Linux systems such as Ubuntu). Videos in the Flash and WebM formats can be created by !bc sys Terminal> ffmpeg -r 12 -i tmp_frame_%04d.png -vcodec flv mov.flv Terminal> ffmpeg -r 12 -i tmp_frame_%04d.png -vcodec libvpx mov.webm !ec An animated GIF movie can also be made using the `convert` program from the ImageMagick software suite: !bc sys Terminal> convert -delay 20 tmp_frame*.png mov.gif Terminal> animate mov.gif # play movie !ec The delay between frames, in units of 1/100 s, governs the speed of the movie. To play the animated GIF file in a web page, simply insert `` in the HTML code. The individual PNG frames can be directly played in a web browser by running !bc sys Terminal> scitools movie output_file=mov.html fps=5 tmp_frame* !ec or calling !bc pycod from scitools.std import movie movie(files, encoder='html', output_file='mov.html') !ec in Python. Load the resulting file `mov.html` into a web browser to play the movie. Try to run "`vehicle0.py`": "${src_path_pysketcher}/vehicle0.py" and then load `mov.html` into a browser, or play one of the `mov.*` video files. Alternatively, you can view a ready-made "movie": "${src_path_tut}/mov-tut/vehicle0.html". === Animation: Rolling the Wheels === label{sketcher:vehicle1:anim} It is time to show rolling wheels. To this end, we add spokes to the wheels, formed by two crossing lines, see Figure ref{sketcher:fig:vehicle1}. The construction of the wheels will now involve a circle and two lines: !bc pycod wheel1 = Composition({ 'wheel': Circle(center=(w_1, R), radius=R), 'cross': Composition({'cross1': Line((w_1,0), (w_1,2*R)), 'cross2': Line((w_1-R,R), (w_1+R,R))})}) wheel2 = wheel1.copy() wheel2.translate((L,0)) !ec Observe that `wheel1.copy()` copies all the objects that make up the first wheel, and `wheel2.translate` translates all the copied objects. FIGURE: [fig-tut/vehicle1, width=400 frac=0.8] Wheels with spokes to illustrate rolling. label{sketcher:fig:vehicle1} The `move` function now needs to displace all the objects in the entire vehicle and also rotate the `cross1` and `cross2` objects in both wheels. The rotation angle follows from the fact that the arc length of a rolling wheel equals the displacement of the center of the wheel, leading to a rotation angle !bc pycod angle = - x_displacement/R !ec With `w_1` tracking the $x$ coordinate of the center of the front wheel, we can rotate that wheel by !bc pycod w1 = fig['vehicle']['wheels']['wheel1'] from math import degrees w1.rotate(degrees(angle), center=(w_1, R)) !ec The `rotate` function takes two parameters: the rotation angle (in degrees) and the center point of the rotation, which is the center of the wheel in this case. The other wheel is rotated by !bc pycod w2 = fig['vehicle']['wheels']['wheel2'] w2.rotate(degrees(angle), center=(w_1 + L, R)) !ec That is, the angle is the same, but the rotation point is different. The update of the center point is done by `w_1 += x_displacement`. The complete `move` function with translation of the entire vehicle and rotation of the wheels then becomes !bc pycod w_1 = w_1 + L # start position def move(t, fig): x_displacement = dt*v(t) fig['vehicle'].translate((x_displacement, 0)) # Rotate wheels global w_1 w_1 += x_displacement # R*angle = -x_displacement angle = - x_displacement/R w1 = fig['vehicle']['wheels']['wheel1'] w1.rotate(degrees(angle), center=(w_1, R)) w2 = fig['vehicle']['wheels']['wheel2'] w2.rotate(degrees(angle), center=(w_1 + L, R)) !ec The complete example is found in the file "`vehicle1.py`": "${src_path_pysketcher}/vehicle1.py". You may run this file or watch a "ready-made movie": "${src_path_tut}/mov-tut/vehicle1.html". The advantages with making figures this way, through programming rather than using interactive drawing programs, are numerous. For example, the objects are parameterized by variables so that various dimensions can easily be changed. Subparts of the figure, possible involving a lot of figure objects, can change color, linetype, filling or other properties through a *single* function call. Subparts of the figure can be rotated, translated, or scaled. Subparts of the figure can also be copied and moved to other parts of the drawing area. However, the single most important feature is probably the ability to make animations governed by mathematical formulas or data coming from physics simulations of the problem, as shown in the example above.