tkscene: a scene geometry manager for tkinter a scene geometry manager for tkinter john w. shipman...

66
tkscene: A scene geometry manager for Tkinter John W. Shipman 2011-09-14 18:56 Abstract A Python-language module that manages the transformations between scene and display geo- metry for the Tkinter graphical user interface. This publication is available in Web form 1 and also as a PDF document 2 . Please forward any comments to [email protected]. Table of Contents 1. Scene coordinates and display coordinates ................................................................................. 3 2. The toolbase ............................................................................................................................ 3 3. Downloadable files .................................................................................................................. 4 4. Design considerations for Tkinter .............................................................................................. 4 5. Space cases .............................................................................................................................. 5 6. Design of the Rudiment class ................................................................................................... 6 7. The tkscene.py file: Prologue ................................................................................................ 7 8. Module imports ....................................................................................................................... 8 9. Manifest constants ................................................................................................................... 8 9.1. Standard angles ............................................................................................................. 8 9.2. COLOR_OPTION ............................................................................................................ 8 9.3. FILL_OPTION .............................................................................................................. 9 9.4. OUTLINE_OPTION ........................................................................................................ 9 9.5. SMOOTH_OPTION .......................................................................................................... 9 9.6. TAGS_OPTION .............................................................................................................. 9 9.7. WIDTH_OPTION ............................................................................................................. 9 9.8. IDENTITY_XFORM ........................................................................................................ 9 10. class Scene: The scene-display transform ............................................................................ 9 10.1. Future work: stacking order ......................................................................................... 11 10.2. Scene.__init__(): Constructor .............................................................................. 11 10.3. Scene.__findTransform(): Map the scene onto the display space ........................... 12 10.4. Scene.__fixAspect(): Match aspect ratios ............................................................. 13 10.5. Scene.__buildTransform(): Build the scene transform .......................................... 13 10.6. Scene.s_d(): Scene to display transform .................................................................. 14 10.7. Scene.d_s(): Display to scene transform ................................................................... 15 10.8. Scene.place(): Add a copy of an element to the scene .............................................. 15 10.9. Scene.remove(): Remove one or all elements ........................................................... 15 10.10. Scene.tkDraw(): Draw the scene on a Tkinter canvas .............................................. 16 10.11. Scene.erase() ...................................................................................................... 16 1 http://www.nmt.edu/tcc/help/lang/python/examples/tkscene/ 2 http://www.nmt.edu/tcc/help/lang/python/examples/tkscene/tkscene.pdf 1 tkscene: A scene geometry manager New Mexico Tech Computer Center

Upload: dangkhanh

Post on 20-May-2018

221 views

Category:

Documents


1 download

TRANSCRIPT

Page 1: tkscene: A scene geometry manager for Tkinter A scene geometry manager for Tkinter John W. Shipman 2011-09-14 18:56 Abstract A Python-language module that manages the …

tkscene: A scene geometrymanager for Tkinter

John W. Shipman2011-09-14 18:56

Abstract

A Python-language module that manages the transformations between scene and display geo-metry for the Tkinter graphical user interface.

This publication is available in Web form1 and also as a PDF document2. Please forward anycomments to [email protected].

Table of Contents1. Scene coordinates and display coordinates ................................................................................. 32. The toolbase ............................................................................................................................ 33. Downloadable files .................................................................................................................. 44. Design considerations for Tkinter .............................................................................................. 45. Space cases .............................................................................................................................. 56. Design of the Rudiment class ................................................................................................... 67. The tkscene.py file: Prologue ................................................................................................ 78. Module imports ....................................................................................................................... 89. Manifest constants ................................................................................................................... 8

9.1. Standard angles ............................................................................................................. 89.2. COLOR_OPTION ............................................................................................................ 89.3. FILL_OPTION .............................................................................................................. 99.4. OUTLINE_OPTION ........................................................................................................ 99.5. SMOOTH_OPTION .......................................................................................................... 99.6. TAGS_OPTION .............................................................................................................. 99.7. WIDTH_OPTION ............................................................................................................. 99.8. IDENTITY_XFORM ........................................................................................................ 9

10. class Scene: The scene-display transform ............................................................................ 910.1. Future work: stacking order ......................................................................................... 1110.2. Scene.__init__(): Constructor .............................................................................. 1110.3. Scene.__findTransform(): Map the scene onto the display space ........................... 1210.4. Scene.__fixAspect(): Match aspect ratios ............................................................. 1310.5. Scene.__buildTransform(): Build the scene transform .......................................... 1310.6. Scene.s_d(): Scene to display transform .................................................................. 1410.7. Scene.d_s(): Display to scene transform ................................................................... 1510.8. Scene.place(): Add a copy of an element to the scene .............................................. 1510.9. Scene.remove(): Remove one or all elements ........................................................... 1510.10. Scene.tkDraw(): Draw the scene on a Tkinter canvas .............................................. 1610.11. Scene.erase() ...................................................................................................... 16

1 http://www.nmt.edu/tcc/help/lang/python/examples/tkscene/2 http://www.nmt.edu/tcc/help/lang/python/examples/tkscene/tkscene.pdf

1tkscene: A scene geometry managerNew Mexico Tech Computer Center

About this document
This document has been generated with RenderX XEP. Visit http://www.renderx.com/ to learn more about RenderX family of software solutions for digital typography.
Page 2: tkscene: A scene geometry manager for Tkinter A scene geometry manager for Tkinter John W. Shipman 2011-09-14 18:56 Abstract A Python-language module that manages the …

10.12. Scene.__str__() .................................................................................................. 1611. class Element: Generic scene element ............................................................................... 17

11.1. Beyond rudiments ...................................................................................................... 1812. class PlacedElt: One copy of an element in the scene ....................................................... 18

12.1. PlacedElt.__init__() ......................................................................................... 1912.2. PlacedElt.tkDraw(): Draw this element on a canvas ............................................... 2012.3. PlacedElt.erase() ............................................................................................... 2112.4. PlacedElt.__str__() ........................................................................................... 21

13. class Rudiment: Scene display component ......................................................................... 2113.1. Rudiment class variables ............................................................................................ 2313.2. Rudiment.__init__(): Constructor ........................................................................ 2313.3. Rudiment.transform(): Create a transformed rudiment ......................................... 2313.4. Rudiment.addCan(): Add the oid of a canvas object ................................................. 2413.5. Rudiment.erase(): Remove this rudiment from the display ..................................... 2413.6. Rudiment.tkDraw(): Render this rudiment .............................................................. 2513.7. Rudiment.setWidth(): Store the width value .......................................................... 2513.8. Rudiment.getWidth(): Set up Tkinter's border width .............................................. 2513.9. Rudiment.filterOptions(): Set up Tkinter option values ...................................... 2613.10. Rudiment.__str__() ............................................................................................ 27

14. class StraightRudiment: One line segment .................................................................... 2714.1. StraightRudiment.__init__() ........................................................................... 2814.2. StraightRudiment.make(): Factory method .......................................................... 2914.3. StraightRudiment.tkDraw(): Render it ................................................................ 30

15. class BoxRudiment: Rectangle rudiment ........................................................................... 3015.1. BoxRudiment.__init__() ..................................................................................... 3215.2. BoxRudiment.make() ............................................................................................. 3215.3. BoxRudiment.tkDraw() ......................................................................................... 33

16. class OvalRudiment: Ellipse ............................................................................................ 3416.1. OvalRudiment.__init__() ................................................................................... 3516.2. OvalRudiment.make() ........................................................................................... 3516.3. OvalRudiment.tkDraw() ....................................................................................... 3616.4. OvalRudiment.__drawOval() ............................................................................... 3916.5. OvalRudiment.__drawPolygon() ......................................................................... 39

17. class ArcRudiment: Circular arc ....................................................................................... 4017.1. ArcRudiment.__init__() ...................................................................................... 4217.2. ArcRudiment.make(): Circular arc .......................................................................... 4217.3. ArcRudiment.tkDraw() ......................................................................................... 43

18. class PolygonRudiment: Arbitrary polygons .................................................................... 4418.1. PolygonRudiment.__init__() ............................................................................. 4518.2. PolygonRudiment.make() ..................................................................................... 4518.3. PolygonRudiment.tkDraw() ................................................................................. 4618.4. PolygonRudiment.vertexNo(): Point name for a numbered vertex ......................... 47

19. class Cardinals: A transformable collection of named points ............................................ 4720. class Box: Basic rectangle .................................................................................................. 48

20.1. Box.__init__(): Constructor .................................................................................. 4920.2. Box.__contains__() ............................................................................................. 4920.3. Box.sizes(): Return the sizes of the sides ................................................................ 5020.4. Box.aspect(): Compute the aspect ratio .................................................................. 50

21. pointsTuple(): Build a Tkinter points tuple ....................................................................... 5022. An example: conference .................................................................................................... 50

22.1. Prologue .................................................................................................................... 5122.2. Module imports ......................................................................................................... 52

New Mexico Tech Computer Centertkscene: A scene geometry manager2

Page 3: tkscene: A scene geometry manager for Tkinter A scene geometry manager for Tkinter John W. Shipman 2011-09-14 18:56 Abstract A Python-language module that manages the …

22.3. Manifest constants ...................................................................................................... 5222.4. Main program ............................................................................................................ 5322.5. class App: The application as a whole ...................................................................... 5322.6. App.__createWidgets(): Set up Tkinter widgets .................................................... 5422.7. App.__buildScene(): Set up the canvas .................................................................. 5522.8. App.__furnish(): Arrange the furniture ................................................................. 5522.9. App.__placeTable() ............................................................................................. 5622.10. App.__placeCouch() ............................................................................................ 5722.11. App.__placeMovingChairs() .............................................................................. 5822.12. App.__setChairPositions() ............................................................................. 5922.13. App.__angleHandler(): Handler for the chair angle widget ................................... 6122.14. class Couch: Sectional seating ............................................................................... 6122.15. Couch.__init__() ................................................................................................ 6322.16. Couch.render() .................................................................................................... 6322.17. class RoundTable ................................................................................................ 6422.18. RoundTable.__init__() ...................................................................................... 6422.19. RoundTable.render() .......................................................................................... 6422.20. Epilogue .................................................................................................................. 64

1. Scene coordinates and display coordinatesIn some graphic applications we need to describe items in terms of some other coordinate system thanan actual display device. For example, if you are displaying earth maps, you will want to use feet ormiles or UTM coordinates; you don't want to worry about exactly where it will show up on your screen.

Two vital definitions:

scene coordinatesThe coordinate system that is convenient for the application.

display coordinatesThe addresses of pixels on a display device, or perhaps dots on paper.

To address this problem, this document describes the tkscene module, which manages the conversionbetween scene and display coordinates using the Python programming language3. The initial imple-mentation will use Python's Tkinter graphical user interface4, but other display platforms may be sup-ported in the future.

2.The toolbaseThis application layer is built on top of a number of other pieces. Here are some relevant documents.

• The Python programming language5.

• The Tkinter graphical user interface6 for Python.

• Graphics transformations with homogeneous coordinates7 describes the homcoord.py module, whichtakes care of calculations involving general coordinate transforms.

3 http://www.python.org/4 http://www.nmt.edu/tcc/help/pubs/tkinter/5 http://www.python.org/6 http://www.nmt.edu/tcc/help/pubs/tkinter/7 http://www.nmt.edu/tcc/help/lang/python/examples/homcoord/

3tkscene: A scene geometry managerNew Mexico Tech Computer Center

Page 4: tkscene: A scene geometry manager for Tkinter A scene geometry manager for Tkinter John W. Shipman 2011-09-14 18:56 Abstract A Python-language module that manages the …

3. Downloadable files• tkscene.py8 is the main Python module for this application.

• tkscene.xml9 is the DocBook-XML10 source file for this document.

• conference11 is a small demonstration script, described in Section 22, “An example: confer-ence” (p. 50).

4. Design considerations for TkinterThis application is built on a 2½-dimensional model: all rendered elements are flat, but they have astacking order that determines which elements are “in front” of others and therefore visible.

We assume that ultimately your scene will be rendered in Tkinter using a Canvas widget.

Let us examine the set of graphics primitives available for this widget and consider how each may relateto scene coordinates. Considering the three types of transforms—translation, scaling, and rotation—someprimitives cannot be transformed in arbitrary ways.

arcIn general, a Tkinter arc is a wedge-shaped slice out of an oval. It may be rendered in three styles:PIESLICE, CHORD, and ARC; refer to the Tkinter document for illustrations. For reasons describedbelow under the oval primitive, we can scale and translate arcs but cannot rotate them by arbitraryangles.

bitmapA bitmap is a rasterized image using two colors. A bitmap cannot be scaled or rotated, but it canbe translated.

imageA Tkinter image is a rasterized color graphic. Such items cannot be scaled or rotated, but they canbe translated.

lineA Tkinter line is defined by a sequence of two or more points. The rendering can be either a linkedsequence of line segments, or an approximated spline curve. These primitives can be translated,scaled, or rotated freely, and will still render correctly in Tkinter.

These primitives support a large number of features: dash or dot patterns, arrows, cap and joinstyles, for instance. The first release of tkscene will support only solid lines of various widths andcolors.

ovalActually an ellipse, Tkinter ovals are described by the rectangle that encloses them. The ellipsesrendered by the Canvas.create_oval() method are always oriented with the major and minoraxes parallel to the coordinate axes. However, for ellipses (other than circles) whose bounding boxesare not parallel to the axes, we can use Tkinter's Canvas.create_polygon() method to createa four-vertex polygon with the smooth=1 option to render an ellipse that can be rotated by an ar-bitrary angle.

However, this trick will not work to get rotated arcs. Tkinter's arc rudiments are based on their el-lipses, which do not support arbitrary rotation.

8 http://www.nmt.edu/tcc/help/lang/python/examples/tkscene/tkscene.py9 http://www.nmt.edu/tcc/help/lang/python/examples/tkscene/tkscene.xml10 http://www.nmt.edu/tcc/help/pubs/docbook/11 http://www.nmt.edu/tcc/help/lang/python/examples/tkscene/conference

New Mexico Tech Computer Centertkscene: A scene geometry manager4

Page 5: tkscene: A scene geometry manager for Tkinter A scene geometry manager for Tkinter John W. Shipman 2011-09-14 18:56 Abstract A Python-language module that manages the …

polygonBecause polygons are defined by the set of their vertices, they may be translated, scaled, or rotated.

rectangleThe sides of Tkinter rectangles must be parallel to the major axes, and they are defined by two op-posite corner points. However, because Tkinter supports polygons, if we represent rectangles in-ternally using all four corners, we can render rectangles that have been arbitrarily translated, scaled,or rotated.

textTkinter fonts are sized according to pixels or printer's points, and there is no facility for drawingthem at any orientation except horizontal. Hence, the only transform they could support is translation.

windowA Tkinter window item is used to place active widgets on the canvas. They can be positioned andsized explicitly, but their function is tied quite closely to the Tkinter application, and are not reallyelements of a rendered scene. Hence, tkscene will not support them directly.

Here is a table summarizing the Tkinter primitives, and which ones will be supported as tkscene rudi-ments. You may still use the other primitives, of course, and tkscene provides facilities for positioningthem on the canvas in terms of their locations in scene space.

RudimentRotateScaleTranslatePrimitiveYesNoYesYesarc

NoNoNoYesbitmap

NoNoNoYesimage

YesYesYesYesline

YesYesYesYesoval

NoYesYesYespolygon

YesYesYesYesrectangle

NoNoNoYestext

NoNoNoYeswindow

5. Space casesComplex scenes are often constructed by reproducing many copies of basic building blocks. For example,in a floor plan, you may use a library of furniture items to show the arrangement of tables, desks andchairs.

To construct these building blocks, you will draw a diagram showing the rendered elements in someCartesian coordinate system.

Effectively, then, we have to manage three different spaces in our application.

• The display space is the final destination, such as a flat panel. Coordinates will be the addresses ofspecific pixels.

• The scene space uses the coordinates that you are modeling, such as miles or centimeters.• Within a graphics building block, the reference space is whatever coordinate system is most convenient

for building up a complex image from primitive components. Typically the coordinates of a buildingblock will have the same scale as scene space, so that the process of placing a copy of the buildingblock into the scene consists of translation and rotation transforms.

5tkscene: A scene geometry managerNew Mexico Tech Computer Center

