| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896 |
- <!--
- Automatically generated HTML file from DocOnce source
- (https://github.com/hplgit/doconce/)
- -->
- <html>
- <head>
- <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
- <meta name="generator" content="DocOnce: https://github.com/hplgit/doconce/" />
- <meta name="description" content="Using Pysketcher to Create Principal Sketches of Physics Problems">
- <meta name="keywords" content="tree data structure,recursive function calls">
- <title>Using Pysketcher to Create Principal Sketches of Physics Problems</title>
- <style type="text/css">
- /* blueish style */
- /* Color definitions: http://www.december.com/html/spec/color0.html
- CSS examples: http://www.w3schools.com/css/css_examples.asp */
- body {
- margin-top: 1.0em;
- background-color: #ffffff;
- font-family: Helvetica, Arial, FreeSans, san-serif;
- color: #000000;
- }
- h1 { font-size: 1.8em; color: #1e36ce; }
- h2 { font-size: 1.6em; color: #1e36ce; }
- h3 { font-size: 1.4em; color: #1e36ce; }
- a { color: #1e36ce; text-decoration:none; }
- tt { font-family: "Courier New", Courier; }
- /* pre style removed because it will interfer with pygments */
- p { text-indent: 0px; }
- hr { border: 0; width: 80%; border-bottom: 1px solid #aaa}
- p.caption { width: 80%; font-style: normal; text-align: left; }
- hr.figure { border: 0; width: 80%; border-bottom: 1px solid #aaa}
- div { text-align: justify; text-justify: inter-word; }
- </style>
- </head>
- <!-- tocinfo
- {'highest level': 1,
- 'sections': [(' A First Glimpse of Pysketcher ', 1, None, '___sec0'),
- (' Basic Construction of Sketches ', 2, None, '___sec1'),
- (' Basic Drawing ', 3, None, '___sec2'),
- (' Groups of Objects ', 3, None, '___sec3'),
- (' Changing Line Styles and Colors ', 3, None, '___sec4'),
- (' The Figure Composition as an Object Hierarchy ',
- 3,
- None,
- '___sec5'),
- (' Animation: Translating the Vehicle ', 3, None, '___sec6'),
- (' Animation: Rolling the Wheels ',
- 3,
- 'sketcher:vehicle1:anim',
- 'sketcher:vehicle1:anim'),
- (' Basic Shapes ', 1, None, '___sec8'),
- (' Axis ', 2, None, '___sec9'),
- (' Distance with Text ', 2, None, '___sec10'),
- (' Rectangle ', 2, None, '___sec11'),
- (' Triangle ', 2, None, '___sec12'),
- (' Arc ', 2, None, '___sec13'),
- (' Spring ', 2, None, '___sec14'),
- (' Dashpot ', 2, None, '___sec15'),
- (' Wavy ', 2, None, '___sec16'),
- (' Stochastic curves ', 2, None, '___sec17'),
- (' Inner Workings of the Pysketcher Tool ',
- 1,
- None,
- '___sec18'),
- (' Example of Classes for Geometric Objects ',
- 2,
- None,
- '___sec19'),
- (' Simple Geometric Objects ', 3, None, '___sec20'),
- (' Class Curve ', 3, None, '___sec21'),
- (' Compound Geometric Objects ', 3, None, '___sec22'),
- (' Adding Functionality via Recursion ', 2, None, '___sec23'),
- (' Basic Principles of Recursion ', 3, None, '___sec24'),
- (' Explaining Recursion ', 3, None, '___sec25'),
- (' Scaling, Translating, and Rotating a Figure ',
- 2,
- 'sketcher:scaling',
- 'sketcher:scaling'),
- (' Scaling ', 3, None, '___sec27'),
- (' Translation ', 3, None, '___sec28'),
- (' Rotation ', 3, None, '___sec29')]}
- end of tocinfo -->
- <body>
- <script type="text/x-mathjax-config">
- MathJax.Hub.Config({
- TeX: {
- equationNumbers: { autoNumber: "none" },
- extensions: ["AMSmath.js", "AMSsymbols.js", "autobold.js", "color.js"]
- }
- });
- </script>
- <script type="text/javascript"
- src="http://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML">
- </script>
-
- <a name="part0003"></a>
- <p>
- <!-- begin top navigation -->
- <table style="width: 100%"><tr><td>
- <div style="text-align: left;"><a href="._pysketcher002.html"><img src="http://hplgit.github.io/doconce/bundled/html_images/prev1.png" border=0 alt="« Previous"></a></div>
- </td><td>
- </td></tr></table>
- <!-- end top navigation -->
- </p>
- <p>
- <!-- !split -->
- <h1 id="___sec18">Inner Workings of the Pysketcher Tool </h1>
- <p>
- We shall now explain how we can, quite easily, realize software with
- the capabilities demonstrated in the previous examples. Each object in
- the figure is represented as a class in a class hierarchy. Using
- inheritance, classes can inherit properties from parent classes and
- add new geometric features.
- <p>
- Class programming is a key technology for realizing Pysketcher.
- 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 the <code>pysketcher</code> package is a hierarchical tree, and much
- of the material on implementation issues targets how to traverse tree
- structures with recursive function calls in object hierarchies. This
- topic is of key relevance in a wide range of other applications as
- well. In total, the inner workings of Pysketcher constitute an
- excellent example on the power of class programming.
- <h2 id="___sec19">Example of Classes for Geometric Objects </h2>
- <p>
- We introduce class <code>Shape</code> as superclass for all specialized objects
- in a figure. This class does not store any data, but provides a
- series of functions that add functionality to all the subclasses.
- This will be shown later.
- <h3 id="___sec20">Simple Geometric Objects </h3>
- <p>
- One simple subclass is <code>Rectangle</code>, specified by the coordinates of
- the lower left corner and its width and height:
- <p>
- <!-- code=python (!bc pycod) typeset with pygments style "default" -->
- <div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">Rectangle</span>(Shape):
- <span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">__init__</span>(<span style="color: #008000">self</span>, lower_left_corner, width, height):
- p <span style="color: #666666">=</span> lower_left_corner <span style="color: #408080; font-style: italic"># short form</span>
- x <span style="color: #666666">=</span> [p[<span style="color: #666666">0</span>], p[<span style="color: #666666">0</span>] <span style="color: #666666">+</span> width,
- p[<span style="color: #666666">0</span>] <span style="color: #666666">+</span> width, p[<span style="color: #666666">0</span>], p[<span style="color: #666666">0</span>]]
- y <span style="color: #666666">=</span> [p[<span style="color: #666666">1</span>], p[<span style="color: #666666">1</span>], p[<span style="color: #666666">1</span>] <span style="color: #666666">+</span> height,
- p[<span style="color: #666666">1</span>] <span style="color: #666666">+</span> height, p[<span style="color: #666666">1</span>]]
- <span style="color: #008000">self</span><span style="color: #666666">.</span>shapes <span style="color: #666666">=</span> {<span style="color: #BA2121">'rectangle'</span>: Curve(x,y)}
- </pre></div>
- <p>
- Any subclass of <code>Shape</code> will have a constructor that takes geometric
- information about the shape of the object and creates a dictionary
- <code>self.shapes</code> with the shape built of simpler shapes. The most
- fundamental shape is <code>Curve</code>, which is just a collection of \( (x,y) \)
- coordinates in two arrays <code>x</code> and <code>y</code>. Drawing the <code>Curve</code> object is
- a matter of plotting <code>y</code> versus <code>x</code>. For class <code>Rectangle</code> the <code>x</code>
- and <code>y</code> arrays contain the corner points of the rectangle in
- counterclockwise direction, starting and ending with in the lower left
- corner.
- <p>
- Class <code>Line</code> is also a simple class:
- <p>
- <!-- code=python (!bc pycod) typeset with pygments style "default" -->
- <div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">Line</span>(Shape):
- <span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">__init__</span>(<span style="color: #008000">self</span>, start, end):
- x <span style="color: #666666">=</span> [start[<span style="color: #666666">0</span>], end[<span style="color: #666666">0</span>]]
- y <span style="color: #666666">=</span> [start[<span style="color: #666666">1</span>], end[<span style="color: #666666">1</span>]]
- <span style="color: #008000">self</span><span style="color: #666666">.</span>shapes <span style="color: #666666">=</span> {<span style="color: #BA2121">'line'</span>: Curve(x, y)}
- </pre></div>
- <p>
- Here we only need two points, the start and end point on the line.
- However, we may want to add some useful functionality, e.g., the ability
- to give an \( x \) coordinate and have the class calculate the
- corresponding \( y \) coordinate:
- <p>
- <!-- code=python (!bc pycod) typeset with pygments style "default" -->
- <div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"> <span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">__call__</span>(<span style="color: #008000">self</span>, x):
- <span style="color: #BA2121; font-style: italic">"""Given x, return y on the line."""</span>
- x, y <span style="color: #666666">=</span> <span style="color: #008000">self</span><span style="color: #666666">.</span>shapes[<span style="color: #BA2121">'line'</span>]<span style="color: #666666">.</span>x, <span style="color: #008000">self</span><span style="color: #666666">.</span>shapes[<span style="color: #BA2121">'line'</span>]<span style="color: #666666">.</span>y
- <span style="color: #008000">self</span><span style="color: #666666">.</span>a <span style="color: #666666">=</span> (y[<span style="color: #666666">1</span>] <span style="color: #666666">-</span> y[<span style="color: #666666">0</span>])<span style="color: #666666">/</span>(x[<span style="color: #666666">1</span>] <span style="color: #666666">-</span> x[<span style="color: #666666">0</span>])
- <span style="color: #008000">self</span><span style="color: #666666">.</span>b <span style="color: #666666">=</span> y[<span style="color: #666666">0</span>] <span style="color: #666666">-</span> <span style="color: #008000">self</span><span style="color: #666666">.</span>a<span style="color: #666666">*</span>x[<span style="color: #666666">0</span>]
- <span style="color: #008000; font-weight: bold">return</span> <span style="color: #008000">self</span><span style="color: #666666">.</span>a<span style="color: #666666">*</span>x <span style="color: #666666">+</span> <span style="color: #008000">self</span><span style="color: #666666">.</span>b
- </pre></div>
- <p>
- Unfortunately, this is too simplistic because vertical lines cannot be
- handled (infinite <code>self.a</code>). The true source code of <code>Line</code> therefore
- provides a more general solution at the cost of significantly longer
- code with more tests.
- <p>
- A circle implies a somewhat increased complexity. Again we represent
- the geometric object by a <code>Curve</code> object, but this time the <code>Curve</code>
- object needs to store a large number of points on the curve such that
- a plotting program produces a visually smooth curve. The points on
- the circle must be calculated manually in the constructor of class
- <code>Circle</code>. The formulas for points \( (x,y) \) on a curve with radius \( R \)
- and center at \( (x_0, y_0) \) are given by
- $$
- \begin{align*}
- x &= x_0 + R\cos (t),\\
- y &= y_0 + R\sin (t),
- \end{align*}
- $$
- where \( t\in [0, 2\pi] \). A discrete set of \( t \) values in this
- interval gives the corresponding set of \( (x,y) \) coordinates on
- the circle. The user must specify the resolution as the number
- of \( t \) values. The circle's radius and center must of course
- also be specified.
- <p>
- We can write the <code>Circle</code> class as
- <p>
- <!-- code=python (!bc pycod) typeset with pygments style "default" -->
- <div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">Circle</span>(Shape):
- <span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">__init__</span>(<span style="color: #008000">self</span>, center, radius, resolution<span style="color: #666666">=180</span>):
- <span style="color: #008000">self</span><span style="color: #666666">.</span>center, <span style="color: #008000">self</span><span style="color: #666666">.</span>radius <span style="color: #666666">=</span> center, radius
- <span style="color: #008000">self</span><span style="color: #666666">.</span>resolution <span style="color: #666666">=</span> resolution
- t <span style="color: #666666">=</span> linspace(<span style="color: #666666">0</span>, <span style="color: #666666">2*</span>pi, resolution<span style="color: #666666">+1</span>)
- x0 <span style="color: #666666">=</span> center[<span style="color: #666666">0</span>]; y0 <span style="color: #666666">=</span> center[<span style="color: #666666">1</span>]
- R <span style="color: #666666">=</span> radius
- x <span style="color: #666666">=</span> x0 <span style="color: #666666">+</span> R<span style="color: #666666">*</span>cos(t)
- y <span style="color: #666666">=</span> y0 <span style="color: #666666">+</span> R<span style="color: #666666">*</span>sin(t)
- <span style="color: #008000">self</span><span style="color: #666666">.</span>shapes <span style="color: #666666">=</span> {<span style="color: #BA2121">'circle'</span>: Curve(x, y)}
- </pre></div>
- <p>
- As in class <code>Line</code> we can offer the possibility to give an angle
- \( \theta \) (equivalent to \( t \) in the formulas above)
- and then get the corresponding \( x \) and \( y \) coordinates:
- <p>
- <!-- code=python (!bc pycod) typeset with pygments style "default" -->
- <div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"> <span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">__call__</span>(<span style="color: #008000">self</span>, theta):
- <span style="color: #BA2121; font-style: italic">"""Return (x, y) point corresponding to angle theta."""</span>
- <span style="color: #008000; font-weight: bold">return</span> <span style="color: #008000">self</span><span style="color: #666666">.</span>center[<span style="color: #666666">0</span>] <span style="color: #666666">+</span> <span style="color: #008000">self</span><span style="color: #666666">.</span>radius<span style="color: #666666">*</span>cos(theta), \
- <span style="color: #008000">self</span><span style="color: #666666">.</span>center[<span style="color: #666666">1</span>] <span style="color: #666666">+</span> <span style="color: #008000">self</span><span style="color: #666666">.</span>radius<span style="color: #666666">*</span>sin(theta)
- </pre></div>
- <p>
- There is one flaw with this method: it yields illegal values after
- a translation, scaling, or rotation of the circle.
- <p>
- A part of a circle, an arc, is a frequent geometric object when
- drawing mechanical systems. The arc is constructed much like
- a circle, but \( t \) runs in \( [\theta_s, \theta_s + \theta_a] \). Giving
- \( \theta_s \) and \( \theta_a \) the slightly more descriptive names
- <code>start_angle</code> and <code>arc_angle</code>, the code looks like this:
- <p>
- <!-- code=python (!bc pycod) typeset with pygments style "default" -->
- <div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">Arc</span>(Shape):
- <span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">__init__</span>(<span style="color: #008000">self</span>, center, radius,
- start_angle, arc_angle,
- resolution<span style="color: #666666">=180</span>):
- <span style="color: #008000">self</span><span style="color: #666666">.</span>start_angle <span style="color: #666666">=</span> radians(start_angle)
- <span style="color: #008000">self</span><span style="color: #666666">.</span>arc_angle <span style="color: #666666">=</span> radians(arc_angle)
- t <span style="color: #666666">=</span> linspace(<span style="color: #008000">self</span><span style="color: #666666">.</span>start_angle,
- <span style="color: #008000">self</span><span style="color: #666666">.</span>start_angle <span style="color: #666666">+</span> <span style="color: #008000">self</span><span style="color: #666666">.</span>arc_angle,
- resolution<span style="color: #666666">+1</span>)
- x0 <span style="color: #666666">=</span> center[<span style="color: #666666">0</span>]; y0 <span style="color: #666666">=</span> center[<span style="color: #666666">1</span>]
- R <span style="color: #666666">=</span> radius
- x <span style="color: #666666">=</span> x0 <span style="color: #666666">+</span> R<span style="color: #666666">*</span>cos(t)
- y <span style="color: #666666">=</span> y0 <span style="color: #666666">+</span> R<span style="color: #666666">*</span>sin(t)
- <span style="color: #008000">self</span><span style="color: #666666">.</span>shapes <span style="color: #666666">=</span> {<span style="color: #BA2121">'arc'</span>: Curve(x, y)}
- </pre></div>
- <p>
- Having the <code>Arc</code> class, a <code>Circle</code> can alternatively be defined as
- a subclass specializing the arc to a circle:
- <p>
- <!-- code=python (!bc pycod) typeset with pygments style "default" -->
- <div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">Circle</span>(Arc):
- <span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">__init__</span>(<span style="color: #008000">self</span>, center, radius, resolution<span style="color: #666666">=180</span>):
- Arc<span style="color: #666666">.</span>__init__(<span style="color: #008000">self</span>, center, radius, <span style="color: #666666">0</span>, <span style="color: #666666">360</span>, resolution)
- </pre></div>
- <h3 id="___sec21">Class Curve </h3>
- <p>
- Class <code>Curve</code> sits on the coordinates to be drawn, but how is that
- done? The constructor of class <code>Curve</code> just stores the coordinates,
- while a method <code>draw</code> sends the coordinates to the plotting program to
- make a graph. Or more precisely, to avoid a lot of (e.g.)
- Matplotlib-specific plotting commands in class <code>Curve</code> we have created
- a small layer with a simple programming interface to plotting
- programs. This makes it straightforward to change from Matplotlib to
- another plotting program. The programming interface is represented by
- the <code>drawing_tool</code> object and has a few functions:
- <ul>
- <li> <code>plot_curve</code> for sending a curve in terms of \( x \) and \( y \) coordinates
- to the plotting program,</li>
- <li> <code>set_coordinate_system</code> for specifying the graphics area,</li>
- <li> <code>erase</code> for deleting all elements of the graph,</li>
- <li> <code>set_grid</code> for turning on a grid (convenient while constructing the figure),</li>
- <li> <code>set_instruction_file</code> for creating a separate file with all
- plotting commands (Matplotlib commands in our case),</li>
- <li> a series of <code>set_X</code> functions where <code>X</code> is some property like
- <code>linecolor</code>, <code>linestyle</code>, <code>linewidth</code>, <code>filled_curves</code>.</li>
- </ul>
- This is basically all we need to communicate to a plotting program.
- <p>
- Any class in the <code>Shape</code> hierarchy inherits <code>set_X</code> functions for
- setting properties of curves. This information is propagated to
- all other shape objects in the <code>self.shapes</code> dictionary. Class
- <code>Curve</code> stores the line properties together with the coordinates
- of its curve and propagates this information to the plotting program.
- When saying <code>vehicle.set_linewidth(10)</code>, all objects that make
- up the <code>vehicle</code> object will get a <code>set_linewidth(10)</code> call,
- but only the <code>Curve</code> object at the end of the chain will actually
- store the information and send it to the plotting program.
- <p>
- A rough sketch of class <code>Curve</code> reads
- <p>
- <!-- code=python (!bc pycod) typeset with pygments style "default" -->
- <div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">Curve</span>(Shape):
- <span style="color: #BA2121; font-style: italic">"""General curve as a sequence of (x,y) coordintes."""</span>
- <span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">__init__</span>(<span style="color: #008000">self</span>, x, y):
- <span style="color: #008000">self</span><span style="color: #666666">.</span>x <span style="color: #666666">=</span> asarray(x, dtype<span style="color: #666666">=</span><span style="color: #008000">float</span>)
- <span style="color: #008000">self</span><span style="color: #666666">.</span>y <span style="color: #666666">=</span> asarray(y, dtype<span style="color: #666666">=</span><span style="color: #008000">float</span>)
- <span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">draw</span>(<span style="color: #008000">self</span>):
- drawing_tool<span style="color: #666666">.</span>plot_curve(
- <span style="color: #008000">self</span><span style="color: #666666">.</span>x, <span style="color: #008000">self</span><span style="color: #666666">.</span>y,
- <span style="color: #008000">self</span><span style="color: #666666">.</span>linestyle, <span style="color: #008000">self</span><span style="color: #666666">.</span>linewidth, <span style="color: #008000">self</span><span style="color: #666666">.</span>linecolor, <span style="color: #666666">...</span>)
- <span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">set_linewidth</span>(<span style="color: #008000">self</span>, width):
- <span style="color: #008000">self</span><span style="color: #666666">.</span>linewidth <span style="color: #666666">=</span> width
- det set_linestyle(<span style="color: #008000">self</span>, style):
- <span style="color: #008000">self</span><span style="color: #666666">.</span>linestyle <span style="color: #666666">=</span> style
- <span style="color: #666666">...</span>
- </pre></div>
- <h3 id="___sec22">Compound Geometric Objects </h3>
- <p>
- The simple classes <code>Line</code>, <code>Arc</code>, and <code>Circle</code> could can the geometric
- shape through just one <code>Curve</code> object. More complicated shapes are
- built from instances of various subclasses of <code>Shape</code>. Classes used
- for professional drawings soon get quite complex in composition and
- have a lot of geometric details, so here we prefer to make a very
- simple composition: the already drawn vehicle from Figure
- <a href="._pysketcher001.html#sketcher:fig:vehicle0">2</a>. That is, instead of composing the drawing
- in a Python program as shown above, we make a subclass <code>Vehicle0</code> in
- the <code>Shape</code> hierarchy for doing the same thing.
- <p>
- The <code>Shape</code> hierarchy is found in the <code>pysketcher</code> package, so to use these
- classes or derive a new one, we need to import <code>pysketcher</code>. The constructor
- of class <code>Vehicle0</code> performs approximately the same statements as
- in the example program we developed for making the drawing in
- Figure <a href="._pysketcher001.html#sketcher:fig:vehicle0">2</a>.
- <p>
- <!-- code=python (!bc pycod) typeset with pygments style "default" -->
- <div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">from</span> <span style="color: #0000FF; font-weight: bold">pysketcher</span> <span style="color: #008000; font-weight: bold">import</span> <span style="color: #666666">*</span>
- <span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">Vehicle0</span>(Shape):
- <span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">__init__</span>(<span style="color: #008000">self</span>, w_1, R, L, H):
- wheel1 <span style="color: #666666">=</span> Circle(center<span style="color: #666666">=</span>(w_1, R), radius<span style="color: #666666">=</span>R)
- wheel2 <span style="color: #666666">=</span> wheel1<span style="color: #666666">.</span>copy()
- wheel2<span style="color: #666666">.</span>translate((L,<span style="color: #666666">0</span>))
- under <span style="color: #666666">=</span> Rectangle(lower_left_corner<span style="color: #666666">=</span>(w_1<span style="color: #666666">-2*</span>R, <span style="color: #666666">2*</span>R),
- width<span style="color: #666666">=2*</span>R <span style="color: #666666">+</span> L <span style="color: #666666">+</span> <span style="color: #666666">2*</span>R, height<span style="color: #666666">=</span>H)
- over <span style="color: #666666">=</span> Rectangle(lower_left_corner<span style="color: #666666">=</span>(w_1, <span style="color: #666666">2*</span>R <span style="color: #666666">+</span> H),
- width<span style="color: #666666">=2.5*</span>R, height<span style="color: #666666">=1.25*</span>H)
- wheels <span style="color: #666666">=</span> Composition(
- {<span style="color: #BA2121">'wheel1'</span>: wheel1, <span style="color: #BA2121">'wheel2'</span>: wheel2})
- body <span style="color: #666666">=</span> Composition(
- {<span style="color: #BA2121">'under'</span>: under, <span style="color: #BA2121">'over'</span>: over})
- vehicle <span style="color: #666666">=</span> Composition({<span style="color: #BA2121">'wheels'</span>: wheels, <span style="color: #BA2121">'body'</span>: body})
- xmax <span style="color: #666666">=</span> w_1 <span style="color: #666666">+</span> <span style="color: #666666">2*</span>L <span style="color: #666666">+</span> <span style="color: #666666">3*</span>R
- ground <span style="color: #666666">=</span> Wall(x<span style="color: #666666">=</span>[R, xmax], y<span style="color: #666666">=</span>[<span style="color: #666666">0</span>, <span style="color: #666666">0</span>], thickness<span style="color: #666666">=-0.3*</span>R)
- <span style="color: #008000">self</span><span style="color: #666666">.</span>shapes <span style="color: #666666">=</span> {<span style="color: #BA2121">'vehicle'</span>: vehicle, <span style="color: #BA2121">'ground'</span>: ground}
- </pre></div>
- <p>
- Any subclass of <code>Shape</code> <em>must</em> define the <code>shapes</code> attribute, otherwise
- the inherited <code>draw</code> method (and a lot of other methods too) will
- not work.
- <p>
- The painting of the vehicle, as shown in the right part of
- Figure <a href="._pysketcher001.html#sketcher:fig:vehicle0:v2">6</a>, could in class <code>Vehicle0</code>
- be offered by a method:
- <p>
- <!-- code=python (!bc pycod) typeset with pygments style "default" -->
- <div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"> <span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">colorful</span>(<span style="color: #008000">self</span>):
- wheels <span style="color: #666666">=</span> <span style="color: #008000">self</span><span style="color: #666666">.</span>shapes[<span style="color: #BA2121">'vehicle'</span>][<span style="color: #BA2121">'wheels'</span>]
- wheels<span style="color: #666666">.</span>set_filled_curves(<span style="color: #BA2121">'blue'</span>)
- wheels<span style="color: #666666">.</span>set_linewidth(<span style="color: #666666">6</span>)
- wheels<span style="color: #666666">.</span>set_linecolor(<span style="color: #BA2121">'black'</span>)
- under <span style="color: #666666">=</span> <span style="color: #008000">self</span><span style="color: #666666">.</span>shapes[<span style="color: #BA2121">'vehicle'</span>][<span style="color: #BA2121">'body'</span>][<span style="color: #BA2121">'under'</span>]
- under<span style="color: #666666">.</span>set_filled_curves(<span style="color: #BA2121">'red'</span>)
- over <span style="color: #666666">=</span> <span style="color: #008000">self</span><span style="color: #666666">.</span>shapes[<span style="color: #BA2121">'vehicle'</span>][<span style="color: #BA2121">'body'</span>][<span style="color: #BA2121">'over'</span>]
- over<span style="color: #666666">.</span>set_filled_curves(pattern<span style="color: #666666">=</span><span style="color: #BA2121">'/'</span>)
- over<span style="color: #666666">.</span>set_linewidth(<span style="color: #666666">14</span>)
- </pre></div>
- <p>
- The usage of the class is simple: after having set up an appropriate
- coordinate system as previously shown, we can do
- <p>
- <!-- code=python (!bc pycod) typeset with pygments style "default" -->
- <div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%">vehicle <span style="color: #666666">=</span> Vehicle0(w_1, R, L, H)
- vehicle<span style="color: #666666">.</span>draw()
- drawing_tool<span style="color: #666666">.</span>display()
- </pre></div>
- <p>
- and go on the make a painted version by
- <p>
- <!-- code=python (!bc pycod) typeset with pygments style "default" -->
- <div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%">drawing_tool<span style="color: #666666">.</span>erase()
- vehicle<span style="color: #666666">.</span>colorful()
- vehicle<span style="color: #666666">.</span>draw()
- drawing_tool<span style="color: #666666">.</span>display()
- </pre></div>
- <p>
- A complete code defining and using class <code>Vehicle0</code> is found in the file
- <a href="http://tinyurl.com/ot733jn/vehicle2.py" target="_self"><tt>vehicle2.py</tt></a>.
- <p>
- The <code>pysketcher</code> package contains a wide range of classes for various
- geometrical objects, particularly those that are frequently used in
- drawings of mechanical systems.
- <h2 id="___sec23">Adding Functionality via Recursion </h2>
- <p>
- The really powerful feature of our class hierarchy is that we can add
- much functionality to the superclass <code>Shape</code> and to the "bottom" class
- <code>Curve</code>, and then all other classes for various types of geometrical shapes
- immediately get the new functionality. To explain the idea we may
- look at the <code>draw</code> method, which all classes in the <code>Shape</code>
- hierarchy must have. The inner workings of the <code>draw</code> method explain
- the secrets of how a series of other useful operations on figures
- can be implemented.
- <h3 id="___sec24">Basic Principles of Recursion </h3>
- <p>
- Note that we work with two types of hierarchies in the
- present documentation: one Python <em>class hierarchy</em>,
- with <code>Shape</code> as superclass, and one <em>object hierarchy</em> of figure elements
- in a specific figure. A subclass of <code>Shape</code> stores its figure in the
- <code>self.shapes</code> dictionary. This dictionary represents the object hierarchy
- of figure elements for that class. We want to make one <code>draw</code> call
- for an instance, say our class <code>Vehicle0</code>, and then we want this call
- to be propagated to <em>all</em> objects that are contained in
- <code>self.shapes</code> and all is nested subdictionaries. How is this done?
- <p>
- The natural starting point is to call <code>draw</code> for each <code>Shape</code> object
- in the <code>self.shapes</code> dictionary:
- <p>
- <!-- code=python (!bc pycod) typeset with pygments style "default" -->
- <div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">draw</span>(<span style="color: #008000">self</span>):
- <span style="color: #008000; font-weight: bold">for</span> shape <span style="color: #AA22FF; font-weight: bold">in</span> <span style="color: #008000">self</span><span style="color: #666666">.</span>shapes:
- <span style="color: #008000">self</span><span style="color: #666666">.</span>shapes[shape]<span style="color: #666666">.</span>draw()
- </pre></div>
- <p>
- This general method can be provided by class <code>Shape</code> and inherited in
- subclasses like <code>Vehicle0</code>. Let <code>v</code> be a <code>Vehicle0</code> instance.
- Seemingly, a call <code>v.draw()</code> just calls
- <p>
- <!-- code=python (!bc pycod) typeset with pygments style "default" -->
- <div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%">v<span style="color: #666666">.</span>shapes[<span style="color: #BA2121">'vehicle'</span>]<span style="color: #666666">.</span>draw()
- v<span style="color: #666666">.</span>shapes[<span style="color: #BA2121">'ground'</span>]<span style="color: #666666">.</span>draw()
- </pre></div>
- <p>
- However, in the former call we call the <code>draw</code> method of a <code>Composition</code> object
- whose <code>self.shapes</code> attributed has two elements: <code>wheels</code> and <code>body</code>.
- Since class <code>Composition</code> inherits the same <code>draw</code> method, this method will
- run through <code>self.shapes</code> and call <code>wheels.draw()</code> and <code>body.draw()</code>.
- Now, the <code>wheels</code> object is also a <code>Composition</code> with the same <code>draw</code>
- method, which will run through <code>self.shapes</code>, now containing
- the <code>wheel1</code> and <code>wheel2</code> objects. The <code>wheel1</code> object is a <code>Circle</code>,
- so calling <code>wheel1.draw()</code> calls the <code>draw</code> method in class <code>Circle</code>,
- but this is the same <code>draw</code> method as shown above. This method will
- therefore traverse the circle's <code>shapes</code> dictionary, which we have seen
- consists of one <code>Curve</code> element.
- <p>
- The <code>Curve</code> object holds the coordinates to be plotted so here <code>draw</code>
- really needs to do something "physical", namely send the coordinates to
- the plotting program. The <code>draw</code> method is outlined in the short listing
- of class <code>Curve</code> shown previously.
- <p>
- We can go to any of the other shape objects that appear in the figure
- hierarchy and follow their <code>draw</code> calls in the similar way. Every time,
- a <code>draw</code> call will invoke a new <code>draw</code> call, until we eventually hit
- a <code>Curve</code> object at the "bottom" of the figure hierarchy, and then that part
- of the figure is really plotted (or more precisely, the coordinates
- are sent to a plotting program).
- <p>
- When a method calls itself, such as <code>draw</code> does, the calls are known as
- <em>recursive</em> and the programming principle is referred to as
- <em>recursion</em>. This technique is very often used to traverse hierarchical
- structures like the figure structures we work with here. Even though the
- hierarchy of objects building up a figure are of different types, they
- all inherit the same <code>draw</code> method and therefore exhibit the same
- behavior with respect to drawing. Only the <code>Curve</code> object has a different
- <code>draw</code> method, which does not lead to more recursion.
- <h3 id="___sec25">Explaining Recursion </h3>
- <p>
- Understanding recursion is usually a challenge. To get a better idea of
- how recursion works, we have equipped class <code>Shape</code> with a method <code>recurse</code>
- that just visits all the objects in the <code>shapes</code> dictionary and prints
- out a message for each object.
- This feature allows us to trace the execution and see exactly where
- we are in the hierarchy and which objects that are visited.
- <p>
- The <code>recurse</code> method is very similar to <code>draw</code>:
- <p>
- <!-- code=python (!bc pycod) typeset with pygments style "default" -->
- <div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"> <span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">recurse</span>(<span style="color: #008000">self</span>, name, indent<span style="color: #666666">=0</span>):
- <span style="color: #408080; font-style: italic"># print message where we are (name is where we come from)</span>
- <span style="color: #008000; font-weight: bold">for</span> shape <span style="color: #AA22FF; font-weight: bold">in</span> <span style="color: #008000">self</span><span style="color: #666666">.</span>shapes:
- <span style="color: #408080; font-style: italic"># print message about which object to visit</span>
- <span style="color: #008000">self</span><span style="color: #666666">.</span>shapes[shape]<span style="color: #666666">.</span>recurse(indent<span style="color: #666666">+2</span>, shape)
- </pre></div>
- <p>
- The <code>indent</code> parameter governs how much the message from this
- <code>recurse</code> method is intended. We increase <code>indent</code> by 2 for every
- level in the hierarchy, i.e., every row of objects in Figure
- <a href="#sketcher:fig:Vehicle0:hier2">8</a>. This indentation makes it easy to
- see on the printout how far down in the hierarchy we are.
- <p>
- A typical message written by <code>recurse</code> when <code>name</code> is <code>'body'</code> and
- the <code>shapes</code> dictionary has the keys <code>'over'</code> and <code>'under'</code>,
- will be
- <p>
- <!-- code=text (!bc dat) typeset with pygments style "default" -->
- <div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"> Composition: body.shapes has entries 'over', 'under'
- call body.shapes["over"].recurse("over", 6)
- </pre></div>
- <p>
- The number of leading blanks on each line corresponds to the value of
- <code>indent</code>. The code printing out such messages looks like
- <p>
- <!-- code=python (!bc pycod) typeset with pygments style "default" -->
- <div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"> <span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">recurse</span>(<span style="color: #008000">self</span>, name, indent<span style="color: #666666">=0</span>):
- space <span style="color: #666666">=</span> <span style="color: #BA2121">' '</span><span style="color: #666666">*</span>indent
- <span style="color: #008000; font-weight: bold">print</span> space, <span style="color: #BA2121">'</span><span style="color: #BB6688; font-weight: bold">%s</span><span style="color: #BA2121">: </span><span style="color: #BB6688; font-weight: bold">%s</span><span style="color: #BA2121">.shapes has entries'</span> <span style="color: #666666">%</span> \
- (<span style="color: #008000">self</span><span style="color: #666666">.</span>__class__<span style="color: #666666">.</span>__name__, name), \
- <span style="color: #008000">str</span>(<span style="color: #008000">list</span>(<span style="color: #008000">self</span><span style="color: #666666">.</span>shapes<span style="color: #666666">.</span>keys()))[<span style="color: #666666">1</span>:<span style="color: #666666">-1</span>]
- <span style="color: #008000; font-weight: bold">for</span> shape <span style="color: #AA22FF; font-weight: bold">in</span> <span style="color: #008000">self</span><span style="color: #666666">.</span>shapes:
- <span style="color: #008000; font-weight: bold">print</span> space,
- <span style="color: #008000; font-weight: bold">print</span> <span style="color: #BA2121">'call </span><span style="color: #BB6688; font-weight: bold">%s</span><span style="color: #BA2121">.shapes["</span><span style="color: #BB6688; font-weight: bold">%s</span><span style="color: #BA2121">"].recurse("</span><span style="color: #BB6688; font-weight: bold">%s</span><span style="color: #BA2121">", </span><span style="color: #BB6688; font-weight: bold">%d</span><span style="color: #BA2121">)'</span> <span style="color: #666666">%</span> \
- (name, shape, shape, indent<span style="color: #666666">+2</span>)
- <span style="color: #008000">self</span><span style="color: #666666">.</span>shapes[shape]<span style="color: #666666">.</span>recurse(shape, indent<span style="color: #666666">+2</span>)
- </pre></div>
- <p>
- Let us follow a <code>v.recurse('vehicle')</code> call in detail, <code>v</code> being
- a <code>Vehicle0</code> instance. Before looking into the output from <code>recurse</code>,
- let us get an overview of the figure hierarchy in the <code>v</code> object
- (as produced by <code>print v</code>)
- <p>
- <!-- code=text (!bc dat) typeset with pygments style "default" -->
- <div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%">ground
- wall
- vehicle
- body
- over
- rectangle
- under
- rectangle
- wheels
- wheel1
- arc
- wheel2
- arc
- </pre></div>
- <p>
- The <code>recurse</code> method performs the same kind of traversal of the
- hierarchy, but writes out and explains a lot more.
- <p>
- The data structure represented by <code>v.shapes</code> is known as a <em>tree</em>.
- As in physical trees, there is a <em>root</em>, here the <code>v.shapes</code>
- dictionary. A graphical illustration of the tree (upside down) is
- shown in Figure <a href="#sketcher:fig:Vehicle0:hier2">8</a>.
- From the root there are one or more branches, here two:
- <code>ground</code> and <code>vehicle</code>. Following the <code>vehicle</code> branch, it has two new
- branches, <code>body</code> and <code>wheels</code>. Relationships as in family trees
- are often used to describe the relations in object trees too: we say
- that <code>vehicle</code> is the parent of <code>body</code> and that <code>body</code> is a child of
- <code>vehicle</code>. The term <em>node</em> is also often used to describe an element
- in a tree. A node may have several other nodes as <em>descendants</em>.
- <p>
- <center> <!-- figure -->
- <hr class="figure">
- <center><p class="caption">Figure 8: Hierarchy of figure elements in an instance of class <code>Vehicle0</code>. <div id="sketcher:fig:Vehicle0:hier2"></div> </p></center>
- <p><img src="fig-tut/Vehicle0_hier2.png" align="bottom" width=600></p>
- </center>
- <p>
- Recursion is the principal programming technique to traverse tree structures.
- Any object in the tree can be viewed as a root of a subtree. For
- example, <code>wheels</code> is the root of a subtree that branches into
- <code>wheel1</code> and <code>wheel2</code>. So when processing an object in the tree,
- we imagine we process the root and then recurse into a subtree, but the
- first object we recurse into can be viewed as the root of the subtree, so the
- processing procedure of the parent object can be repeated.
- <p>
- A recommended next step is to simulate the <code>recurse</code> method by hand and
- carefully check that what happens in the visits to <code>recurse</code> is
- consistent with the output listed below. Although tedious, this is
- a major exercise that guaranteed will help to demystify recursion.
- <p>
- A part of the printout of <code>v.recurse('vehicle')</code> looks like
- <p>
- <!-- code=text (!bc dat) typeset with pygments style "default" -->
- <div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"> Vehicle0: vehicle.shapes has entries 'ground', 'vehicle'
- call vehicle.shapes["ground"].recurse("ground", 2)
- Wall: ground.shapes has entries 'wall'
- call ground.shapes["wall"].recurse("wall", 4)
- reached "bottom" object Curve
- call vehicle.shapes["vehicle"].recurse("vehicle", 2)
- Composition: vehicle.shapes has entries 'body', 'wheels'
- call vehicle.shapes["body"].recurse("body", 4)
- Composition: body.shapes has entries 'over', 'under'
- call body.shapes["over"].recurse("over", 6)
- Rectangle: over.shapes has entries 'rectangle'
- call over.shapes["rectangle"].recurse("rectangle", 8)
- reached "bottom" object Curve
- call body.shapes["under"].recurse("under", 6)
- Rectangle: under.shapes has entries 'rectangle'
- call under.shapes["rectangle"].recurse("rectangle", 8)
- reached "bottom" object Curve
- ...
- </pre></div>
- <p>
- This example should clearly demonstrate the principle that we
- can start at any object in the tree and do a recursive set
- of calls with that object as root.
- <h2 id="sketcher:scaling">Scaling, Translating, and Rotating a Figure</h2>
- <p>
- With recursion, as explained in the previous section, we can within
- minutes equip <em>all</em> classes in the <code>Shape</code> hierarchy, both present and
- future ones, with the ability to scale the figure, translate it,
- or rotate it. This added functionality requires only a few lines
- of code.
- <h3 id="___sec27">Scaling </h3>
- <p>
- We start with the simplest of the three geometric transformations,
- namely scaling. For a <code>Curve</code> instance containing a set of \( n \)
- coordinates \( (x_i,y_i) \) that make up a curve, scaling by a factor \( a \)
- means that we multiply all the \( x \) and \( y \) coordinates by \( a \):
- $$
- x_i \leftarrow ax_i,\quad y_i\leftarrow ay_i,
- \quad i=0,\ldots,n-1\thinspace .
- $$
- Here we apply the arrow as an assignment operator.
- The corresponding Python implementation in
- class <code>Curve</code> reads
- <p>
- <!-- code=python (!bc pycod) typeset with pygments style "default" -->
- <div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">Curve</span>:
- <span style="color: #666666">...</span>
- <span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">scale</span>(<span style="color: #008000">self</span>, factor):
- <span style="color: #008000">self</span><span style="color: #666666">.</span>x <span style="color: #666666">=</span> factor<span style="color: #666666">*</span><span style="color: #008000">self</span><span style="color: #666666">.</span>x
- <span style="color: #008000">self</span><span style="color: #666666">.</span>y <span style="color: #666666">=</span> factor<span style="color: #666666">*</span><span style="color: #008000">self</span><span style="color: #666666">.</span>y
- </pre></div>
- <p>
- Note here that <code>self.x</code> and <code>self.y</code> are Numerical Python arrays,
- so that multiplication by a scalar number <code>factor</code> is
- a vectorized operation.
- <p>
- An even more efficient implementation is to make use of in-place
- multiplication in the arrays,
- <p>
- <!-- code=python (!bc pycod) typeset with pygments style "default" -->
- <div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">Curve</span>:
- <span style="color: #666666">...</span>
- <span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">scale</span>(<span style="color: #008000">self</span>, factor):
- <span style="color: #008000">self</span><span style="color: #666666">.</span>x <span style="color: #666666">*=</span> factor
- <span style="color: #008000">self</span><span style="color: #666666">.</span>y <span style="color: #666666">*=</span> factor
- </pre></div>
- <p>
- as this saves the creation of temporary arrays like <code>factor*self.x</code>.
- <p>
- In an instance of a subclass of <code>Shape</code>, the meaning of a method
- <code>scale</code> is to run through all objects in the dictionary <code>shapes</code> and
- ask each object to scale itself. This is the same delegation of
- actions to subclass instances as we do in the <code>draw</code> (or <code>recurse</code>)
- method. All objects, except <code>Curve</code> instances, can share the same
- implementation of the <code>scale</code> method. Therefore, we place the <code>scale</code>
- method in the superclass <code>Shape</code> such that all subclasses inherit the
- method. Since <code>scale</code> and <code>draw</code> are so similar, we can easily
- implement the <code>scale</code> method in class <code>Shape</code> by copying and editing
- the <code>draw</code> method:
- <p>
- <!-- code=python (!bc pycod) typeset with pygments style "default" -->
- <div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">Shape</span>:
- <span style="color: #666666">...</span>
- <span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">scale</span>(<span style="color: #008000">self</span>, factor):
- <span style="color: #008000; font-weight: bold">for</span> shape <span style="color: #AA22FF; font-weight: bold">in</span> <span style="color: #008000">self</span><span style="color: #666666">.</span>shapes:
- <span style="color: #008000">self</span><span style="color: #666666">.</span>shapes[shape]<span style="color: #666666">.</span>scale(factor)
- </pre></div>
- <p>
- This is all we have to do in order to equip all subclasses of
- <code>Shape</code> with scaling functionality!
- Any piece of the figure will scale itself, in the same manner
- as it can draw itself.
- <h3 id="___sec28">Translation </h3>
- <p>
- A set of coordinates \( (x_i, y_i) \) can be translated \( v_0 \) units in
- the \( x \) direction and \( v_1 \) units in the \( y \) direction using the formulas
- $$
- \begin{equation*}
- x_i\leftarrow x_i+v_0,\quad y_i\leftarrow y_i+v_1,
- \quad i=0,\ldots,n-1\thinspace .
- \end{equation*}
- $$
- The natural specification of the translation is in terms of the
- vector \( v=(v_0,v_1) \).
- The corresponding Python implementation in class <code>Curve</code> becomes
- <p>
- <!-- code=python (!bc pycod) typeset with pygments style "default" -->
- <div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">Curve</span>:
- <span style="color: #666666">...</span>
- <span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">translate</span>(<span style="color: #008000">self</span>, v):
- <span style="color: #008000">self</span><span style="color: #666666">.</span>x <span style="color: #666666">+=</span> v[<span style="color: #666666">0</span>]
- <span style="color: #008000">self</span><span style="color: #666666">.</span>y <span style="color: #666666">+=</span> v[<span style="color: #666666">1</span>]
- </pre></div>
- <p>
- The translation operation for a shape object is very similar to the
- scaling and drawing operations. This means that we can implement a
- common method <code>translate</code> in the superclass <code>Shape</code>. The code
- is parallel to the <code>scale</code> method:
- <p>
- <!-- code=python (!bc pycod) typeset with pygments style "default" -->
- <div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">Shape</span>:
- <span style="color: #666666">....</span>
- <span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">translate</span>(<span style="color: #008000">self</span>, v):
- <span style="color: #008000; font-weight: bold">for</span> shape <span style="color: #AA22FF; font-weight: bold">in</span> <span style="color: #008000">self</span><span style="color: #666666">.</span>shapes:
- <span style="color: #008000">self</span><span style="color: #666666">.</span>shapes[shape]<span style="color: #666666">.</span>translate(v)
- </pre></div>
- <h3 id="___sec29">Rotation </h3>
- <p>
- Rotating a figure is more complicated than scaling and translating.
- A counter clockwise rotation of \( \theta \) degrees for a set of
- coordinates \( (x_i,y_i) \) is given by
- $$
- \begin{align*}
- \bar x_i &\leftarrow x_i\cos\theta - y_i\sin\theta,\\
- \bar y_i &\leftarrow x_i\sin\theta + y_i\cos\theta\thinspace .
- \end{align*}
- $$
- This rotation is performed around the origin. If we want the figure
- to be rotated with respect to a general point \( (x,y) \), we need to
- extend the formulas above:
- $$
- \begin{align*}
- \bar x_i &\leftarrow x + (x_i -x)\cos\theta - (y_i -y)\sin\theta,\\
- \bar y_i &\leftarrow y + (x_i -x)\sin\theta + (y_i -y)\cos\theta\thinspace .
- \end{align*}
- $$
- The Python implementation in class <code>Curve</code>, assuming that \( \theta \)
- is given in degrees and not in radians, becomes
- <p>
- <!-- code=python (!bc pycod) typeset with pygments style "default" -->
- <div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"> <span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">rotate</span>(<span style="color: #008000">self</span>, angle, center):
- angle <span style="color: #666666">=</span> radians(angle)
- x, y <span style="color: #666666">=</span> center
- c <span style="color: #666666">=</span> cos(angle); s <span style="color: #666666">=</span> sin(angle)
- xnew <span style="color: #666666">=</span> x <span style="color: #666666">+</span> (<span style="color: #008000">self</span><span style="color: #666666">.</span>x <span style="color: #666666">-</span> x)<span style="color: #666666">*</span>c <span style="color: #666666">-</span> (<span style="color: #008000">self</span><span style="color: #666666">.</span>y <span style="color: #666666">-</span> y)<span style="color: #666666">*</span>s
- ynew <span style="color: #666666">=</span> y <span style="color: #666666">+</span> (<span style="color: #008000">self</span><span style="color: #666666">.</span>x <span style="color: #666666">-</span> x)<span style="color: #666666">*</span>s <span style="color: #666666">+</span> (<span style="color: #008000">self</span><span style="color: #666666">.</span>y <span style="color: #666666">-</span> y)<span style="color: #666666">*</span>c
- <span style="color: #008000">self</span><span style="color: #666666">.</span>x <span style="color: #666666">=</span> xnew
- <span style="color: #008000">self</span><span style="color: #666666">.</span>y <span style="color: #666666">=</span> ynew
- </pre></div>
- <p>
- The <code>rotate</code> method in class <code>Shape</code> follows the principle of the
- <code>draw</code>, <code>scale</code>, and <code>translate</code> methods.
- <p>
- We have already seen the <code>rotate</code> method in action when animating the
- rolling wheel at the end of the section <a href="._pysketcher001.html#sketcher:vehicle1:anim">Animation: Rolling the Wheels</a>.
- <p>
- <!-- begin bottom navigation -->
- <table style="width: 100%"><tr><td>
- <div style="text-align: left;"><a href="._pysketcher002.html"><img src="http://hplgit.github.io/doconce/bundled/html_images/prev1.png" border=0 alt="« Previous"></a></div>
- </td><td>
- </td></tr></table>
- <!-- end bottom navigation -->
- </p>
- <!-- ------------------- end of main content --------------- -->
- </body>
- </html>
-
|