#!/usr/bin/python import cairo import math class Boxes: def __init__(self, width=300, height=200, thickness=3.0): self.thickness = thickness self.burn = 0.1 self.fingerJointSettings = (10.0, 10.0) self.fingerHoleEdgeWidth = 1.0 # multitudes of self.thickness self.doveTailJointSettings = (10, 5, 50, 0.4) # width, depth, angle, radius self.flexSettings = (1.5, 3.0, 15.0) # line distance, connects, width self.output = "box.svg" self._init_surface(width, height) def _init_surface(self, width, height): self.surface = cairo.SVGSurface(self.output, width, height) self.ctx = ctx = cairo.Context(self.surface) ctx.translate(0, height) ctx.scale(1, -1) ctx.set_source_rgb(1.0, 1.0, 1.0) ctx.rectangle(0, 0, width, height) ctx.fill() ctx.set_source_rgb(0.0, 0.0, 0.0) ctx.set_line_width(0.1) def cc(self, callback, number, x=0.0, y=0.0): """call callback""" self.ctx.save() self.moveTo(x, y) if callable(callback): callback(number) elif hasattr(callback, '__getitem__'): try: callback = callback[number] if callable(callback): callback() except KeyError: pass except: self.ctx.restore() raise self.ctx.restore() ############################################################ ### Turtle graphics commands ############################################################ def corner(self, degrees, radius=0): d = 1 if (degrees > 0) else -1 rad = degrees*math.pi/180 if degrees > 0: self.ctx.arc(0, radius+self.burn, radius+self.burn, -0.5*math.pi, rad - 0.5*math.pi) else: self.ctx.arc_negative(0, -(radius+self.burn), radius+self.burn, 0.5*math.pi, rad + 0.5*math.pi) self.continueDirection(rad) def edge(self, length): self.ctx.line_to(length, 0) self.ctx.translate(*self.ctx.get_current_point()) def curveTo(self, x1, y1, x2, y2, x3, y3): """control point 1, control point 2, end point""" self.ctx.curve_to(x1, y1, x2, y2, x3, y3) dx = x3-x2 dy = y3-y2 rad = math.atan2(dy, dx) self.continueDirection(rad) def fingerJoint(self, length, positive=True, settings=None): # assumes, we are already moved out by self.burn! # negative also assumes we are moved out by self.thinkness! space, finger = settings or self.fingerJointSettings fingers = int((length-space) // (space+finger)) leftover = length - fingers*(space+finger) - finger b = self.burn s, f, thickness = space, finger, self.thickness if not positive: b = -b thickness = -thickness self.ctx.move_to(0, 0) for i in xrange(fingers): pos = leftover/2.0+i*(space+finger) self.ctx.line_to(pos+s-b, 0) self.ctx.line_to(pos+s-b, -thickness) self.ctx.line_to(pos+s+f+b, -thickness) self.ctx.line_to(pos+s+f+b, 0) self.ctx.line_to(length, 0) self.ctx.translate(*self.ctx.get_current_point()) def fingerHoles(self, length, settings=None): space, finger = settings or self.fingerJointSettings fingers = int((length-space) // (space+finger)) leftover = length - fingers*(space+finger) - finger b = self.burn s, f = space, finger for i in xrange(fingers): pos = leftover/2.0+i*(space+finger) self.ctx.rectangle(pos+s+b, -self.thickness/2+b, f-2*b, self.thickness - 2*b) self.ctx.move_to(0, length) self.ctx.translate(*self.ctx.get_current_point()) def fingerHoleEdge(self, length, dist, settings=None): self.ctx.save() self.moveTo(0, dist+self.thickness/2) self.fingerHoles(length, settings) self.ctx.restore() # XXX continue path self.ctx.move_to(0, 0) self.ctx.line_to(length, 0) self.ctx.translate(*self.ctx.get_current_point()) # helpers for doveTailJoint # not intended for general use def _turnLeft(self, radius, angle): self.ctx.arc(0, radius, radius, -0.5*math.pi, angle) self.continueDirection(0.5*math.pi+angle) def _turnRight(self, radius, angle): self.ctx.arc_negative(0, -radius, radius, 0.5*math.pi, -angle) self.continueDirection(-0.5*math.pi - angle) def _turn(self, radius, angle, right=True): if right: self._turnRight(radius, angle) else: self._turnLeft(radius, angle) def doveTailJoint(self, length, positive=True, settings=None): width, depth, angle, radius = settings or self.doveTailJointSettings angle = math.pi*angle/180.0 alpha = 0.5*math.pi - angle l1 = radius/math.tan(alpha/2.0) diffx = 0.5*depth/math.tan(alpha) l2 = 0.5*depth / math.sin(alpha) sections = int((length) // (width*2)) leftover = length - sections*width*2 p = 1 if positive else -1 self.edge((width+leftover)/2.0+diffx-l1) for i in xrange(sections): self._turn(radius-p*self.burn, angle, right=positive) self.edge(2*(l2-l1)) self._turn(radius+p*self.burn, angle, right=not positive) self.edge(2*(diffx-l1)+width) self._turn(radius+p*self.burn, angle, right=not positive) self.edge(2*(l2-l1)) self._turn(radius-p*self.burn, angle, right=positive) if i