Page 6: tkscene: A scene geometry manager for Tkinter A scene geometry manager for Tkinter John W. Shipman 2011-09-14 18:56 Abstract A Python-language module that manages the …

ImportantRendering order is important. When rendering a compound element whose pieces overlap, it is vitalto draw the elements starting from the deepest (background) elements and proceeding toward theviewer, so that those drawn last conceal those drawn earlier, where they overlap.

Hence, any item that needs to render itself on the canvas will be a Python generator that generates asequence of rudiments from back to front.

Here, then, is an outline of the rendering process.

1. Write the necessary building blocks you will need to build up your scene. Each building block hasa .draw() method that renders itself by generating a sequence of rudiments in its reference space.

2. Create your Tkinter application and the canvas where the rendering will take place.

3. Create a Scene instance that defines the range of scene coordinates and the size of the canvas. Thisinstance can convert scene to display coordinates and back again.

4. Build up your scene using instances of your building blocks. For each copy of a building block placedinto the scene, keep track of the transform from the building block's reference space to the scenespace.

5. To render the scene, each building block generates a sequence of graphics rudiments in its referencespace. As each rudiment is generated, it is transformed into the scene space, and then into the displayspace, where it is rendered onto the canvas.

The various graphics primitives are all derived from base class Rudiment. This class has a .trans-form(x) method that returns the same primitive transformed into a new space by some transform x(an instance of the homcoord.Xform class). Instances also have a .tkDraw() method that rendersthat rudiment onto a Tkinter Canvas widget.

6. Design of the Rudiment classThere are a number of issues that complicate the implementation of the Rudiment.transform(x)method that takes a transform object x, as a homcoord.Xform instance, and uses it to create a newRudiment instance in the transformed space.

It would be nice to implement the .transform() method in the base class. However, each derivedclass may have a completely different set of attributes. With this architecture, each derived class wouldhave had to supply its own .transform() method, so that the attributes particular to that class couldbe transformed into the new instance.

As is often the case, the author got into the pool one day to swim laps and emerged with a solution.

Ultimately, every graphics rudiment can be defined in terms of a set of parameter values. For example:

• A straight line segment is defined by two endpoints.

• A circular arc can be defined in several ways, but Tkinter's arcs are defined in terms of a boundingbox (which defines the circle) and two angles that specify where along that circle to start and stopdrawing.

• A rectangle, assuming its sides are parallel to the coordinate axes, is defined by two points at oppositeends of one of its diagonals.

So, in order to implement transformation in the base class, it must have some way to find all the pointsin the instance that must be transformed to the new space.

New Mexico Tech Computer Centertkscene: A scene geometry manager6

Page 7: tkscene: A scene geometry manager for Tkinter A scene geometry manager for Tkinter John W. Shipman 2011-09-14 18:56 Abstract A Python-language module that manages the …

Part of the solution is to create a container class called Cardinals that stores all the point coordinates(sometimes called “cardinal points”) that define the shape of the rudiment. Each Rudiment instancehas one of these inside it. The Rudiment.transform() method can produce a new Cardinals instancefrom the old one by applying the transform to each point.

There are three remaining problems.

• What about attributes of a rudiment that are expressed as angles? For instance, the circular arcprimitive in Tkinter is defined by four items: two points that define the bounding box of the circle,and two angles that define which part of the circle is included in the arc.

The solution to this is to redesign the rudiments that use angles so that they are defined in terms ofpoints instead. For example, instead of specifying the limits of an arc rudiment as angles, we coulduse the endpoints of the arc.

• What about dimensional attributes? The line rudiment has a width attribute. We want all the dimen-sions of a rudiment to be expressed in the same space: so, if we are rendering a straight section ofrail, we want the width of the rail to be expressed in scene space (feet and inches). The line widthshould be transformed from reference to scene to display coordinates just like any other.

We can't apply a transform to a dimension, but we can do the equivalent graphically. A line from (0,0) to (0, m) has a length m; so, if we transform both those points, the distance between them afterwardis the transformed dimension.

In practice, rather than defining two points, we'll reuse one of the other cardinal points (x, y), anddefine a width reference point at (x+m, y). When it comes time to render the line, we measure thedistance between those points to get the current width.

• What about non-dimensional attributes like colors? A color attribute is specific to the derived class,not to rudiments generally.

One approach is to carry these attributes in a separate dictionary rather than make them part of theCardinals class.

There is a more subtle problem. How does the Rudiment.transform() method create the new in-stance? The .__class__ attribute of the derived class tells us what class constructor to call. But howdoes a method in the base class know what the argument list of the constructor of the derived classlooks like?

The solution to all these problems is to carry all the effective attributes of a Rudiment instance in onlytwo physical attributes:

• The points that define the shape of the rudiment are stored as named points in its Cardinals instance.Linear dimensions are represented as pairs of points.

• Non-dimensional attributes are carried in a separate dictionary; we'll call it the aux (for auxiliary)dictionary.

Now we can restrict the constructor calling sequences of all the derived classes to this general form:

Rudiment ( tagList, cardinals, aux )

So the Rudiment.transform() method knows what constructor to call—self.__class__()—and what arguments to pass it.

7.The tkscene.py file: PrologueThe tkscene module starts with a module documentation string that points back at this documentation.

7tkscene: A scene geometry managerNew Mexico Tech Computer Center

Page 8: tkscene: A scene geometry manager for Tkinter A scene geometry manager for Tkinter John W. Shipman 2011-09-14 18:56 Abstract A Python-language module that manages the …

tkscene.py

'''tkscene.py: Tkinter scene transform manager.

Do not edit this file. It is extracted automatically from thedocumentation:http://www.nmt.edu/tcc/help/lang/python/examples/tkscene/

'''

8. Module importsAll the coordinate transform geometry is handled by the homcoord package; see Graphics transformationswith homogeneous coordinates12. This module includes pi and all the standard trig functions, althoughsome of the function names differ from those in the standard Python math module: arctan2 and arccosinstead of atan2 and acos.

tkscene.py

# - - - - - I m p o r t s

from homcoord import *

The current graphics base is Tkinter.tkscene.py

from Tkinter import *

9. Manifest constantsThis section declares names that are constant and global to the application.

tkscene.py

# - - - - - M a n i f e s t c o n s t a n t s

9.1. Standard anglesThe four cardinal directions as Cartesian angles in radians, and other common angles. Constants RAD_90and RAD_180, representing 90° and 180° in radians, come from the homcoord module.

tkscene.py

ANGLE_E = 0.0ANGLE_N = RAD_90ANGLE_W = RAD_180ANGLE_S = RAD_180 + RAD_90

9.2. COLOR_OPTIONName of the keyword option used to specify the color of a line.

tkscene.py

COLOR_OPTION = 'color'

12 http://www.nmt.edu/tcc/help/lang/python/examples/homcoord/

New Mexico Tech Computer Centertkscene: A scene geometry manager8

Page 9: tkscene: A scene geometry manager for Tkinter A scene geometry manager for Tkinter John W. Shipman 2011-09-14 18:56 Abstract A Python-language module that manages the …

9.3. FILL_OPTIONName of the keyword option used to specify the interior color of a box.

tkscene.py

FILL_OPTION = 'fill'

9.4. OUTLINE_OPTIONName of the keyword option for the color of the outline of a box.

tkscene.py

OUTLINE_OPTION = 'outline'

9.5. SMOOTH_OPTIONThis is an option sent to Tkinter, not an option for our exported interface.

tkscene.py

SMOOTH_OPTION = 'smooth'

9.6. TAGS_OPTIONThis is an option sent to Tkinter, not an option for our exported interface.

tkscene.py

TAGS_OPTION = 'tags'

9.7. WIDTH_OPTIONKeyword option for specifying line and border widths.

tkscene.py

WIDTH_OPTION = 'width'

9.8. IDENTITY_XFORMAn instance of homcoord.Xform that leaves coordinates untouched. It is implemented as a translationwith zero offsets.

tkscene.py

IDENTITY_XFORM = Xlate( (0.0, 0.0) )

10. class Scene:The scene-display transformThe concern of this class is the relationship between scene coordinates and display coordinates. It isalso a container for PlacedElt instances that describe the scene.

tkscene.py

# - - - - - c l a s s S c e n e

9tkscene: A scene geometry managerNew Mexico Tech Computer Center

Page 10: tkscene: A scene geometry manager for Tkinter A scene geometry manager for Tkinter John W. Shipman 2011-09-14 18:56 Abstract A Python-language module that manages the …

class Scene(object):'''Describes the scene-display relationship.

Exports:Scene(sceneBounds, displayBounds, mag=1.0):[ (sceneBounds is a Box instance specifying the range ofscene coordinates in Cartesian form) and(displayBounds is a Box instance specifying the range ofdisplay coordinates with +x to the right and +y down) and(mag is the ratio between the virtual display bounds andthe display viewport, such that mag=1.0 means that thescene will always fit within the displayBounds) ->return a new Scene instance that can transformscene coordinates to and from display coordinates ]

.sceneBounds: [ as passed to constructor, read-only ]

.displayBounds: [ as passed to constructor, read-only ]

.mag: [ as passed to constructor, read-only ]

.toDisplay:[ the scene-to-display coordinate transform as ahomcoord.Xform instance ]

.fromDisplay:[ the display-toa-scene coordinate transform as ahomcoord.Xform instance ]

.s_d(s):[ s is a point in scene space as a homcoord.Pt instance ->

return the corresponding display coordinates as ahomcoord.Pt instance ]

.d_s(d):[ d is a point in display space as a homcoord.Pt instance ->

return the corresponding scene coordinates as ahomcoord.Pt instance ]

.place(pelt):[ (pelt is a PlacedElement) ->

self := self with a copy of pelt added at the end ofself's drawing sequence

return pelt ].remove ( pelt=None ):[ if pelt is None ->

self := self with all elements removed ]else if pelt is a PlacedElt in self's drawing sequence ->self := self with pelt removed

else -> I ].tkDraw(can):[ can is a Tkinter.Canvas widget ->

can := can - (any previous rendering of self) +(self, rendered) ]

.erase():[ if the scene is currently rendered on a canvas ->

remove all scene's elements from that canvaselse -> I ]

.__str__(): [ return a string representation of self ]

The list of placed elements in the scene is a private attribute.

New Mexico Tech Computer Centertkscene: A scene geometry manager10

Page 11: tkscene: A scene geometry manager for Tkinter A scene geometry manager for Tkinter John W. Shipman 2011-09-14 18:56 Abstract A Python-language module that manages the …

tkscene.py

State/Invariants:.__drawList:[ a sequence of PlacedElt instances representing thecontents of self's scene ]

'''

10.1. Future work: stacking orderIn this version, elements are always added at the end of .__drawList. The only way to change thestacking order is to remove an element and then place it again, and in that case it is always on top.

There are lots of possible ways to give the application control over the stacking order. Here is the author'scurrent thinking on how to implement this.

• The application would define a sequence of layers, from back to front. Each layer would be identifiedby some name.

• When an element is added to the scene, it can specify to which layer it belongs. (It might be useful tohave a default, nameless layer containing elements that do not specify a layer name. This might makefor less work in converting legacy applications.)

• The Scene class would manage the canvas's stacking order so that whenever a rudiment is renderedonto the canvas it is tagged with the layer name.

• Whenever an element is added to a layer other than the top layer, its component display rudimentsare restacked by calling the Tkinter Canvas.tag_lower(b) method on them; this method insertsthe canvas items just before b in its stacking order.

The value of b may be either a tag or an object ID.• If it is a tag, there must be at least one item that carries that tag. Hence, the Scene instance must

know the tag of the next higher layer that actually has items in it. If there are no such items, thenew rudiments can correctly be rendered at the top of the stack.

• If the Scene instance knows the OIDs of all the items it is managing, it can use the OID of the firstitem in the drawing sequence that is above the layer where the new element is to be rendered.

For example, suppose you decide to enhance the furniture-arranging application described in Section 22,“An example: conference” (p. 50). You want to implement seagulls that fly around and, because thisis a plan view, they always appear above (in front of) the furniture and the stage.

This example could be managed with three layers. In order from back to front, you might assign themnames 'room', 'furniture', and 'birds'. When a piece of furniture is moved, it will be necessaryfor the Scene class to insure that all the associated rudiments are placed under all bird-layer rudimentsin the canvas's stacking order, so that the furniture will appear behind the birds.

10.2. Scene.__init__(): Constructortkscene.py

# - - - S c e n e . _ _ i n i t _ _

def __init__(self, sceneBounds, displayBounds, mag=1.0):'''Constructor.'''#-- 1 --self.sceneBounds = sceneBounds

11tkscene: A scene geometry managerNew Mexico Tech Computer Center

Page 12: tkscene: A scene geometry manager for Tkinter A scene geometry manager for Tkinter John W. Shipman 2011-09-14 18:56 Abstract A Python-language module that manages the …

self.displayBounds = displayBoundsself.mag = magself.__drawList = []

For the method that determines the scene-display transform, see Section 10.3, “Scene.__findTrans-form(): Map the scene onto the display space” (p. 12).

tkscene.py

#-- 2 --# [ self.toDisplay := a transform that converts a scene# coordinate into a display coordinate# self.fromDisplay := inverse of that transform ]self.__findTransform()

10.3.Scene.__findTransform(): Map the scene onto the display spacetkscene.py

# - - - S c e n e . _ _ f i n d T r a n s f o r m

def __findTransform(self):'''Figure out how to fit the scene into the display space.

[ self.sceneBounds, self.displayBounds, and self.magare as invariant ->

self.toDisplay := a transform that converts a scenecoordinate into a display coordinate ]

self.fromDisplay := inverse of that transform ]'''

The purpose of this method is to compute a single Xform instance that represents the transform froma point in the scene space to the coordinates of that point in the display space. This transform can beused in the opposite direction, converting the coordinate of a mouse click on the display into the equi-valent scene coordinates.

There is no guarantee that the display rectangle is the same shape as the scene rectangle, so the firststep is to modify the display rectangle so it has the same aspect ratio, defined as the ratio of the widthto the height. For this logic, see Section 10.4, “Scene.__fixAspect(): Match aspect ratios” (p. 13).

tkscene.py

#-- 1 --# [ self.displayBounds := self.displayBounds reduced if# necessary so that it has the same aspect ratio as# self.sceneBounds ]self.__fixAspect()

For the logic that assembles the transform from basic operations, see Section 10.5,“Scene.__buildTransform(): Build the scene transform” (p. 13).

tkscene.py

#-- 2 --# [ self.toDisplay := a transform that converts a scene# coordinate to a coordinate in self.displayBounds# self.fromDisplay := inverse of that transform ]self.__buildTransform()

New Mexico Tech Computer Centertkscene: A scene geometry manager12

Page 13: tkscene: A scene geometry manager for Tkinter A scene geometry manager for Tkinter John W. Shipman 2011-09-14 18:56 Abstract A Python-language module that manages the …

10.4. Scene.__fixAspect(): Match aspect ratiostkscene.py

# - - - S c e n e . _ _ f i x A s p e c t

def __fixAspect(self):'''Make the display bounds the same shape as the scene bounds.'''

The aspect ratio of a box, the ratio of its height to its width, is a larger number for wider boxes, smallerfor narrower boxes. First compute the aspect ratios of the scene and display bounds.

tkscene.py

#-- 1 --# [ aScene := aspect ratio of self.sceneBounds# aDisplay := aspect ratio of self.displayBounds ]aScene = self.sceneBounds.aspect()aDisplay = self.displayBounds.aspect()

If the display's aspect ratio is smaller that the scene's, the display is too tall; reduce the height. Otherwise,the display is too wide; reduce the width.

tkscene.py

#-- 2 --# [ if aDisplay < aScene -># self.displayBounds := self.displayBounds with its# height reduced to its width divided by aScene# else -># self.displayBounds := self.displayBounds with its# width reduced to its height multiplied by aScene ]w, h = self.displayBounds.sizes()if aDisplay < aScene:

h = w / aSceneelif aDisplay > aScene:

w = h * aSceneself.displayBounds = Box ( Pt(0, 0), Pt(w, h) )

10.5. Scene.__buildTransform(): Build the scene transformtkscene.py

# - - - S c e n e . _ _ b u i l d T r a n s f o r m

