#! python # # $Id$ import math import sys import unittest class point: def __init__(self, xloc, yloc): self.x = xloc self.y = yloc self.zstyle = "none" self.name = "unnamed" self.fwangle = 90 def clone(self): newpoint = point(self.x, self.y) newpoint.zstyle = self.zstyle newpoint.name = self.name newpoint.fwangle = self.fwangle return newpoint def rotateorigin(self, degrees): radians = degrees * math.pi / 180.0 newpoint = self.clone() newpoint.x = self.x * math.cos(radians) - self.y * math.sin(radians) newpoint.y = self.x * math.sin(radians) + self.y * math.cos(radians) newpoint.fwangle = self.fwangle + degrees return newpoint def rotate(self, degrees, xorigin, yorigin): rot = point(self.x - xorigin, self.y - yorigin).rotateorigin(degrees) newpoint = self.clone() newpoint.x = rot.x + xorigin newpoint.y = rot.y + yorigin newpoint.fwangle = self.fwangle + degrees return newpoint def translate(self, x, y): newpoint = self.clone() newpoint.x += x newpoint.y += y return newpoint def setzstyle(self, zstyle): newpoint = point(self.x, self.y) newpoint.name = self.name newpoint.zstyle = zstyle return newpoint class actor: _width = 80 _depth = 48 def __init__(self, boyOrGirl, x, y, angle, inscene): self._x = x self._y = y self._angle = angle self.stored_handpos = {} self.name = boyOrGirl if boyOrGirl == "boy": self._isGirl = False self._class = "boy" else: self._isGirl = True self._class = "girl" self.scene = inscene def center(self): return point(self._x, self._y) def point(self, id): pt = self.pointrel(id).rotateorigin(self._angle).translate(self._x, self._y) pt.name = self.name + " " + id return pt def pointrel(self, id): if id == "center": return point(0, 0) elif id == "L shoulder": return point(self._width/2 - 2, 0).setzstyle("shoulder") elif id == "R shoulder": return point( - self._width/2 + 2, 0).setzstyle("shoulder") elif id == "L hip": newpoint = point(self._width/2 - 2, 0) newpoint.zstyle = "L hip" newpoint.fwangle += 180 return newpoint elif id == "R hip": return point( - self._width/2 + 2, 0).setzstyle("R hip") elif id == "mid-lower back": newpoint = point(0, -self._depth/2 + 2) newpoint.zstyle = "lower back" newpoint.fwangle += 90 return newpoint elif id == "L arm": return point(self._width/2 - 2, 0) elif id == "R arm": return point( - self._width/2 + 2, 0) elif id == "ribcage": return point(0, - self._depth / 2 + 2) elif id == "L outward": return point(self._width, self._depth) elif id == "R outward": return point(- self._width, self._depth) elif id == "L in front": return point(self._width / 2, self._depth) elif id == "R in front": return point(- self._width / 2, self._depth) elif id == "L neutral": return point(self._width / 2, self._depth / 2) elif id == "R neutral": return point(- self._width / 2, self._depth / 2) def armto(self, origid, dest): orig = self.point(origid + " arm") if dest.zstyle == "L hip" or dest.zstyle == "R hip" or dest.zstyle == "lower back": self.drawlowhand(dest) self.scene.line(x1 = orig.x, y1 = orig.y, x2 = dest.x, y2 = dest.y, css = (self._class + ' arm')) if dest.zstyle == "shoulder": self.drawhand(dest) self.scene.addinfo(self.name + ": " + orig.name + " to " + dest.name) dest.name = self.name + " " + origid + " hand [" + dest.name + "]" self.stored_handpos[origid] = dest def drawhand(self, at): self.scene.circle(at.x, at.y, 7, self._class) def drawlowhand(self, at): self.scene.drawsymbol("lowhand", at, 14, 14, at.fwangle, self._class) def handpos(self, hand): dest = self.stored_handpos[hand] return dest def draw(self): x = self._x y = self._y if self._isGirl: symbol = "girl" else: symbol = "boy" self.scene.drawsymbol(symbol, point(self._x, self._y), self._width, self._depth, self._angle, self._class) class scene: def __init__(self, title): self.xmin = 1000 self.ymin = 1000 self.xmax = 0 self.ymax = 0 self.xml = [] self.sceneinfo = [] self.title = title def addinfo(self, info): self.sceneinfo.append(info) def line(self, x1, y1, x2, y2, css): self.extendboundstopoint(x1, y1) self.extendboundstopoint(x2, y2) self.writexml("line", { 'x1': x1, 'y1': y1, 'x2': x2, 'y2': y2, 'class': css } ) def rect(self, x, y, width, height, css): self.extendboundstopoint(x, y) self.extendboundstopoint(x + width, y + height) self.writexml("rect", { 'x': x, 'y': y, 'width': width, 'height': height, 'class': css } ) def circle(self, cx, cy, r, css): self.extendboundstopoint(cx - r, cy - r) self.extendboundstopoint(cx + r, cy + r) self.writexml('circle', { 'cx': cx, 'cy': cy, 'r': r, 'class': css} ) def drawsymbol(self, symbol, at, width, height, angle, css): self.extendboundstopoint(at.x - width/2, at.y - height/2) self.extendboundstopoint(at.x + width/2, at.y + height/2) self.writexml('use', { 'xlink:href' : '#' + symbol, 'x': at.x - width/2, 'y': at.y - height/2, 'width': width, 'height': height, 'transform': 'rotate(' + str(angle) + ', ' + str(at.x) + ', ' + str(at.y) + ')', 'class': css }) def extendboundstopoint(self, x, y): if x > self.xmax: self.xmax = x if x < self.xmin: self.xmin = x if y > self.ymax: self.ymax = y if y < self.ymin: self.ymin = y def writexml(self, eltname, kwargs): # actually, we don't write it. We stash it away, and we'll write # it later when we know what the bounding box is. if self.xml != None: self.xml.append([eltname, kwargs]) else: # calling during shutdown, we've already taken the cached # elements self.writeactualxml(eltname, kwargs) def writeactualxml(self, eltname, kwargs): res = " <" + eltname for kw in kwargs.keys(): # special case :-( attr = kw if attr == "css": attr = "class" res += ' ' + attr + '="' + str(kwargs[kw]) + '"' res += " />" print res def writexmloffset(self, xoff, yoff, eltname, attrs): newattrs = {} for attr in attrs.keys(): if attr == 'x' or attr == 'x1' or attr == 'x2' or attr == 'cx': newattrs[attr] = attrs[attr] + xoff elif attr == 'y' or attr == 'y1' or attr == 'y2' or attr == 'cy': newattrs[attr] = attrs[attr] + yoff elif attr == 'transform': newattrs[attr] = self.offsettransform(xoff, yoff, attrs[attr]) else: newattrs[attr] = attrs[attr] self.writeactualxml(eltname, newattrs) def offsettransform(self, xoff, yoff, transformstr): # for now assume there's only one transformation openparenpos = transformstr.find("(") if openparenpos == -1: return transformstr closeparenpos = transformstr.index(")") function = transformstr[:openparenpos].strip() args = transformstr[openparenpos+1:closeparenpos] if transformstr[closeparenpos+1:].strip() != "": raise ValueError(transformstr) # now split the arguments at commas arglist = [s.strip() for s in args.split(',')] if function == 'translate': arglist[0] = str(float(arglist[0]) + xoff) arglist[1] = str(float(arglist[1]) + yoff) elif function == 'rotate': # first arg is the angle, second two are the center of rotation arglist[1] = str(float(arglist[1]) + xoff) arglist[2] = str(float(arglist[2]) + yoff) return function + "(" + ", ".join(arglist) + ")" def drawbb(self): # we pad, not only for aesthetic reasons, but because the # calculated bounding box doesn't take account of the # stroke width. padding = 10 self.rect(self.xmin - padding, self.ymin - padding, self.xmax - self.xmin + padding * 2, self.ymax - self.ymin + padding * 2, "boundingbox") def writestart(self, title, width, height): print "