def __buildTransform(self):'''Construct the scene-display transform.

[ self.toDisplay := a transform that converts a scenecoordinate to a coordinate in self.displayBounds

self.fromDisplay := inverse of that transform ]'''

To build the transform that takes us from scene space to display space, we will use the composition ofa number of the basic transforms from the homcoord module.

The x domain of scene coordinates is the range [xmin, xmax], and the y domain is [ymin, ymax]. Thefirst transform removes the minimum values so that both start at zero.

13tkscene: A scene geometry managerNew Mexico Tech Computer Center

Page 14: tkscene: A scene geometry manager for Tkinter A scene geometry manager for Tkinter John W. Shipman 2011-09-14 18:56 Abstract A Python-language module that manages the …

tkscene.py

#-- 1 --# [ t1 := an Xform that translates scene coordinates to# the origin ]xMin, yMin = self.sceneBounds.cMin.xy()t1 = Xlate ( (-xMin, -yMin) )

Next, we compose a transform that normalizes the maximum coordinates, so the x and y values arenow in the interval [0, 1].

tkscene.py

#-- 2 --# [ t2 := t1 composed with an Xform such that x' is x# divided by xMax - xMin and y= is y divided by# yMax - yMin ]w, h = self.sceneBounds.sizes()t2 = t1.compose ( Xscale ( (1.0/w, 1.0/h) ) )

The next two transforms are necessary because display coordinates are vertically flipped from scenecoordinates: in the display, (0, 0) is the upper left corner, not the lower left corner as in Cartesian co-ordinates. The first of these transforms inverts the y coordinate, and the second translates the y coordinateso that the new y range is [1,0].

tkscene.py

#-- 3 --# [ t3 := t2 composed with an Xform that changes the sign# of the y coordinate ]t3 = t2.compose ( Xscale ( (1.0, -1.0) ) )

#-- 4 --# [ t4 := t3 composed with an Xform that translates y# coordinates by +1 ]t4 = t3.compose ( Xlate ( (0.0, 1.0) ) )

At this point, the x range is [0, 1] and the y range is [1, 0]. The next transform scales the coordinates tothe display space.

tkscene.py

#-- 5 --# [ self.toDisplay := t4 composed with an Xform that# scales coordinates to self.displayBounds# self.fromDisplay := inverse of that transform ]wd, hd = self.displayBounds.cMax.xy()self.toDisplay = t4.compose ( Xscale ( (wd, hd) ) )self.fromDisplay = self.toDisplay.inverse()

10.6. Scene.s_d(): Scene to display transformtkscene.py

# - - - S c e n e . s _ d

def s_d ( self, s ):'''Convert scene to display coordinates.'''return self.toDisplay.apply(s)

New Mexico Tech Computer Centertkscene: A scene geometry manager14

Page 15: tkscene: A scene geometry manager for Tkinter A scene geometry manager for Tkinter John W. Shipman 2011-09-14 18:56 Abstract A Python-language module that manages the …

10.7. Scene.d_s(): Display to scene transformtkscene.py

# - - - S c e n e . d _ s

def d_s ( self, d ):'''Convert display to scene coordinates.'''return self.toDisplay.invert(d)

10.8. Scene.place(): Add a copy of an element to the sceneThis method adds a copy of some element to the scene.

tkscene.py

# - - - S c e n e . p l a c e

def place ( self, pelt ):'''Add a PlacedElt to the end of the drawing list.'''#-- 1 --self.__drawList.append ( pelt )

#-- 2 --return pelt

10.9. Scene.remove(): Remove one or all elementsErases the given PlacedElt from the scene and removes it from the display list. Default is to eraseand remove everything.

tkscene.py

# - - - S c e n e . r e m o v e

def remove ( self, pelt=None ):'''Remove one or all elements.'''#-- 1 --# [ if pelt is None -># all elements of self.__drawList := those elements# erased if rendered# self.__drawList := an empty list# return# else -> I ]if pelt is None:

for elt in self.__drawList:elt.erase()

self.__drawList = []return

#-- 2 --# [ if pelt is in self.__drawList -># pelt := pelt erased if rendered

15tkscene: A scene geometry managerNew Mexico Tech Computer Center

Page 16: tkscene: A scene geometry manager for Tkinter A scene geometry manager for Tkinter John W. Shipman 2011-09-14 18:56 Abstract A Python-language module that manages the …

# self.__drawList := self.__drawList with pelt removed# else -> I ]try:

pelt.erase()self.__drawList.remove(pelt)

except ValueError:pass

10.10. Scene.tkDraw(): Draw the scene on a Tkinter canvasRenders all the placed elements in the drawing list. See Section 12, “class PlacedElt: One copy ofan element in the scene” (p. 18) for its .tkDraw() method.

tkscene.py

# - - - S c e n e . t k D r a w

def tkDraw ( self, can ):'''Render self on a canvas.'''#-- 1 --# [ if self is currently rendered on a canvas -># remove from that canvas all rendered rudiments# else -> I ]self.erase()

#-- 2 --# [ can := can with self rendered onto it ]for pelt in self.__drawList:

pelt.tkDraw ( can )

10.11. Scene.erase()The internal attribute self.__drawList enumerates the PlacedElt instances in the scene. For the.erase() method of this class, see Section 12, “class PlacedElt: One copy of an element in thescene” (p. 18).

tkscene.py

# - - - S c e n e . e r a s e

def erase ( self ):'''Erase the entire scene (but retain the drawing list).'''#-- 1 --for pelt in self.__drawList:

pelt.erase()

10.12. Scene.__str__()This method returns a debug display of the entire scene.

tkscene.py

# - - - S c e n e . _ _ s t r _ _

New Mexico Tech Computer Centertkscene: A scene geometry manager16

Page 17: tkscene: A scene geometry manager for Tkinter A scene geometry manager for Tkinter John W. Shipman 2011-09-14 18:56 Abstract A Python-language module that manages the …

def __str__(self):'''Return a string display of self.'''return "\n".join (

[ str(pelt)for pelt in self.__drawList ] )

11. class Element: Generic scene elementThis is a base class for prefabricated pieces of your scene.

For instance, supposed you are writing an application to set up floor plans. If you have to show how100 identical chairs can be set up in an auditiorium, you shouldn't have to describe the coordinates ofevery vertex of every line or curve segment individually.

Instead, you need only produce a scale drawing of one chair, and work out other details: how closelyshould chairs be spaced in a row? how much room should there be between rows?

Each different furniture type is then represented as a subclass of Element. To write such a class, followthis procedure.

1. On a scale drawing of the piece of furniture, define a Cartesian coordinate system by selecting anorigin, an x axis, and a y axis.

2. Figure out how to draw the figure using only the available rudiment types. This is not always easy;see Section 11.1, “Beyond rudiments” (p. 18) for some help in the harder cases.

3. Assign Cartesian coordinates to points that define the rudiments.

4. Write a .render(C) method that generates rudiments in z-coordinate order, back to front, whereC is a Tkinter.Canvas widget onto which the rudiments are rendered.

Here is the interface to the class.tkscene.py

# - - - - - c l a s s E l e m e n t

class Element(object):'''Base class for scene elements.

Exports:Element():[ return a new, empty Element instance ]

.render():[ generate a sequence of Rudiment instances thatrenders self onto can, back to front, in theelement's reference space ]

.__str__(): [ return a string display of self ]'''def __init__(self):

pass

def render(self):raise NotImplementedError("You must override the "

"Element.render() method in the derived class." )

17tkscene: A scene geometry managerNew Mexico Tech Computer Center

Page 18: tkscene: A scene geometry manager for Tkinter A scene geometry manager for Tkinter John W. Shipman 2011-09-14 18:56 Abstract A Python-language module that manages the …

def __str__(self):return ( "<Element>\n%s" %

'\n'.join ([ str(rudi)for rudi in self.render() ] ) )

11.1. Beyond rudimentsThis system is not intended to be a full-featured graphics rendering engine. Many common graphicsfeatures such as texture mapping are not supported.

When you are writing your Element subclass and find that some aspect of the element is hard to renderwith the existing set of rudiments, in this package you have basically two choices.

• Invent new kinds of rudiments that can still be translated to primitive display elements.

• Draw the elements directly onto the canvas, using the Scene instance to figure out how a given scenelocation translates to a location on the display.

As an example of the latter approach, suppose you are rendering a coffee table in plan view, and youwould like to display wood grain on its surface. It could be implemented like this.

1. Devise an image file containing the wood grain that looks acceptable at 1:1 rendering into displaycoordinates. Make it oversize, or use an image that can be tiled to make arbitrarily large areas.

2. Figure out the display coordinates of the coffee table.

3. Use the Python Imaging Library13 to crop the original image to the current display coordinates, anddisplay it directly on the canvas using .create_image().

12. class PlacedElt: One copy of an element in the sceneAn instance of this class represents an Element instance that has been placed in the scene. The placementis represented as a homcoord.Xform instance that can transform a point in the element's referencespace to the same point in the scene. To move an existing placed element, erase it, modify its .xformattribute to reflect the new position, and redraw it.

tkscene.py

# - - - - - c l a s s P l a c e d E l t

class PlacedElt(object):'''Represents a copy of an Element placed into the scene.

Exports:PlacedElt ( scene, element, xform=None ):[ (scene is the containing Scene instance) and(element is an Element instance) and(xform is a homcoord.Xform instance that describes thetransform from the element's reference space to scenespace) ->scene := scene with a new copy of (element) placed

13 http://www.nmt.edu/tcc/help/pubs/pil/

New Mexico Tech Computer Centertkscene: A scene geometry manager18

Page 19: tkscene: A scene geometry manager for Tkinter A scene geometry manager for Tkinter John W. Shipman 2011-09-14 18:56 Abstract A Python-language module that manages the …

into the scene according to (xform)return a new PlacedElt instance representing thosevalues ]

.element: [ as passed to constructor ]

.xform: [ as passed, with defaulting, read/write ]

.tkDraw(can):[ can is a Tkinter.Canvas widget ->

can := can - (any previous rendering of self) +(self, rendered) ]

.erase():[ if self is currently rendered on a canvas ->

remove from that canvas all rendered rudimentselse -> I ]

.__str__(): [ return a string representation of self ]

When we render the element as a stream of rudiments, we must retain a list of those rudiments, so theycan be erased later. We also remember the canvas so the caller of .erase() doesn't need to provide it.

tkscene.py

State/Invariants:.__rudiList:[ if self has ever been rendered ->

a list containing the Rudiment instances in displayspace generatedduring rendering

else -> an empty list ].__can:[ if self is rendered on a canvas ->

that Tkinter.Canvaselse -> None ]

'''

12.1. PlacedElt.__init__()tkscene.py

# - - - P l a c e d E l t . _ _ i n i t _ _

def __init__(self, scene, element, xform=None):'''Constructor.'''#-- 1 --self.scene = sceneself.element = elementself.xform = xform or IDENTITY_XFORMself.__rudiList = []self.__can = None

#-- 2 --# [ scene := scene with self added at the end of its# display list ]self.scene.place(self)

19tkscene: A scene geometry managerNew Mexico Tech Computer Center

Page 20: tkscene: A scene geometry manager for Tkinter A scene geometry manager for Tkinter John W. Shipman 2011-09-14 18:56 Abstract A Python-language module that manages the …

12.2. PlacedElt.tkDraw(): Draw this element on a canvasThe entire rendering pipeline is in this method.

1. The original element generates a sequence of rudiments in its reference space.

2. We use self.xform to transform each rudiment into the scene space.

3. We use the parent scene's .toDisplay transform to transform each scene space rudiment into adisplay space rudiment.

4. The display space rudiment is drawn onto the canvas. We also retain a reference to each rendereddisplay rudiment in self.__rudiList so we can erase them when the .erase() method is called.

tkscene.py

# - - - P l a c e d E l t . t k D r a w

def tkDraw(self, can):'''Render self onto a canvas.'''#-- 1 --# [ if self is currently rendered onto a canvas -># remove its rendering from that canvas# else -> I ]self.erase()

#-- 2 --self.__can = can

#-- 3 --# [ can := can with self's element rendered onto it,# transformed by self.xform and then by scene.toDisplay# self.__rudiList := display Rudiment instances used to# render, in drawing order ]for eltRudi in self.element.render():

#-- 3 body --# [ eltRudi is a Rudiment in self.element's reference# space -># can := can with eltRudi rendered onto it,# transformed by self.xform and then by# scene.toDisplay# self.__rudiList +:= eltRudi, transformed by# self.xform and then by scene.toDisplay ]#-- 3.1 --# [ sceneRudi := eltRudi, transformed by self.xform ]sceneRudi = eltRudi.transform ( self.xform )

#-- 3.2 --# [ displayRudi := sceneRudi, transformed by# scene.toDisplay# self.__rudiList +:= (same) ]displayRudi = sceneRudi.transform ( self.scene.toDisplay )self.__rudiList.append ( displayRudi )

#-- 3.3 --

New Mexico Tech Computer Centertkscene: A scene geometry manager20

Page 21: tkscene: A scene geometry manager for Tkinter A scene geometry manager for Tkinter John W. Shipman 2011-09-14 18:56 Abstract A Python-language module that manages the …

# [ can := can with displayRudiment rendered onto it ]displayRudi.tkDraw ( can )

12.3. PlacedElt.erase()When the .tkDraw() method of a display-space Rudiment instance has been called to render it ontoa canvas, that instance can also erase itself. We keep a list of those rudiments in self.__rudiList.

tkscene.py

# - - - P l a c e d E l t . e r a s e

def erase ( self ):'''Remove self's rendering from its canvas.'''#-- 1 --if self.__can is None:

return

#-- 2 --# [ self.__can := self.__can - (rendering of rudiments in# self.__rudiList) ]for rudi in self.__rudiList:

rudi.erase ( self.__can )

#-- 3 --self.__rudiList = []self.__can = None

12.4. PlacedElt.__str__()Returns a debug display of the contents of self's element and transform.

tkscene.py

# - - - P l a c e d E l t . _ _ s t r _ _

def __str__(self):'''Return a string display of self.'''return ( "<PElt(%s)>\n%s)>" %

(self.xform, self.element) )

13. class Rudiment: Scene display componentWhen a scene object is rendered onto the display, it is rendered as a sequence of instances of this class.Each instance corresponds to one of the display elements of Tkinter's Canvas widget: straight lines,splines, rectangles, and so forth.

See Section 6, “Design of the Rudiment class” (p. 6) for a lengthy discussion of the arcana requiredfor the .transform() method, and why points are kept inside a Cardinals instance.

tkscene.py

# - - - - - c l a s s R u d i m e n t

21tkscene: A scene geometry managerNew Mexico Tech Computer Center

Page 22: tkscene: A scene geometry manager for Tkinter A scene geometry manager for Tkinter John W. Shipman 2011-09-14 18:56 Abstract A Python-language module that manages the …

class Rudiment(object):'''Base clase for displayed scene elements.

Exports:Rudiment(tagList, cardinals, **kw):[ (tagList is a sequence of tags to be applied to displayelements for this rudiment) and(cardinals is a set of named points as a Cardinalsinstance) and(kw is a dictionary of attributes specific to thederived class) ->return a new Rudiment instance representing thosevalues ]

.tagList: [ as constructor ]

.p: [ the cardinals argument passed to the constructor ]

.aux: [ the kw argument passed to the constructor ]

.transform(x):[ x is a transform as a homcoord.Xform instance ->

return a new instance of self's class with itspoints transformed by x and kw==self.aux ]

.addCan(oid):[ oid is an object ID on a canvas ->

self := self with that oid added ].erase(can):[ can is a Tkinter canvas ->

can := can with all display elements pertaining toself removed ]

.tkDraw(can): # Virtual method[ can is a Tkinter canvas ->

can := can - (display elements pertaining to self) +(display elements rendering self) ]

Rudiment.setWidth(c, ref, kw):[ (c is a Cardinals instance) and(ref is a homcoord.Pt instance) and(kw is a dictionary) ->if kw has a WIDTH_OPTION key ->c := c with a point named Rudiment.PT_W

defined at a distance kw[WIDTH_OPTION] from refin the +x direction

else -> I ].getWidth(kw, ref):[ (kw is a dictionary) and(ref is a homcoord.Pt) ->if self has a point named Rudiment.PT_W ->kw[WIDTH_OPTION] := the distance from ref to that

point, but not less than 1 ]Rudiment.filterOptions(tagList, kw, validOptions):[ (tagList is as in the constructor) and(kw is the user's option dictionary) and(validOptions is a dictionary of valid option keysand their default values or None if there is no default) ->return a new dictionary that is a copy of validOptions

New Mexico Tech Computer Centertkscene: A scene geometry manager22

Page 23: tkscene: A scene geometry manager for Tkinter A scene geometry manager for Tkinter John W. Shipman 2011-09-14 18:56 Abstract A Python-language module that manages the …

but updated by any entries in kw whose keys are invalidOptions, plus an entry with key 'tags' and value(tagList) ]

.__str__(self): [ display self as a string ]

State/Invariants:.__oidList: [ list of canvas object IDs rendering self ]

'''

13.1. Rudiment class variablesSeveral derived classes include a width attribute that describes the width of the outline in scene dimen-sions. By convention, class constant PT_W is the name of a point whose distance from some referencepoint defines the width. This convention allows use of two base class methods: Section 13.7, “Rudi-ment.setWidth(): Store the width value” (p. 25) to check for a width attribute and define pointPT_W, and Section 13.8, “Rudiment.getWidth(): Set up Tkinter's border width” (p. 25) to scale thewidth, if present, into a display option.

tkscene.py

PT_W = 'W'

13.2. Rudiment.__init__(): Constructortkscene.py

# - - - R u d i m e n t . _ _ i n i t _ _

def __init__(self, tagList, cardinals, **kw):'''Constructor'''self.tagList = tagListself.p = cardinalsself.aux = kwself.__oidList = []

13.3. Rudiment.transform(): Create a transformed rudimentBe sure to read Section 6, “Design of the Rudiment class” (p. 6) if you want to understand why thiscode requires so much Python magic.

tkscene.py

# - - - R u d i m e n t . t r a n s f o r m

def transform(self, x):'''Return a new Rudiment transformed using x.'''

The new instance is basically a copy of self except that the Cardinals instance, self.p, has all itspoints transformed using x, which is an instance of homcoord.Xform.

tkscene.py

#-- 1 --# [ newCardinals := self.p transformed by x ]newCardinals = self.p.transform(x)

23tkscene: A scene geometry managerNew Mexico Tech Computer Center

Page 24: tkscene: A scene geometry manager for Tkinter A scene geometry manager for Tkinter John W. Shipman 2011-09-14 18:56 Abstract A Python-language module that manages the …

WarningThe following code works only if every derived class has exactly the same constructor calling sequenceas the base class.

We have everything we need now to create the new instance of self's class. What follows demonstrates,in the author's humble opinion, something that might be hard to do in another language.

The problem is that this method has to work for all derived classes, so it has to create an instance of thatsame derived class. This calls for understanding of two Python special class attributes: the .__new(),used to construct a new, uninitialized instance; and .__class__, which is the class object for any value.

tkscene.py

#-- 2 --# [ newRudi := an unitialized instance of self's class ]newRudi = self.__class__.__new__(self.__class__)

#-- 3 --# [ newRudi := newRudi initialized with taglist=self.tagList,# cardinals=newCardinals, and kw=a copy of self.aux ]self.__class__.__init__(newRudi, self.tagList, newCardinals,

**self.aux)

#-- 4 --return newRudi

In the constructor call above, it is important to note that when you pass a pre-build dictionary to afunction's keyword arguments using the f(**d) syntax, the called function receives a copy of yourdictionary, so you can be sure that that function will not modify your dictionary.

13.4. Rudiment.addCan(): Add the oid of a canvas objecttkscene.py

# - - - R u d i m e n t . a d d C a n

def addCan ( self, oid ):'''Keep track of a canvas object ID so we can delete it later.'''self.__oidList.append ( oid )return oid

13.5. Rudiment.erase(): Remove this rudiment from the displaytkscene.py

# - - - R u d i m e n t . e r a s e

def erase(self, can):'''Remove self's elements from the canvas.'''#-- 1 --# [ can := can minus elements whose oids are in self.__oidList ]

for oid in self.__oidList:

New Mexico Tech Computer Centertkscene: A scene geometry manager24

Page 25: tkscene: A scene geometry manager for Tkinter A scene geometry manager for Tkinter John W. Shipman 2011-09-14 18:56 Abstract A Python-language module that manages the …

can.delete(oid)

#-- 2 --self.__oidList = []

13.6. Rudiment.tkDraw(): Render this rudimenttkscene.py

# - - - R u d i m e n t . t k D r a w

def tkDraw(self, can):'''Virtual method.'''raise NotImplementedError ( "Rudiment.tkDraw() is a "

"virtual method.")

13.7. Rudiment.setWidth(): Store the width valueThis is a service routine to be used by the .make() method of derived classes.

The purpose of this static method is to check for a WIDTH option in a derived class's .make() methodand, if one is found, to store a point named Rudiment.PT_W whose distance from some reference pointdefines the width. For a discussion of why widths are stored as points, see Section 6, “Design of theRudiment class” (p. 6).

tkscene.py

# - - - R u d i m e n t . s e t W i d t h

@staticmethoddef setWidth ( c, ref, kw ):

'''Set the WIDTH_OPTION if specified.'''#-- 1 --# [ if kw has a key WIDTH_OPTION -># w := the corresponding value# else -> return ]try:

w = kw[WIDTH_OPTION]except KeyError:

return

#-- 2 --# [ c := c with a point named Rudiment.PT_W located# (w) away from (ref) in the +x direction ]c[Rudiment.PT_W] = Pt ( ref.x()+w, ref.y() )

13.8. Rudiment.getWidth(): Set up Tkinter's border widthThis is a service routine to be used by the .tkDraw() method of derived classes.

25tkscene: A scene geometry managerNew Mexico Tech Computer Center

Page 26: tkscene: A scene geometry manager for Tkinter A scene geometry manager for Tkinter John W. Shipman 2011-09-14 18:56 Abstract A Python-language module that manages the …

The purpose is to check for a user-specified border width. If it was specified, the instance will have apoint named Rudiment.PT_W whose distance from the given ref point defines the border width. Ifpresent, this point is translated into a width value for Tkinter.

tkscene.py

# - - - R u d i m e n t . g e t W i d t h

def getWidth ( self, kw, ref ):'''Check for a user-specified border width.'''#-- 1 --# [ if self has a point named Rudiment.PT_W -># w := the distance from that point to (ref)# else -> return ]try:

w = ref.dist(self.p[self.PT_W])except KeyError:

return

#-- 2 --# [ kw[WIDTH_OPTION] := w rounded to an integer, but not# less than 1 ]kw[WIDTH_OPTION] = max ( 1, int(round(w)) )

13.9. Rudiment.filterOptions(): Set up Tkinter option valuesThis static method is for use by the .make() method of derived classes. It builds the .aux dictionaryof options.

• The validOptions argument is a dictionary whose set of keys defines the expected option names.The related value for each key is the default option value; if there is no default, specify None as therelated value.

• For any entry in the user-specified kw dictionary whose key is also a key in validOptions, the key-value pair from kw will be in the result.

• A key-value pair will be added with key 'tags' and value tagList, in which form it will be passedto the Canvas item constructor.

tkscene.py

# - - - R u d i m e n t . f i l t e r O p t i o n s

@staticmethoddef filterOptions(tagList, kw, validOptions):

'''Set up an option dictionary for Tkinter.'''#-- 1--# [ aux := a new dictionary containing all the key-value# pairs from (validOptions) whose values are not None ]aux = {}for key, value in validOptions.items():

if value is not None:aux[key] = value

#-- 2 --

New Mexico Tech Computer Centertkscene: A scene geometry manager26

Page 27: tkscene: A scene geometry manager for Tkinter A scene geometry manager for Tkinter John W. Shipman 2011-09-14 18:56 Abstract A Python-language module that manages the …

# [ aux := aux with any entries whose keys are in# validOptions copied from kw ]for key in validOptions:

#-- 2 body --# [ key is a string -># if key is in kw -># aux[key] := kw[key]# else -> I ]try:

aux[key] = kw[key]except KeyError:

pass

#-- 3 --aux[TAGS_OPTION] = tagList

#-- 4 --return aux

13.10. Rudiment.__str__()tkscene.py

# - - - R u d i m e n t . _ _ s t r _ _

def __str__(self):'''Return a string representation of self.'''cards = ' '.join( [ "%s=%s" % (name, str(self.p[name]))

for name in self.p.keys() ] )return ( "<Rudiment(%s)>\n p(%s)\n aux=(%r))" %

(self.tagList, cards, self.aux) )

14. class StraightRudiment: One line segmentAn instance of this class represents a single line segment in scene space. Here are the cardinal points:

Figure 1. Geometry of a straight rudiment

y

PT_APT_W

width

PT_B

x

27tkscene: A scene geometry managerNew Mexico Tech Computer Center

Page 28: tkscene: A scene geometry manager for Tkinter A scene geometry manager for Tkinter John W. Shipman 2011-09-14 18:56 Abstract A Python-language module that manages the …

For a discussion of why these points are stored inside a Cardinal instance self.p inherited from thebase class, and why widths are represented as a pair of points, see Section 6, “Design of the Rudimentclass” (p. 6). The points whose names in the Cardinal class are self.PT_A and self.PT_B are theends of the line segment to be drawn.

If a line width option width=W is specified, we also define a cardinal point named self.PT_W whichis initially located a distance W east (that is, in the +x direction) of PT_A. (PT_W is actually defined inSection 13, “class Rudiment: Scene display component” (p. 21).)

In the .tkDraw() method, if point PT_W is defined, the rendered width will be the distance betweenPT_A and PT_W. If PT_W is undefined, the actual width will be one pixel.

Because of the constraint that all class constructors of subclasses of Rudiment must have exactly thesame arguments (see Section 6, “Design of the Rudiment class” (p. 6)), this class provides a staticmake() method that takes arguments in a more convenient form and converts them into the Cardinalsand auxiliary dictionary required by the Rudiment() constructor.

tkscene.py

# - - - - - c l a s s S t r a i g h t R u d i m e n t

class StraightRudiment(Rudiment):'''Line segment in scene space.

Exports:StraightRudiment(tagList, c, **aux):[ (arguments are as in Rudiment() ->

return a new StraightRudiment instance with thosevalues ]

StraightRudiment.make ( tagList, p1, p2, fill='black',width=None ): # Static method

[ (tagList is as in Rudiment()) and(p1 and p2 are endpoints in scene space as Pt instances) and(fill is a line color as a Tkinter color) and(width is the line width in scene dimensions) ->return a StraightRudiment representing those values,with a minimum line width of 1 pixel ]

.PT_A: [ name of the starting point ]

.PT_B: [ name of the endpoint ]'''PT_A = 'A'PT_B = 'B'DEFAULT_OPTIONS = { 'fill': 'black', 'width': None }

14.1. StraightRudiment.__init__()tkscene.py

# - - - S t r a i g h t R u d i m e n t . _ _ i n i t _ _

def __init__(self, tagList, c, **aux):'''Constructor'''Rudiment.__init__(self, tagList, c, **aux)

New Mexico Tech Computer Centertkscene: A scene geometry manager28

Page 29: tkscene: A scene geometry manager for Tkinter A scene geometry manager for Tkinter John W. Shipman 2011-09-14 18:56 Abstract A Python-language module that manages the …

14.2. StraightRudiment.make(): Factory methodtkscene.py

# - - - S t r a i g h t R u d i m e n t . m a k e

@staticmethoddef make ( tagList, p1, p2, **kw ):

'''Factory method.'''

If this is the first class derived from Rudiment that you have seen, you need to know that the cardinalpoints that define the shape of a rudiment are stored in a Cardinals instance named c, which mostlyworks like a dictionary where the points are stored as homcoord.Pt instances with their names askeys.

In this particular class, there are two points named StraightRudiment.PT_A and StraightRudi-ment.PT_B stored there. For a discussion of why we can't just store them in an attribute, see Section 6,“Design of the Rudiment class” (p. 6).

The p1 and p2 arguments become those two named points, but the keyword arguments kw go in twodirections. The fill= value, specifying the line color, is routed into the keyword arguments to the baseclass constructor. The width dimension, however, is subject to being transformed, so it is representedas a point StraightRudiment.PT_W, whose distance from point PT_A defines the width.

tkscene.py

#-- 1 --c = Cardinals ( ** { StraightRudiment.PT_A: p1,

StraightRudiment.PT_B: p2 } )

See Section 13.9, “Rudiment.filterOptions(): Set up Tkinter option values” (p. 26).tkscene.py

#-- 2 --# [ aux := a new dictionary containing the entries from# self.DEFAULT_OPTIONS whose values are not None,# updated by the entries from kw whose keys are in# self.DEFAULT_OPTIONS ]aux = Rudiment.filterOptions ( tagList, kw,

StraightRudiment.DEFAULT_OPTIONS )

See Section 13.7, “Rudiment.setWidth(): Store the width value” (p. 25).tkscene.py

#-- 3 --# [ if kw has a WIDTH_OPTION key -># c := c with a point named Rudiment.PT_W defined# at a distance w from p1 in the +x direction# else -> I ]Rudiment.setWidth ( c, p1, kw )

#-- 3 --return StraightRudiment(tagList, c, **aux)

29tkscene: A scene geometry managerNew Mexico Tech Computer Center

Page 30: tkscene: A scene geometry manager for Tkinter A scene geometry manager for Tkinter John W. Shipman 2011-09-14 18:56 Abstract A Python-language module that manages the …

14.3. StraightRudiment.tkDraw(): Render ittkscene.py

# - - - S t r a i g h t R u d i m e n t . t k D r a w

def tkDraw(self, can):'''Render self onto a canvas.'''#-- 1 --# [ can := can - (elements whose oids are in self.__oidList)# self.__oidList := a new, empty list ]self.erase(can)

Straight lines are rendered using Tkinter's Canvas.create_line() method.

Keyword options to .create_line() come from the base class's .aux dictionary, including tags,but there is one we must add at rendering time: the line width. If a width was passed to our constructor,it was stored as two points named PT_ORIG and PT_W; if self.p does not contain a point named PT_W,we'll use the default width: one pixel.

tkscene.py

#-- 2 --a,b = self.p[self.PT_A], self.p[self.PT_B]kw = dict(self.aux)

#-- 3 --# [ if self has a point named self.PT_W -># kw[WIDTH_OPTION] := the distance between (a) and# that point, but not less than 1# else -> I ]self.getWidth(kw, a)try:

if kw[WIDTH_OPTION] is None:del kw[WIDTH_OPTION]

except KeyError:pass

See Section 21, “pointsTuple(): Build a Tkinter points tuple” (p. 50) for the function that convertsthe endpoints into a tuple (x0, y0, x1, y1, …)

tkscene.py

#-- 4 --# [ can := can + (a new line segment with endpoints (a)# and (b) and options kw)# self.__oidList +:= object ID of that line ]points = pointsTuple ( [a, b] )oid = self.addCan ( can.create_line ( *points, **kw ) )

15. class BoxRudiment: Rectangle rudimentThis graphic primitive describes a rectangle of arbitrary size, location, and rotation angle.

Because it is initially unrotated, a rectangle can be specified by two points at opposite ends of eithermain diagonal. However, because a rudiment may be rotated, internally we store all four corners ascardinal points, named after the directions northeast, northwest, southwest, and southeast.

New Mexico Tech Computer Centertkscene: A scene geometry manager30

Page 31: tkscene: A scene geometry manager for Tkinter A scene geometry manager for Tkinter John W. Shipman 2011-09-14 18:56 Abstract A Python-language module that manages the …

Figure 2. Geometry of a box rudiment

PT_NE

PT_SEPT_SW

PT_NW

width

PT_W

fill color

outline color

Also, the border width that comes in as a keyword argument width=W is not stored in the self.auxdictionary, for reasons discussed in Section 6, “Design of the Rudiment class” (p. 6). Instead, we storea point named self.PT_W that is a distance W from PT_SW; the distance between these two points willremain in proportion through scaling transformations. If no width is specified, point self.PT_W remainsunset; the default border will be rendered one pixel wide. (PT_W comes from Section 13, “classRudiment: Scene display component” (p. 21).)

The other keyword arguments are stored in the inherited .aux dictionary: outline for the color ofthe border, and fill for the color of the interior, if any.