" + title + "

" print ' ' print """ """ def end(self): padding = 10 self.writestart(self.title, self.xmax - self.xmin + padding*2, self.ymax - self.ymin + padding*2) for xmlelt in self.xml: self.writexmloffset(-self.xmin + padding, -self.ymin + padding, xmlelt[0], xmlelt[1]) self.xml = None # stop caching print " " print " " print class scenetests(unittest.TestCase): def testoffsettransform(self): """Make sure that the offsetting of transforms is working right""" myscene = scene('dummy') self.assertEqual("dummy", myscene.offsettransform(0, 0, "dummy")) self.assertEqual("scale(12)", myscene.offsettransform(6, 2, "scale(12)")) self.assertEqual("translate(10.0, 3.0)", myscene.offsettransform(6, 2, "translate(4, 1)")) self.assertEqual("rotate(90, 10.0, 3.0)", myscene.offsettransform(6, 2, "rotate(90, 4, 1)")) def fileheader(filetitle): print """ """ + filetitle + """

""" + filetitle + """

$Id$

""" def filefooter(): print """ """ def main(): fileheader("Trying out hand positions") # run any unit tests before we start suite = unittest.TestLoader().loadTestsFromModule(__import__('__main__')) unittest.TextTestRunner(verbosity=2).run(suite) inscene = scene("1: shoulders") boy = actor("boy", 150, 90, 90, inscene) boy.draw() girl = actor("girl", 100, 34, 0, inscene) girl.draw() boy.armto("R", girl.point("R shoulder")) boy.armto("L", boy.point("L in front")) girl.armto("R", girl.point("R in front")) girl.armto("L", boy.handpos("L")) inscene.end() inscene = scene("2: hip") boy = actor("boy", 150, 90, 90, inscene) boy.draw() girl = actor("girl", 100, 34, 0, inscene) girl.draw() boy.armto("R", girl.point("R hip")) boy.armto("L", boy.point("L in front")) girl.armto("R", girl.point("R in front")) girl.armto("L", boy.handpos("L")) inscene.end() inscene = scene("hip again") boy = actor("boy", 100, 34, 0, inscene) boy.draw() girl = actor("girl", 100, 104, 0, inscene) girl.draw() boy.armto("R", girl.point("R hip")) boy.armto("L", girl.point("L hip")) girl.armto("R", girl.point("R in front")) girl.armto("L", girl.point("L in front")) inscene.end() inscene = scene("XB") boy = actor("boy", 150, 90, 90, inscene) boy.draw() girl = actor("girl", 100, 34, 0, inscene) girl.draw() boy.armto("R", girl.point("mid-lower back")) boy.armto("L", boy.point("L in front")) girl.armto("R", boy.handpos("L")) girl.armto("L", boy.point("R shoulder")) inscene.end() filefooter() main()