The .make() static method accepts arguments in a convenient form, but the actual constructor musthave the exact same argument list as the constructor for Rudiment(), for reasons discussed in Section 6,“Design of the Rudiment class” (p. 6).

tkscene.py

# - - - - - c l a s s B o x R u d i m e n t

class BoxRudiment(Rudiment):'''Represents a rectangle with arbitrary position and rotation.

Exports:BoxRudiment(tagList, c, *aux):[ arguments are as in Rudiment() ->

return a new BoxRudiment instance with those values ]BoxRudiment.make(tagList, p1, p2, fill='', outline='black',

width=None): # Static method[ (tagList is a list of tags to be applied to Tkinter canvasobjects) and(p1 and p2 are opposite corners of a rectangle inscene space, as Pt instances) and(fill is the interior color of the rendered box,as a Tkinter color, with '' meaning transparent) and(outline is the border color of the box, defaulting toblack) and(width is the border width of the box, with Noneinterpreted as 1 pixel, and 0 meaning no border) ->return a new BoxRudiment representing those values ]

.PT_NE: [ name of the original upper right corner ]

.PT_NW, .PT_SW, .PT_SE: [ other original corners ]

31tkscene: A scene geometry managerNew Mexico Tech Computer Center

Page 32: tkscene: A scene geometry manager for Tkinter A scene geometry manager for Tkinter John W. Shipman 2011-09-14 18:56 Abstract A Python-language module that manages the …

'''PT_NE, PT_NW, PT_SW, PT_SE = 'NE', 'NW', 'SW', 'SE'VERTICES = (PT_NE, PT_NW, PT_SW, PT_SE)DEFAULT_OPTIONS = { FILL_OPTION: '', OUTLINE_OPTION: 'black' }

15.1. BoxRudiment.__init__()tkscene.py

# - - - B o x R u d i m e n t . _ _ i n i t _ _

def __init__ ( self, tagList, c, **aux ):'''Constructor.'''Rudiment.__init__(self, tagList, c, **aux)

15.2. BoxRudiment.make()tkscene.py

# - - - B o x R u d i m e n t . m a k e

@staticmethoddef make ( tagList, p1, p2, **kw ):

'''Factory method.'''

The coordinates p1 and p2 are always in standard position, meaning that the box's sides are parallel tothe x and y axes. We use the Box() constructor to standardize the point positions so that our localcorners will be the lower left and upper right.

tkscene.py

#-- 1 --# [ baseBox := a Box whose corners are p1 and p2 ]baseBox = Box(p1, p2)

Next we build the Cardinals instance where the cardinal points of the figure are stored. Refer to thefigure in Section 15, “class BoxRudiment: Rectangle rudiment” (p. 30) for the geometry.

tkscene.py

#-- 2 --# [ c := a Cardinals object with points PT_SW=baseBox.cMin,# PT_NE=baseBox.cMax, PT_NW=upper left corner of that# box, and PT_SE=lower right corner of that box ]nw = Pt ( baseBox.cMin.x(), baseBox.cMax.y() )se = Pt ( baseBox.cMax.x(), baseBox.cMin.y() )c = Cardinals ( ** {

BoxRudiment.PT_NE: baseBox.cMax,BoxRudiment.PT_NW: nw,BoxRudiment.PT_SW: baseBox.cMin,BoxRudiment.PT_SE: se } )

#-- 3 --# [ if kw has a WIDTH_OPTION key -># c := c with a point named Rudiment.PT_W defined# at a distance kw[WIDTH_OPTION] from

New Mexico Tech Computer Centertkscene: A scene geometry manager32

Page 33: tkscene: A scene geometry manager for Tkinter A scene geometry manager for Tkinter John W. Shipman 2011-09-14 18:56 Abstract A Python-language module that manages the …

# baseBox.cMin in the +x direction# else -> I ]Rudiment.setWidth ( c, baseBox.cMin, kw )

Our intended function doesn't specify what to do with options other than the expected two, outlineand fill. We'll just silently ignore them. The expected ones get passed on for the .aux attribute. Alsoadded to this dictionary is a tags value that comes from tagList.

tkscene.py

#-- 4 --# [ aux := a new dictionary containing the entries from# self.DEFAULT_OPTIONS whose values are not None,# updated by the entries from kw whose keys are in# self.DEFAULT_OPTIONS ]aux = Rudiment.filterOptions(tagList, kw,

BoxRudiment.DEFAULT_OPTIONS)

After all that, we're ready to call the base class constructor.tkscene.py

#-- 5 --return BoxRudiment(tagList, c, **aux)

15.3. BoxRudiment.tkDraw()tkscene.py

# - - - B o x R u d i m e n t . t k D r a w

def tkDraw(self, can):'''Render self onto a canvas.'''

We can't use Tkinter's Canvas.create_rectangle() primitive, because that can only draw boxeswhose sides are parallel to the coordinate axes. So we'll just call it a polygon. The Canvas.create_poly-gon() method defines a polygon by its vertices—four of them, in this case.

tkscene.py

#-- 1 --# [ can := can - (all the objects whose object IDs are# in self.__oidList) ]self.erase(can)

#-- 2 --# [ cornerTuple := list of the four corner points as# (x0, y0, x1, y1, ...)# p0 := point self.PT_SW# kw := copy of self.aux ]cornerTuple = pointsTuple ( [ self.p[key]

for key in self.VERTICES ] )p0 = self.p[self.PT_SW]kw = dict(self.aux)

Next we have to set up the keyword attributes.

33tkscene: A scene geometry managerNew Mexico Tech Computer Center

Page 34: tkscene: A scene geometry manager for Tkinter A scene geometry manager for Tkinter John W. Shipman 2011-09-14 18:56 Abstract A Python-language module that manages the …

• The fill and outline attributes hav the same meanings in Tkinter: interior color (default '',meaning transparent) and border color.

• The width is the border width. If the cardinal point self.PT_W is defined, the distance from thatpoint to self.PT_SW specifies the border width. The default value is one pixel. See Rudiment-getWidth.

tkscene.py

#-- 3 --# [ if self has a point named Rudiment.PT_W -># kw[WIDTH_OPTION] := the distance from p0 to that# point, but not less than 1 ]self.getWidth ( kw, p0 )

#-- 4 --# [ can := can with a new canvas polygon element added, with# corners given by (cornerTuple) and options (kw)# self.__oidList +:= that element ]oid = self.addCan ( can.create_polygon ( *cornerTuple, **kw ) )

16. class OvalRudiment: EllipseWhat Tkinter calls an oval is actually an ellipse. Geometrically, an oval is defined by two points on op-posite corners of a bounding box whose sides are parallel to the coordinate axes.

Although the bounding boxes of Tkinter's canvas oval objects are always oriented in this way, withtheir axes parallel to the coordinate system, we can support arbitrary rotation of ellipses by abusing thecanvas polygon widget. If you render a polygon that happens to be a rectangle, in any orientation, anduse the smooth=1 option, the result is an ellipse that fits that bounding box.

The names of the four points of the bounding box are the same as in Section 15, “class BoxRudiment:Rectangle rudiment” (p. 30): PT_SW for the southwest corner, PT_NW for the northeast corner, and soforth.

tkscene.py

# - - - - - c l a s s O v a l R u d i m e n t

class OvalRudiment(Rudiment):'''Ellipse rudiment.

Exports:OvalRudiment(tagList, cardinals, **kw):[ arguments are as in Rudiment() ->

return a new OvalRudiment instance with those values ]OvalRudiment.make(tagList, p1, p2, fill='', outline='black',

width=None):[ (tagList is a list of tags to be applied to Tkinter canvasobjects) and(p1 and p2 are opposite corners of the bounding box of anellipse whose axes are parallel to the x- and y-axes) and(fill is the interior color of the rendered ellipse, with'' meaning transparent) and(outline is the border color of the ellipse, defaultingto black) and

New Mexico Tech Computer Centertkscene: A scene geometry manager34

Page 35: tkscene: A scene geometry manager for Tkinter A scene geometry manager for Tkinter John W. Shipman 2011-09-14 18:56 Abstract A Python-language module that manages the …

(width is the border width of the box, with Noneinterpreted as 1 pixel, and 0 meaning no border) ->return a new OvalRudiment with those values ]

.PT_NE, .PT_SE, .PT_SW, .PT_NW:[ names of the corner points of the bounding box ]

.VERTICES:[ sequence of the corner point names in order ]

.FUZZ:[ distances less than this amount are considered to beclose enough in .tkDraw() ]

'''PT_NE, PT_SE, PT_SW, PT_NW = 'NE', 'SE', 'SW', 'NW'VERTICES = (PT_NE, PT_SE, PT_SW, PT_NW)DEFAULT_OPTIONS = { FILL_OPTION: '', OUTLINE_OPTION: 'black' }FUZZ = 1.5

16.1. OvalRudiment.__init__()A straight pass-through; all derived class constructors must be identical to that of the base class, as ex-plained in Section 6, “Design of the Rudiment class” (p. 6).

tkscene.py

# - - - O v a l R u d i m e n t . _ _ i n i t _ _

def __init__(self, tagList, c, **aux):'''Constructor'''Rudiment.__init__(self, tagList, c, **aux)

16.2. OvalRudiment.make()Ellipses start out parallel to the coordinate axes, so we need only two points to define them. However,in order to support arbitrary rotation, we store all four vertices of the bounding box. See Section 20,“class Box: Basic rectangle” (p. 48).

tkscene.py

# - - - O v a l R u d i m e n t . m a k e

@staticmethoddef make(tagList, p1, p2, **kw):

'''Oval factory.'''#-- 1 --# [ box := a new Box instance whose main diagonal is# defined by points p1 and p2# nw := northwest corner of that box# se := southeast corner of that boxbox = Box ( p1, p2 )nw = Pt ( box.cMin.x(), box.cMax.y() )se = Pt ( box.cMax.x(), box.cMin.y() )

See Section 19, “class Cardinals: A transformable collection of named points” (p. 47).

35tkscene: A scene geometry managerNew Mexico Tech Computer Center

Page 36: tkscene: A scene geometry manager for Tkinter A scene geometry manager for Tkinter John W. Shipman 2011-09-14 18:56 Abstract A Python-language module that manages the …

tkscene.py

#-- 2 --c = Cardinals ( ** {

OvalRudiment.PT_NE: box.cMax,OvalRudiment.PT_SE: se,OvalRudiment.PT_SW: box.cMin,OvalRudiment.PT_NW: nw } )

Next we check for a width option. See Section 13.7, “Rudiment.setWidth(): Store the widthvalue” (p. 25).

tkscene.py

#-- 3 --# [ if kw has a WIDTH_OPTION key -># c := c with a point named Rudiment.PT_W defined# at a distance kw[WIDTH_OPTION] from# box.cMin in the +x direction# else -> I ]Rudiment.setWidth ( c, box.cMin, kw )

Next we copy the user's options to a new dictionary aux, omitting any we don't expect (anything exceptfill and outline), and add a tags option.

tkscene.py

#-- 4 --# [ aux := a new dictionary containing the entries from# self.DEFAULT_OPTIONS whose values are not None,# updated by the entries from kw whose keys are in# self.DEFAULT_OPTIONS ]aux = Rudiment.filterOptions(tagList, kw,

OvalRudiment.DEFAULT_OPTIONS )

#-- 5 --return OvalRudiment(tagList, c, **aux)

16.3. OvalRudiment.tkDraw()There are three different rendering cases.

1. If the bounding box is parallel to the coordinate axes, we can use Tkinter's canvas oval object.

In practice, if either the Δx or the Δy is less than 1.5 (pixels), the bounding box is parallel or closeenough.

2. If the bounding box is not parallel to the coordinate axes, and it is not square, then we can't use Tk-inter's canvas oval object, because it does not support arbitrary rotation of ellipses. Instead, we usethe canvas polygon object with a smooth=1 option, which fits a closed spline into the box. This isnot perfect but at least looks close for most cases.

In practice, if the difference between the lengths of the first two sides of the bounding box is greaterthan 1.5, we use the smoothed polygon substitution.

3. If the bounding box is a square, the desired rendering is a circle. However, if we use the smoothedpolygon trick, the result looks distinctly uncircular. In this case, since the rotation doesn't matter, wecalculate the circle's center as the midpoint of two vertices opposite each other on the main diagonal,calculate the radius as half that, and construct a new, square bounding box parallel to the coordinateaxes and use the Tkinter canvas oval object to render the circle.

New Mexico Tech Computer Centertkscene: A scene geometry manager36

Page 37: tkscene: A scene geometry manager for Tkinter A scene geometry manager for Tkinter John W. Shipman 2011-09-14 18:56 Abstract A Python-language module that manages the …

tkscene.py

# - - - O v a l R u d i m e n t . t k D r a w

def tkDraw ( self, can ):'''Render an oval on a Tkinter canvas.'''#-- 1 --# [ vList := the four vertices of the bounding box in# clockwise order# a, b, c, d := those same four vertices# sw := self.p[self.PT_SW]# deltaP := difference between the first two vertices# deltaL := (distance from first vertex to second) -# (distance from second vertex to third) ]a, b, c, d = vList = [ self.p[vName]

for vName in self.VERTICES ]sw = self.p[self.PT_SW]deltaP = a - bdeltaL = abs ( a.dist(b) - b.dist(c) )

When we use Tkinter's oval object, see Section 16.4, “OvalRudiment.__drawOval()” (p. 39). Forthe polygon case, see Section 16.5, “OvalRudiment.__drawPolygon()” (p. 39). The class constantself.FUZZ is used to test whether two points are close enough.

tkscene.py

#-- 2 --# [ if (abs(deltaP.x()) < self.FUZZ) or# (abs(deltaP.y()) < self.FUZZ) -># can := can - (display elements pertaining to self) +# (a new canvas oval with main diagonal from a to c,# and attributes from self.aux and (sw)# else if (deltaL < self.FUZZ) -># can := can - (display elements pertaining to self) +# (a new canvas oval whose center is the midpoint# between points a and c and whose radius is half# the distance from a to c, and attributes from# self.aux and (sw))# else -># can := can - (display elements pertaining to self) +# (a new, smoothed canvas polygon with vertices# (vList), and attributes from self.aux and (sw)) ]if ( ( abs(deltaP.x()) < self.FUZZ ) or

( abs(deltaP.y()) < self.FUZZ ) ):points = pointsTuple ( [a, c] )self.__drawOval ( can, points, sw )

elif deltaL < self.FUZZ:center = Pt ( (a.x() + c.x())/2.0,

(a.y() + c.y())/2.0 )radius = a.dist(center)points = pointsTuple (

[ Pt ( center.x()-radius, center.y()-radius ),Pt ( center.x()+radius, center.y()+radius ) ] )

self.__drawOval ( can, points, sw )else:

37tkscene: A scene geometry managerNew Mexico Tech Computer Center

Page 38: tkscene: A scene geometry manager for Tkinter A scene geometry manager for Tkinter John W. Shipman 2011-09-14 18:56 Abstract A Python-language module that manages the …

points = pointsTuple ( vList )self.__drawPolygon ( can, points, sw )

tkscene.py

#-- 1 --# [ sw := the original southwest corner of the bounding box# as a homcoord.Pt# points := the vertices of the bounding box as a Tkinter# points tuple# kw := a copy of self.aux ]sw = self.p[self.PT_SW]points = pointsTuple ( [ self.p[vName]

for vName in self.VERTICES ] )kw = dict(self.aux)

See Section 13.8, “Rudiment.getWidth(): Set up Tkinter's border width” (p. 25) for the logic thatsets up the border width.

tkscene.py

#-- 2 --# [ if self has a point named Rudiment.PT_W -># kw[WIDTH_OPTION] := the distance from (sw) to that# point, but not less than 1 ]self.getWidth ( kw, sw )

#-- 3 --

For the management of canvas object IDs, see Section 13.4, “Rudiment.addCan(): Add the oid of acanvas object” (p. 24).

tkscene.py

#-- 4 --# [ if (the sides are not parallel to the axes) and# (the lengths of the sides differ by more than one pixel) -># self := self with a new canvas polygon added with# points (points) and options (kw)# else -># self := self with a new circular oval added whose# center is the center of self.VERTICES and# whose diameter is the length of a side ]p1, p2, p3 = [ self.p[self.VERTICES[i]]

for i in range(3) ]side1 = p1.dist(p2)side2 = p2.dist(p3)diff = abs(side1 - side2)if diff > 1.5:

kw[SMOOTH_OPTION] = '1'oid = self.addCan ( can.create_polygon ( *points, **kw ) )

else:radius = side1 / 2.0center = Pt ( (p1.x()+p3.x())/2.0,

(p1.y()+p3.y())/2.0 )points = pointsTuple (

[ Pt ( center.x()-radius, center.y()-radius ),

New Mexico Tech Computer Centertkscene: A scene geometry manager38

Page 39: tkscene: A scene geometry manager for Tkinter A scene geometry manager for Tkinter John W. Shipman 2011-09-14 18:56 Abstract A Python-language module that manages the …

Pt ( center.x()+radius, center.y()+radius ) ] )oid = self.addCan ( can.create_oval ( *points, **kw ) )

16.4. OvalRudiment.__drawOval()This method renders the cases that fit Tkinter's canvas oval object.

The widthRef argument is the point whose distance from point PT_W defines the border width.tkscene.py

# - - - O v a l R u d i m e n t . _ _ d r a w O v a l

def __drawOval ( self, can, points, widthRef ):'''Render this rudiment using Tkinter's canvas oval.

[ (can is a Tkinter.Canvas) and(points is the diagonal of the bounding box as a tuple(x0, y0, x1, y1)) and(widthRef is the point relative to which PT_W definesthe border width) ->can := can - (display elements pertaining to self) +

(a new canvas oval with bounding box (points) andattributes from self.aux and widthRef) ]

'''#-- 1 --# [ kw := a copy of self.aux ]kw = dict(self.aux)

See Section 13.8, “Rudiment.getWidth(): Set up Tkinter's border width” (p. 25) for the logic thatsets up the border width.

tkscene.py

#-- 2 --# [ if self has a point named Rudiment.PT_W -># kw[WIDTH_OPTION] := the distance from (widthRef)# to that point, but not less than 1 ]self.getWidth ( kw, widthRef )

#-- 3 --# [ self := self with a new canvas oval added with# points (points) and attributes (kw) ]oid = self.addCan ( can.create_oval(*points, **kw) )

16.5. OvalRudiment.__drawPolygon()This method implements the substitute rendering that must be used when the oval is not circular andwhen its bounding box is not parallel to the coordinate axes. See Section 16.3, “OvalRudiment.tk-Draw()” (p. 36) for further discussion, and compare Section 16.4, “OvalRudiment.__drawOv-al()” (p. 39) for the setup of the kw dictionary.

tkscene.py

# - - - O v a l R u d i m e n t . _ _ d r a w P o l y g o n

def __drawPolygon ( self, can, points, ref ):

39tkscene: A scene geometry managerNew Mexico Tech Computer Center

Page 40: tkscene: A scene geometry manager for Tkinter A scene geometry manager for Tkinter John W. Shipman 2011-09-14 18:56 Abstract A Python-language module that manages the …

'''Render a rotated ellipse as a smoothed polygon.

[ (can is a Tkinter.Canvas) and(points are the four vertices of a bounding box as aTkinter points tuple) and(widthRef is the point relative to which PT_W definesthe border width) ->can := can - (display elements pertaining to self) +

(a new, smoothed canvas polygon with bounding box(points) and attributes from self.aux and widthRef) ]

'''#-- 1 --# [ kw := a copy of self.aux ]kw = dict(self.aux)

#-- 2 --# [ if self has a point named Rudiment.PT_W -># kw[WIDTH_OPTION] := the distance from (ref) to that# point, but not less than 1 ]self.getWidth ( kw, ref )

A minor Python point about the call to create_polygon: if one provides both explicit keyword argu-ments, and a prefabricated set of keywords using the “**” convention, the method receives a dictionarycontaining both, so long as no key occurs in both places.

tkscene.py

#-- 3 --# [ self := self with a new smoothed canvas added with# points (points) and attributes (kw) ]oid = self.addCan ( can.create_polygon ( *points,

smooth=1, **kw ) )

17. class ArcRudiment: Circular arcThis rudiment is used for drawing pieces of circles. An arc is initially specified by three points: thecenter of the base circle, the point where the arc begins, and the point where the arc ends.

WarningIt is possible to apply a transform to a circular arc that produces a conic section that cannot be renderedas a Tkinter Canvas arc. This class does not guarantee correct rendering for segments of rotated ellipses,which could result from a scaling transform with different values for the x and y scale factors, followedby a rotation transform.

You may transform this rudiment all you want, but when it comes time to render, the center will be atPT_C, the radius and initial angle will be determined by PT_A, and the final angle will be determinedby PT_B.

Here is the initial geometry of the cardinal points.

New Mexico Tech Computer Centertkscene: A scene geometry manager40

Page 41: tkscene: A scene geometry manager for Tkinter A scene geometry manager for Tkinter John W. Shipman 2011-09-14 18:56 Abstract A Python-language module that manages the …

Figure 3. Geometry of an arc rudiment

PT_B

PT_A

PT_CPT_W

width

tkscene.py

# - - - - - c l a s s A r c R u d i m e n t

class ArcRudiment(Rudiment):'''Represents a circular arc.

Exports:ArcRudiment(tagList, c, **kw):[ arguments are as in Rudiment() ->

return a new ArcRudiment with those values ]ArcRudiment.make(tagList, center, fromPoint, toPoint,

outline='black', width=None): # Static method[ (tagList is a sequence of tags to apply to elements) and(center is the arc's center in scene space as a Pt) and(fromPoint is the arc's start point as a Pt) and(toPoint is the arc's end point (counterclockwise) asa Pt) and(outline is the arc's color as a Tkinter color) and(width is the arc's width, defaulting to 1 pixel) ->return a new ArcRudiment representing those values ]

PT_A: [ name of the arc's start point in self.p ]PT_B: [ name of the arc's start point in self.p ]PT_C: [ name of the arc's center point in self.p ]

State/Invariants:.__corners:[ a tuple (x0, y0, x1, y1) where (x0, y0) is the upperleft corner of the circle's bounding box and (x1, y1)is the lower right corner ]

.__options:[ dictionary of Canvas.create_arc() options for self ]

'''PT_A = 'A'; PT_B = 'B'; PT_C = 'C'DEFAULT_OPTIONS = { OUTLINE_OPTION: 'black' }

The mathematically astute reader may note the assumption that points PT_A and PT_B are both at thesame distance from the center point PT_C. In practice, we will take the distance from PT_C to PT_A as

41tkscene: A scene geometry managerNew Mexico Tech Computer Center

Page 42: tkscene: A scene geometry manager for Tkinter A scene geometry manager for Tkinter John W. Shipman 2011-09-14 18:56 Abstract A Python-language module that manages the …

the radius. The angles will be defined as the angles of rays from the center to the two endpoints, sopoint PT_B will serve only to define the ending angle of the arc. Thus, if in practice the radii to the twopoints differ slightly, the graphics rendering should still be fairly close.

17.1. ArcRudiment.__init__()tkscene.py

# - - - A r c R u d i m e n t . _ _ i n i t _ _

def __init__ ( self, tagList, c, **aux ):'''Constructor.'''Rudiment.__init__(self, tagList, c, **aux)

17.2. ArcRudiment.make(): Circular arctkscene.py

# - - - A r c R u d i m e n t . _ _ i n i t _ _

@staticmethoddef make ( tagList, center, fromPoint, toPoint, **kw):

'''Factory method.'''

First instantiate the Cardinals instance to hold the points, and populate it with the values we knownow. Then check for a width keyword argument and, if it is found, use that width to define the positionof point PT_W.

tkscene.py

#-- 1 --c = Cardinals ( { ArcRudiment.PT_C: center,

ArcRudiment.PT_A: fromPoint,ArcRudiment.PT_B: toPoint } )

#-- 2 --# [ if kw has a WIDTH_OPTION key -># c := c with a point named Rudiment.PT_W# defined at a distance kw[WIDTH_OPTION] from# center in the +x direction# else -> I ]Rudiment.setWidth(c, center, kw)

The aux dictionary holds the outline keyword argument that specifies the color of the arc.tkscene.py

#-- 3 --# [ aux := a new dictionary containing the entries from# self.DEFAULT_OPTIONS whose values are not None,# updated by the entries from kw whose keys are in# self.DEFAULT_OPTIONS ]aux = Rudiment.filterOptions(tagList, kw,

ArcRudiment.DEFAULT_OPTIONS )

New Mexico Tech Computer Centertkscene: A scene geometry manager42

Page 43: tkscene: A scene geometry manager for Tkinter A scene geometry manager for Tkinter John W. Shipman 2011-09-14 18:56 Abstract A Python-language module that manages the …

#-- 4 --return ArcRudiment(tagList, c, **aux)

17.3. ArcRudiment.tkDraw()tkscene.py

# - - - A r c R u d i m e n t . t k D r a w

def tkDraw(self, can):'''Render self on a canvas.'''#-- 1 --# [ can := can - (elements pertaining to self) ]self.erase(can)

To draw an arc in the Tkinter world, first define the bounding box of the circle by two endpoints of itsmain diagonal. We'll use as endpoints (center-radius, center-radius) and (center+radius,center+radius).

tkscene.py

#-- 2 --# [ c := point PT_C# a := point PT_A# b := point PT_B# cx := x coordinate of PT_C# cy := y coordinate of PT_C# r := distance between points PT_C and PT_A ]c = self.p[self.PT_C]a = self.p[self.PT_A]b = self.p[self.PT_B]cx, cy = c.xy()r = c.dist ( self.p[self.PT_A] )

#-- 3 --# [ points := a tuple (c_x-r, c_y-r, c_x+r, c_y+r) ]points = pointsTuple ( [ Pt(cx-r, cy-r),

Pt(cx+r, cy+r) ] )

Tkinter wants as its start argument the initial bearing, and as its extent argument the difference inbearings, both in degrees. One subtlety: since we are by definition in display coordinates, and +y is theopposite direction, the angles and extent values must be negated.

tkscene.py

#-- 4 --# [ start := Cartesian angle from (cx, cy) to PT_A in degrees# extent := (Cartesian angle from (cx, cy) to PT_B in# degrees) - angle from (cx, cy) to PT_A ]start = - degrees ( c.bearing(a) )finish = - degrees ( c.bearing(b) )extent = finish - start

Next we assemble as a dictionary kw all the keyword options to the Canvas.create_arc() method.If there is a width reference point PT_W, we add a width option to that dictionary.

43tkscene: A scene geometry managerNew Mexico Tech Computer Center

Page 44: tkscene: A scene geometry manager for Tkinter A scene geometry manager for Tkinter John W. Shipman 2011-09-14 18:56 Abstract A Python-language module that manages the …

tkscene.py

#-- 5 --# [ kw := a new dictionary with options copied from self.aux# plus start=(start), extent=(extent), and style=ARC ]kw = dict(self.aux)kw.update ( { 'start': start, 'extent': extent,

'style': ARC } )

#-- 6 --# [ if self has a point named Rudiment.PT_W -># kw[WIDTH_OPTION] := the distance from (c) to that# point, but not less than 1 ]self.getWidth ( kw, c )

#-- 7 --# [ can := can + (a new arc with bounding box described by# (points) with options (kw) ]oid = self.addCan ( can.create_arc ( *points, **kw ) )

18. class PolygonRudiment: Arbitrary polygonsTkinter canvas polygons are defined by a sequence of three or more vertices. The naming conventionfor points is 'P_' followed by the vertex number starting with zero. The number of digits in the vertexnumber is determined by the number of vertices. For example, if there are between 100 and 999 vertices,they will be numbered 'P_000', 'P_001', ….

There is one slightly inelegant feature of this class. The number of vertices is stored in the .aux dictionaryunder the key whose name is NV. The other entries in the .aux dictionary are passed to the canvas objectcreation method, but self.aux[self.NV] does not get passed on, since it is internal to this class.

tkscene.py

# - - - - - c l a s s P o l y g o n R u d i m e n t

class PolygonRudiment(Rudiment):'''Arbitrary polygon rudiment.

Exports:PolygonRudiment(tagList, c, **kw):[ arguments are as in Rudiment() ->

return a new PolygonRudiment with those values ]PolygonRudiment.make(tagList, vertices, fill='',

outline='black', width=None):[ (tagList is a sequence of tags to apply to elements) and(vertices is a sequence of vertices as homcoord.Ptinstances) and(fill is the interior color as a Tkinter color,with "" meaning transparent) and(outline is the border color as a Tkinter color) and(width is the border width, defaulting to 1 pixel) ->return a new PolygonRudiment representing those values ]

.PT_PREFIX:[ the prefix for all vertex point names ]

New Mexico Tech Computer Centertkscene: A scene geometry manager44

Page 45: tkscene: A scene geometry manager for Tkinter A scene geometry manager for Tkinter John W. Shipman 2011-09-14 18:56 Abstract A Python-language module that manages the …

.NV:[ key in self.aux under which the vertex count is stored ]

'''PT_PREFIX = 'P_'NV = 'NV'DEFAULT_OPTIONS = { FILL_OPTION: '', OUTLINE_OPTION: 'black' }

18.1. PolygonRudiment.__init__()A pro-forma pass-through constructor; see Section 6, “Design of the Rudiment class” (p. 6).

tkscene.py

# - - - P o l y g o n R u d i m e n t . _ _ i n i t _ _

def __init__(self, tagList, c, **aux ):'''Constructor.'''Rudiment.__init__(self, tagList, c, **aux)

18.2. PolygonRudiment.make()The first step is to see how many digits we need to use for the names of the vertices.

tkscene.py

# - - - P o l y g o n R u d i m e n t . m a k e

@staticmethoddef make ( tagList, vertices, **kw ):

'''PolygonRudiment factory.'''#-- 1 --# [ nVertices := number of elements in vertices# nDigits := number of digits in the number of elements in# vertices# c := a new, empty Cardinals instance ]nVertices = len(vertices)nDigits = len(str(nVertices))

Next we instant convert the vertices into named points in the Cardinals instance. For the logic thattranslates a vertex number to a vertex name, see Section 18.4, “PolygonRudiment.vertexNo(): Pointname for a numbered vertex” (p. 47).

tkscene.py

#-- 2 --# [ c +:= entries whose keys are the names of the points in# (vertices) and each related value is that point ]for i in len(vertices):

#-- 2 body --# [ c +:= an entry whose key is the name of the (i)th# vertex, and the related value is that vertex ]c[PolygonRudiment.vertexNo(i)] = vertices[i]

The reference point for the border width is the first vertex. See Section 13.7, “Rudiment.setWidth():Store the width value” (p. 25).

45tkscene: A scene geometry managerNew Mexico Tech Computer Center

Page 46: tkscene: A scene geometry manager for Tkinter A scene geometry manager for Tkinter John W. Shipman 2011-09-14 18:56 Abstract A Python-language module that manages the …

tkscene.py

#-- 2 --# [ if kw has a WIDTH_OPTION key -># c := c with a point named Rudiment.PT_W# defined at a distance kw[WIDTH_OPTION] from# center in the +x direction# else -> I ]Rudiment.setWidth(c, vertices[0], kw)

Next, copy the interior and outline color options to a new dictionary if they are provided, and call theactual constructor.

tkscene.py

#-- 3 --# [ aux := a new dictionary containing the entries from# self.DEFAULT_OPTIONS whose values are not None,# updated by the entries from kw whose keys are in# self.DEFAULT_OPTIONS ]aux = Rudiment.filterOptions(tagList, kw, self.DEFAULT_OPTIONS )

Here is the inelegant part: passing the vertex count through the aux attribute, which is really intendedfor Tkinter options. But there's no easy way to discover the number of vertices by examining the namesof points in the Cardinals instance. Alternative: we could use a ridiculous length for the sequencenumbers (P_123456789) and just pull out points in order until we get a failure.

tkscene.py

#-- 4 --aux[PolygonRudiment.NV] = nVertices

#-- 5 --return PolygonRudiment(tagList, c, **aux)

18.3. PolygonRudiment.tkDraw()As discussed in Section 18.2, “PolygonRudiment.make()” (p. 45), the number of vertices is passedas an entry in the self.aux dictionary using key self.NV. We remove that entry from the optiondictionary passed to Canvas.create_polygon().

tkscene.py

# - - - P o l y g o n R u d i m e n t . t k D r a w

def tkDraw ( self, can ):'''Render self onto a canvas.'''#-- 1 --# [ nVertices := self.aux[self.NV]# nDigits := number of digits in self.aux[self.NV]# kw := a copy of self.aux without the entry for self.NV# v0 := the first vertex from self ]nVertices = self.aux[self.NV]nDigits = len(str(nVertices))kw = dict(self.aux)del kw[self.NV]v0 = self.p[self.vertexNo(nDigits, 0)]

New Mexico Tech Computer Centertkscene: A scene geometry manager46

Page 47: tkscene: A scene geometry manager for Tkinter A scene geometry manager for Tkinter John W. Shipman 2011-09-14 18:56 Abstract A Python-language module that manages the …

For the function that converts a sequence of homcoord.Pt instances to a Tkinter points tuple, see Sec-tion 21, “pointsTuple(): Build a Tkinter points tuple” (p. 50).

tkscene.py

#-- 2 --# [ points := a Tkinter points tuple containing the vertices# in self number 0, 1, 2, .., (nVertices-1) ]points = pointsTuple ( [ self.p[self.vertexNo(nDigits, i)]

for i in range(0, nVertices) ] )

If a width was specified, point Rudiment.PT_W specifies the width relative to the first vertex. SeeSection 13.8, “Rudiment.getWidth(): Set up Tkinter's border width” (p. 25).

tkscene.py

#-- 3 --# [ if self has a point named Rudiment.PT_W -># kw[WIDTH_OPTION] := the distance from (v0) to that# point, but not less than 1 ]self.getWidth ( kw, v0 )

#-- 4 --# [ can := can + (a new polygon with vertices described by# (points) with options (kw) ]oid = self.addCan ( can.create_polygon ( *points, **kw ) )

18.4.PolygonRudiment.vertexNo(): Point name for a numbered vertexThe vertices of a polygon are given names consisting of the prefix PolygonRudiment.PT_PREFIXfollowed by the vertex's number, starting from zero, and using just enough digits for the total vertexcount. This method translates a vertex number to a vertex name for storage or retrieval in the Cardinalsinstance.

tkscene.py

# - - - P o l y g o n R u d i m e n t . v e r t e x N o

@staticmethoddef vertexNo(nDigits, k):

'''Return the name of the (k)th vertex.'''return ( "%s%0*d" %

(PolygonRudiment.PT_PREFIX, nDigits, k) )

19.class Cardinals: A transformable collection of namedpoints

An instance of this class is basically a container for a set of named points. Each point is an instance ofhomcoord.Pt. For a discussion of why this class is needed, see Section 6, “Design of the Rudimentclass” (p. 6).

The class is basically a dictionary; in fact, it inherits from dict. It adds a method that returns a newinstance with all the same names but after each point has been run through a transform.

47tkscene: A scene geometry managerNew Mexico Tech Computer Center

Page 48: tkscene: A scene geometry manager for Tkinter A scene geometry manager for Tkinter John W. Shipman 2011-09-14 18:56 Abstract A Python-language module that manages the …

tkscene.py

# - - - - - c l a s s C a r d i n a l s

class Cardinals(dict):'''For storing a set of named cardinal points.

Exports:Cardinals(**kw):[ kw is a dictionary whose keys are strings and each relatedvalue is a homcoord.Pt instance ->return a new Cardinals instance containing the entriesfrom kw ]

.transform(x):[ x is a homcoord.transform instance ->

return a new Cardinals instance with the same entriesexcept each value has been transformed by x ]

'''def transform(self, x):

'''Return a transformed instance.'''#-- 1 --result = Cardinals()

#-- 2 --# [ result +:= entries from self, with each value# transformed by x ]for key in self:

result[key] = x.apply(self[key])

#-- 3 --return result

A note on side effects: if you pass a pre-constructed set of keyword arguments to a function using thef(**kw) convention, a copy of that dictionary is made inside the called function. So, if you pass a dic-tionary to this constructor, you don't have to worry about the constructor changing values in that dic-tionary.

20. class Box: Basic rectangleAn instance of this class represents a rectangular area. Applications include bounding boxes as well asdisplayed rectangles.

tkscene.py

# - - - - - c l a s s B o x

class Box(object):'''Represents a rectangle.

Exports:Box(c1, c2):[ c1 and c2 are homcoord.Pt instances representing diagonally

New Mexico Tech Computer Centertkscene: A scene geometry manager48

Page 49: tkscene: A scene geometry manager for Tkinter A scene geometry manager for Tkinter John W. Shipman 2011-09-14 18:56 Abstract A Python-language module that manages the …

opposite corners of a rectangle ->return a new Box representing that rectangle ]

.cMin: [ minimum x and y coordinates as homcoord.Pt instances ]

.cMax: [ maximim x and y coordinates as homcoord.Pt instances ]

.__contains__( p ):[ p is a homcoord.Pt instance ->

if p is within self, including the boundaries ->return True

else -> return False ].sizes(): [ returns (width, height) ].aspect():[ returns the ratio of width:height as a float ]

Because the corner points may be either pair of corners, we compute the minima and maxima separatelyfor convenient testing.

tkscene.py

State/Invariants:self.__xMin: [ minimum x coordinate ]self.__xMax: [ maximum x coordinate ]self.__yMin: [ minimum y coordinate ]self.__yMax: [ maximum y coordinate ]

'''

20.1. Box.__init__(): Constructortkscene.py

# - - - B o x . _ _ i n i t _ _

def __init__(self, c1, c2):'''Constructor for a box with diagonal c1-c2.'''x1, y1 = c1.xy()x2, y2 = c2.xy()self.__xMin = min(x1, x2)self.__xMax = max(x1, x2)self.__yMin = min(y1, y2)self.__yMax = max(y1, y2)self.cMin = Pt(self.__xMin, self.__yMin)self.cMax = Pt(self.__xMax, self.__yMax)

20.2. Box.__contains__()tkscene.py

# - - - B o x . _ _ c o n t a i n s _ _

def __contains__(self, p):'''Is p within the box? Lines are good.'''x, y = p.xy()return ( ( self.__xMin <= x <= self.__xMax ) and

( self.__yMin <= y <= self.__yMax ) )

49tkscene: A scene geometry managerNew Mexico Tech Computer Center

Page 50: tkscene: A scene geometry manager for Tkinter A scene geometry manager for Tkinter John W. Shipman 2011-09-14 18:56 Abstract A Python-language module that manages the …

20.3. Box.sizes(): Return the sizes of the sidestkscene.py

# - - - B o x . s i z e s

def sizes(self):'''Return (width, height)'''return ( self.__xMax - self.__xMin, self.__yMax - self.__yMin )

20.4. Box.aspect(): Compute the aspect ratiotkscene.py

# - - - B o x . a s p e c t

def aspect(self):'''Return the box's width:height ratio.'''w, h = self.sizes()return float(w) / float(h)

21. pointsTuple(): Build a Tkinter points tupleMany Tkinter Canvas methods require argument lists that specify two or more points as a sequenceof positional arguments x0, y0, x1, y1, …. This service function converts a sequence of homcoord.Ptinstances into a tuple that can be passed to the function as a pre-built set of positional arguments usingthe f(*p) calling convention.

tkscene.py

# - - - p o i n t s T u p l e

def pointsTuple(pointsList):'''Build a Tkinter points argument set.

[ pointsList is a sequence of homcoord.Pt instances ->return a tuple(pointsList[0].x(), pointsList[0].y(),pointsList[1].x(), pointsList[1].y(), ... ]

'''result = []for pt in pointsList:

result.append(round(pt.x()))result.append(round(pt.y()))

return tuple(result)

22. An example: conferenceHere is a fully worked-out example script that renders a floor plan with various items of furniture onit. In this conference room, a three-seat couch and two easy chairs are placed around a round table ona stage, and the rest of the room is filled with rows of cheap, uncomfortable folding chairs.

New Mexico Tech Computer Centertkscene: A scene geometry manager50

Page 51: tkscene: A scene geometry manager for Tkinter A scene geometry manager for Tkinter John W. Shipman 2011-09-14 18:56 Abstract A Python-language module that manages the …

MAX_CHAIR_ANGLE

AISLE

COUCH_MARGIN

COUCH_ARM_WIDE

AISLETABLE_RADIUS

CHEAP_MARGIN

CHEAP_WIDE

AISLE

CHEAP_LONG

CHAIR_SPACING

0

10

20

0 10 20 30

ROOM_HIGH

ROOM_WIDE

COUCH_SEAT_WIDE

COUCH_SEAT_LONG

To illustrate how the scene may be animated, the Tkinter application will have a Scale widget thatcan rotate the two easy chairs around the center of the round table.

22.1. Prologueconference

#!/usr/bin/env python#================================================================# conference: Test driver for tkscene: conference hall setup.# Do not edit this file. It is extracted automatically from the

51tkscene: A scene geometry managerNew Mexico Tech Computer Center

Page 52: tkscene: A scene geometry manager for Tkinter A scene geometry manager for Tkinter John W. Shipman 2011-09-14 18:56 Abstract A Python-language module that manages the …

# documentation:# http://www.nmt.edu/tcc/help/lang/python/examples/tkscene/#----------------------------------------------------------------

22.2. Module importsThis application is built on a number of layers: sys is the universal system interface; numpy is

conference

# - - - - - I m p o r t s

import sysimport homcoord as hfrom Tkinter import *from tkscene import *

22.3. Manifest constantsconference

# - - - - - M a n i f e s t c o n s t a n t s

#--# Fonts#--BUTTON_FONT = ("Helvetica", 20)MONO_FONT = ("DejaVu Sans Mono", 12)

#--# Tags to be attached to canvas items#--COUCH_TAG = "c"TABLE_TAG = "t"

#--# Assorted dimensions. Scene units are feet.#--CAN_WIDE = 1200 # Width of the canvasCAN_HIGH = 800 # Height of the canvasDISPLAY_BOUNDS = Box(h.Pt(0, 0), h.Pt(CAN_WIDE, CAN_HIGH))MAX_CHAIR_ANGLE = 45.0 # Max chair rotation from front, degrees

#--# Dimensions of the stage area. Origin is the stage right,# downstage corner; downstage is toward the bottom of the display.#--def feetInches(feet, inches=0.0):

'''Convert feet and inches to decimal feet.

[ (feet is a number in feet) and

New Mexico Tech Computer Centertkscene: A scene geometry manager52

Page 53: tkscene: A scene geometry manager for Tkinter A scene geometry manager for Tkinter John W. Shipman 2011-09-14 18:56 Abstract A Python-language module that manages the …

(inches is a number in inches) ->return the equivalent length in decimal feet ]

'''return feet+float(inches)/12.0

STAGE_WIDE = feetInches(20) # x dimension and...STAGE_LONG = feetInches(15) # y dimension of the stageMARGIN = feetInches(2) # Extra space around the sceneSCENE_BOUNDS = Box(h.Pt(-MARGIN, -MARGIN),

h.Pt(STAGE_WIDE+MARGIN, STAGE_LONG+MARGIN))

#--# General house and stage dimensions.#--AISLE = feetInches(3) # Handicap-accessible aisle widthTABLE_RADIUS = feetInches(3, 6) # Radius of the round table onstageTABLE_COLOR = "#884411" # Table color: dark brownCOUCH_MARGIN = feetInches(2) # Space between table and couch

22.4. Main programSee Section 22.5, “class App: The application as a whole” (p. 53).

conference

# - - - - - m a i n

def main():'''Main program: display a conference room floor plan.'''app = App()app.master.title("conference: A tkscene example.")app.mainloop()

22.5. class App:The application as a wholeconference

# - - - - - c l a s s A p p

class App(Frame):'''Application as a whole.

State/Invariants:.can: [ the main Canvas ].angleVar: [ a DoubleVar for .angleScale ].angleScale: [ a scale with range [0,MAX_CHAIR_ANGLE] ].quitButton: [ a quit Button ].tableCenter: [ center point of the round table ].__scene:

[ a tkscene.Scene instance describing the mapping fromscene to display spaces ]

53tkscene: A scene geometry managerNew Mexico Tech Computer Center

Page 54: tkscene: A scene geometry manager for Tkinter A scene geometry manager for Tkinter John W. Shipman 2011-09-14 18:56 Abstract A Python-language module that manages the …

.couch1: [ a one-seat Couch instance ]

.couch3: [ a three-seat Couch instance ]

.roundTable: [ a RoundTable instance ]

.leftChair: [ the left-hand moving chair as a PlacedElt ]

.rightChair: [ the right-hand moving chair as a PlacedElt ]

Grid plan:+-------------+

0 | .can |+-------------+

1 | .angleScale |+-------------+

2 | .quitButton |+-------------+

'''

# - - - A p p . _ _ i n i t _ _

def __init__(self, master=None):Frame.__init__(self, master)self.grid()self.__createWidgets()self.__buildScene()print "@@@", self.__sceneself.__scene.tkDraw(self.can)

22.6. App.__createWidgets(): Set up Tkinter widgetsconference

# - - - A p p . _ _ c r e a t e w i d g e t s

def __createWidgets(self):'''Place all widgets; create all variables.'''self.can = Canvas ( self,

width=CAN_WIDE, height=CAN_HIGH )rowx, colx = 0, 0self.can.grid(row=rowx, column=colx)

self.angleVar = DoubleVar()self.angleVar.set(0.0)self.angleScale = Scale ( self,

command=self.__angleHandler, length="6i",from_=0.0, to=MAX_CHAIR_ANGLE, resolution=0.1,orient=HORIZONTAL,variable=self.angleVar )

rowx, colx = rowx+1, 0self.angleScale.grid(row=rowx, column=colx, sticky=E)

self.quitButton = Button ( self, text="Quit",font=BUTTON_FONT,command=self.quit )

New Mexico Tech Computer Centertkscene: A scene geometry manager54

Page 55: tkscene: A scene geometry manager for Tkinter A scene geometry manager for Tkinter John W. Shipman 2011-09-14 18:56 Abstract A Python-language module that manages the …

rowx, colx = rowx+1, 0self.quitButton.grid(row=rowx, column=colx, sticky=E+W)

22.7. App.__buildScene(): Set up the canvasFirst we construct the Scene instance that relates the scene and display spaces.

conference

# - - - A p p . _ _ b u i l d S c e n e

def __buildScene(self):'''Construct the scene on the canvas.'''#-- 1 --# [ self.__scene := as invariant ]self.__scene = Scene(SCENE_BOUNDS, DISPLAY_BOUNDS)

#--# Show the stage as a pale green rectangle#--points = pointsTuple (

[ self.__scene.s_d(Pt(0,0)),self.__scene.s_d(Pt(STAGE_WIDE, STAGE_LONG)) ] )

self.can.create_rectangle(points, fill='#efe')

The logic that places elements in the scene is in Section 22.8, “App.__furnish(): Arrange the fur-niture” (p. 55). Once the scene is built, it is rendered onto the canvas.

conference

#-- 2 --# [ self.__scene +:= elements of the scene in their initial# position ]self.__furnish()

22.8. App.__furnish(): Arrange the furnitureAll the furniture on stage is positioned relative to the round table, which is centered in the width of thestage and positioned one aisle width (AISLE) upstage from the edge of the stage, which is also they-axis.

conference

# - - - A p p . _ _ f u r n i s h

def __furnish(self):'''Arrange the furniture in the scene.

[ self.__scene is a Scene instance ->self.__scene +:= elements of the scene in their initial

position ]'''#-- 1 --# [ self.couch1 := as invariant# self.couch3 := as invariant# self.roundTable := as invariant

55tkscene: A scene geometry managerNew Mexico Tech Computer Center

Page 56: tkscene: A scene geometry manager for Tkinter A scene geometry manager for Tkinter John W. Shipman 2011-09-14 18:56 Abstract A Python-language module that manages the …

# self.leftChair := None ]self.couch1 = Couch(1)self.couch3 = Couch(3)self.roundTable = RoundTable(TABLE_RADIUS)self.leftChair = None

#-- 2 --# [ self.tableCenter := center of the round table ]self.tableCenter = h.Pt ( STAGE_WIDE/2.0, AISLE+TABLE_RADIUS )

For the details of the furniture placements, see:

• Section 22.9, “App.__placeTable()” (p. 56).• Section 22.10, “App.__placeCouch()” (p. 57).• Section 22.11, “App.__placeMovingChairs()” (p. 58).

conference

#-- 3 --# [ self.__scene +:= a Table element centered at# self.tableCenter ]self.__placeTable()

#-- 4 --# [ self.__scene +:= a 3-seat Couch element facing the house# and centered on stage behind the table ]self.__placeCouch()

#-- 5 --# [ self.__scene +:= two chairs (one-seat Couch elements)# facing the center of the table and at angles specified# by self.angleVar# self.leftChair := the left-hand chair element# self.rightChair := the right-hand chair element ]self.__placeMovingChairs()

22.9. App.__placeTable()conference

# - - - A p p . _ _ p l a c e T a b l e

def __placeTable(self):'''Place the table element into the scene.

[ self.__scene as invariant ->self.__scene +:= a Table element centered at

self.tableCenter ]'''

The table will be represented as a PlacedElt made from the Element instance self.roundTable.The transform used to place it into the scene is a single translation that moves the table's origin toself.tableCenter.

New Mexico Tech Computer Centertkscene: A scene geometry manager56

Page 57: tkscene: A scene geometry manager for Tkinter A scene geometry manager for Tkinter John W. Shipman 2011-09-14 18:56 Abstract A Python-language module that manages the …

conference

#-- 1 --# [ self.__scene := self.__scene with a new self.roundTable# element placed with its center at self.tableCenter ]PlacedElt ( self.__scene, self.roundTable,

h.Xlate ( self.tableCenter.x(), self.tableCenter.y() ) )

22.10. App.__placeCouch()First we instantiate a Couch element with three seats; see Section 22.14, “class Couch: Sectionalseating” (p. 61). Then we use various transforms to move it into its final position on the stage.

conference

# - - - A p p . _ _ p l a c e C o u c h

def __placeCouch(self):'''Place the couch into the scene.

[ self.__scene as invariant ->self.__scene +:= self.couch3 placed behind the

table, facing front, with COUCH_MARGIN clearance ]'''

The first step is to compose the various transforms needed to get the couch into the final position. In itsreference orientation, the couch faces up, with its reference point at the southwest (lower left) corner.

First we need to rotate the couch 180°around the reference point so that it points down.conference

#-- 2 --# [ swing := an h.Xform that rotates 180 degrees ]swing = h.Xrotate(RAD_180)

At this point the reference point is at the northeast corner. The next transform moves the reference pointsouth to the south edge, then west to the center of the couch, half its length (Couch.COUCH_LONG).

conference

#-- 1 --# [ refShift := an h.Xform that translates the reference# point by (self.couch3.totalWide/2, Couch.COUCH_LONG) ]refShift = h.Xlate(-self.couch3.totalWide/2, -Couch.COUCH_LONG)

Now that the couch is facing the house, the final transform shifts the reference point to the centerlineof the stage, with its y coordinate equal to the table's center, plus the table's radius, plus COUCH_MARGIN.Finally, place the couch element according to the composition of all three of these transforms.

conference

#-- 3 --# [ placer := an h.Xform that translates x by# (self.tableCenter.x()) and translates y by# (self.tableCenter.y()+TABLE_RADIUS+COUCH_MARGIN) ]placer = h.Xlate ( self.tableCenter.x(),

self.tableCenter.y() + TABLE_RADIUS + COUCH_MARGIN )

#-- 4 --# [ self.__scene := self.__scene with a new self.couch3

57tkscene: A scene geometry managerNew Mexico Tech Computer Center

Page 58: tkscene: A scene geometry manager for Tkinter A scene geometry manager for Tkinter John W. Shipman 2011-09-14 18:56 Abstract A Python-language module that manages the …

# element placed according to the composition of# refShift, swing, and placer ]PlacedElt ( self.__scene, self.couch3,

refShift.compose(swing).compose(placer) )

22.11. App.__placeMovingChairs()This method retrieves the current value of self.angleVar and uses it to place two comfy chairs (one-seat Couch elements) so that they face the center of the table. For angle zero, the chairs face each other,their centerlines coinciding, all the way at the front of the stage (see the diagram in Section 22, “An ex-ample: conference” (p. 50)). As the angle increases, the chairs rotate around the center of thetable inthe upstage direction, but the angle must not exceed MAX_CHAIR_ANGLE degrees.

conference

# - - - A p p . _ _ p l a c e M o v i n g C h a i r s

def __placeMovingChairs(self):'''Reposition the two chairs that self.angleScale moves.

[ self.__scene is as invariant ->if self.leftChair is None ->

self.__scene +:= two chairs (one-seat Couch elements)facing the center of the table and at anglesspecified by self.angleVar

self.leftChair := the left-hand chair elementself.rightChair := the right-hand chair element

else ->self.leftChair := self.leftChair moved to the

position specified by self.angleVarself.rightChair := self.rightChair moved to the

position specified by self.angleVar ]'''

Initially, we place the chairs using IDENTITY_XFORM (the identity transform) as a placeholder. Thepositions are then set by Section 22.12, “App.__setChairPositions()” (p. 59).

conference

#-- 1 --# [ self.__scene +:= two copies of self.chair1 placed at# any position# self.leftChair := one of those copies# self.rightChair := the other copy ]self.leftChair = PlacedElt ( self.__scene, self.couch1,

IDENTITY_XFORM )self.rightChair = PlacedElt ( self.__scene, self.couch1,

IDENTITY_XFORM )

#-- 2 --# [ self.leftChair := self.leftChair repositioned by# rotating clockwise by the value of self.angleVar# relative to the base position# self.rightChair := self.rightChair repositioned by# rotating counterclockwise by the value of

New Mexico Tech Computer Centertkscene: A scene geometry manager58

Page 59: tkscene: A scene geometry manager for Tkinter A scene geometry manager for Tkinter John W. Shipman 2011-09-14 18:56 Abstract A Python-language module that manages the …

# self.angleVar relative to the base position ]self.__setChairPositions()

22.12. App.__setChairPositions()conference

# - - - A p p . _ _ s e t C h a i r P o s i t i o n s

def __setChairPositions(self):'''Read the slider and position the left & right chairs

[ (self.couch1 is as invariant) and(self.leftChair and self.rightChair are PlacedEltinstance with element self.couch1) ->self.leftChair := self.leftChair repositioned by

rotating clockwise by the value of self.angleVarrelative to the base position

self.rightChair := self.rightChair repositioned byrotating counterclockwise by the value ofself.angleVar relative to the base position ]

'''

The purpose of this method is to change the current position of the two moving chairs. The base positionof these chairs, corresponding to angle 0° on the self.angleScale scale widget, is facing each otherdirectly across the table, their centerlines parallel to the edge of the stage and passing through the centerof the table. As the slider is moved, the left chair pivots around the table center in the clockwise direction,and the right chair counterclockwise.

To reposition a PlacedElement, one replaces its .xform attribute with a homcoord.Xform instancerepresenting the transform that moves the element from its reference space into the scene space. Tosimplify the logic of repositioning, we will develop the final transforms in two stages.

1. The first transform places a Couch(1) element in the position illustrated below: directly above thetable, its centerline vertical and passing through self.tableCenter. This transform is local variablechairToTop.

59tkscene: A scene geometry managerNew Mexico Tech Computer Center

Page 60: tkscene: A scene geometry manager for Tkinter A scene geometry manager for Tkinter John W. Shipman 2011-09-14 18:56 Abstract A Python-language module that manages the …

θ

x

y

.tableCenter

θ90 −o

θ−90o

TABLE_RADIUS

COUCH_MARGIN

2. The final transform that moves each chair from the position shown above is a rotation around thetable center: by an angle of (90°-θ) for the left-hand chair, and by the negative of that angle for theright-hand chair.

The first transform has two steps. First we rotate the chair 180° around the origin, so it is facing down,with its reference point at the top right corner. The rest is a translation:

• The Δx component is the sum of: half the width of the couch, which moves the reference point to thevertical midline; and self.tableCenter.x(), which moves the reference point to the centerlineof the stage.

• The Δy component is the sum of: the length of the couch, which moves the reference point to the frontedge of the couch; self.tableCenter.y(), which moves that point to the table center; andTABLE_RADIUS+COUCH_MARGIN, which moves that point to the position shown in the diagramabove.

conference

#- 1 --# [ theta := value of self.angleVar in radians# rot180 := a homcoord.Xform that rotates 180 degrees# around the origin# shiftOrigin := a homcoord.Xform that translates x by# ((half the width of self.couch1) +# self.tableCenter.x()) and translates y by# ((the length of self.couch1) + (self.tableCenter.y()) +# TABLE_RADIUS + COUCH_MARGIN) ]theta = h.num.radians(self.angleVar.get())rot180 = h.Xrotate(RAD_180)shiftOrigin = h.Xlate ( self.couch1.totalWide/2.0 +

self.tableCenter.x(),self.couch1.COUCH_LONG + self.tableCenter.y() +TABLE_RADIUS + COUCH_MARGIN )

New Mexico Tech Computer Centertkscene: A scene geometry manager60

Page 61: tkscene: A scene geometry manager for Tkinter A scene geometry manager for Tkinter John W. Shipman 2011-09-14 18:56 Abstract A Python-language module that manages the …

#-- 2 --# [ chairToTop := composition of rot180, then shiftOrigin# leftSwing := a homcoord.Xform representing a rotation# of (90 degrees - theta) around self.tableCenter# rightSwing := a homcoord.Xform representing a rotation# of (theta - 90 degrees) around self.tableCenter ]chairToTop = rot180.compose(shiftOrigin)leftAngle = RAD_90 - thetaleftSwing = h.Xrotaround ( self.tableCenter, leftAngle)rightSwing = h.Xrotaround ( self.tableCenter, - leftAngle )

#-- 3 --# [ self.leftChair.xform := composition of chairToTop# followed by leftSWing ]# self.rightChair.xform := composition of chairToTop# followed by rightSwing ]self.leftChair.xform = chairToTop.compose(leftSwing)self.rightChair.xform = chairToTop.compose(rightSwing)

22.13. App.__angleHandler(): Handler for the chair angle widgetconference

# - - - A p p . _ _ a n g l e H a n d l e r

def __angleHandler(self, *p):'''Handle changes to the chair angle slider.'''self.leftChair.erase()self.rightChair.erase()self.__setChairPositions()self.leftChair.tkDraw(self.can)self.rightChair.tkDraw(self.can)

22.14. class Couch: Sectional seatingTo reduce the number of different kinds of comfortable seating, we generalize easy chairs, love seats,three-seat sofas, four-seat sofas, and so forth into a single class representing seating for n people. Hereis a diagram showing the rudiments and how they are rendered in a love seat (n=2). In this referenceorientation, the arms are on the left and right, and the back is on the bottom.

61tkscene: A scene geometry managerNew Mexico Tech Computer Center

Page 62: tkscene: A scene geometry manager for Tkinter A scene geometry manager for Tkinter John W. Shipman 2011-09-14 18:56 Abstract A Python-language module that manages the …

.__totalWide

A

C BA

DD

CA

A

y

x

.COUCH_SEAT_LONG

.COUCH_BACK_LONG

.COUCH_ARM_WIDE

.COUCH_SEAT_WIDE

The outline is rendered as a box rudiment with the four sides marked A.A

This vertical represents the left-hand side of the right-hand arm.B

Verticals are drawn on the left side of each person's seat.C

These horizontal segments represent the front edge of the seat back.D

The scene dimensions are given as class constants, in inches. In the reference orientation, lengths (namesincluding _LONG) are the vertical dimension.

conference

# - - - - - c l a s s C o u c h

class Couch(Element):'''Sectional couch: n=1 for an armchair, n=2 for a love seat...

Exports:Couch(n):[ n is a positive integer ->

return a new Couch instance representing an armchair orsection couch seating n people ]

.n: [ as passed to constructor, read-only ]

.totalWide: [ couch's total width ]'''COUCH_SEAT_WIDE = feetInches(0,24) # Width of each person's seat.COUCH_ARM_WIDE = feetInches(0,2) # Width of the armrest.COUCH_BACK_LONG = feetInches(0,2) # Width of the back.COUCH_SEAT_LONG = feetInches(0,22) # Length of each person's seat.COUCH_COLOR = "#eeddbb"COUCH_BORDER_WIDTH = feetInches(0,0.5)#--# Total length of the couch.#--COUCH_LONG = COUCH_SEAT_LONG + COUCH_BACK_LONG

New Mexico Tech Computer Centertkscene: A scene geometry manager62

Page 63: tkscene: A scene geometry manager for Tkinter A scene geometry manager for Tkinter John W. Shipman 2011-09-14 18:56 Abstract A Python-language module that manages the …

22.15. Couch.__init__()conference

# - - - C o u c h . _ _ i n i t _ _

def __init__ ( self, n ):'''Constructor.'''#-- 1 --# [ self.totalWide := as invariant ]self.n = nself.totalWide = ( 2 * self.COUCH_ARM_WIDE +

n * self.COUCH_SEAT_WIDE )

22.16. Couch.render()Refer to the diagram in Section 22.14, “class Couch: Sectional seating” (p. 61) for the geometry.

conference

# - - - C o u c h . r e n d e r

def render(self):'''Render the rudiments of self.'''#-- 1 --# [ yield a box enclosing the entire couch, in the couch color ]yield BoxRudiment.make ( COUCH_TAG, Pt(0, 0),

Pt(self.totalWide, self.COUCH_LONG),fill=self.COUCH_COLOR, width=self.COUCH_BORDER_WIDTH )

#-- 2 --# [ yield a line for the left side of the right armrest ]x = self.totalWide - self.COUCH_ARM_WIDEyield StraightRudiment.make ( COUCH_TAG,

Pt(x, 0),Pt(x, self.COUCH_LONG) )

#-- 3 --# [ generate verticals and horizontals for each section ]for seatNo in range(0, self.n):

xMin = self.COUCH_ARM_WIDE + seatNo * self.COUCH_SEAT_WIDExMax = xMin + self.COUCH_SEAT_WIDEyield StraightRudiment.make ( COUCH_TAG,

Pt(xMin, 0), Pt(xMin, self.COUCH_LONG) )yield StraightRudiment.make ( COUCH_TAG,

Pt(xMin, self.COUCH_BACK_LONG),Pt(xMax, self.COUCH_BACK_LONG) )

#-- 4 --raise StopIteration

63tkscene: A scene geometry managerNew Mexico Tech Computer Center

Page 64: tkscene: A scene geometry manager for Tkinter A scene geometry manager for Tkinter John W. Shipman 2011-09-14 18:56 Abstract A Python-language module that manages the …

22.17. class RoundTableThis is an Element that renders as a filled circle of color TABLE_COLOR. The radius of the table is anargument to the constructor. The reference origin is the center of the table.

conference

# - - - - - c l a s s R o u n d T a b l e

class RoundTable(Element):'''A round table Element.

Exports:RoundTable(radius):[ radius is a radius in scene units ->

return a new RoundTable instance with that radius ].radius: [ as passed to constructor ]

'''

22.18. RoundTable.__init__()conference

# - - - R o u n d T a b l e . _ _ i n i t _ _

def __init__(self, radius):'''Constructor.'''self.radius = radius

22.19. RoundTable.render()The round table element renders a single element: a filled circle.

conference

# - - - R o u n d T a b l e . r e n d e r

def render(self):'''Render self's single Oval rudiment.'''#-- 1 --sw = Pt ( -self.radius, -self.radius )ne = Pt ( self.radius, self.radius )yield OvalRudiment.make ( TABLE_TAG, sw, ne, width=0,

fill=TABLE_COLOR)

#-- 2 --raise StopIteration

22.20. Epilogueconference

# - - - - - E p i l o g u e

New Mexico Tech Computer Centertkscene: A scene geometry manager64

Page 65: tkscene: A scene geometry manager for Tkinter A scene geometry manager for Tkinter John W. Shipman 2011-09-14 18:56 Abstract A Python-language module that manages the …

if __name__ == "__main__":main()

65tkscene: A scene geometry managerNew Mexico Tech Computer Center

Page 66: tkscene: A scene geometry manager for Tkinter A scene geometry manager for Tkinter John W. Shipman 2011-09-14 18:56 Abstract A Python-language module that manages the …

New Mexico Tech Computer Centertkscene: A scene geometry manager66