#!/usr/bin/env python3 """svgturtle - simple library to generate SVG paths from turtle graphics style commands""" import math import sys class SvgTurtle(): def __init__(self, homex=0, homey=0): """Create a new turtle at the given home location, facing right""" self.homex = homex self.homey = homey self.cvtangle = math.tau/360 self.reset() def penup(self): """Subsequent movements will be invisible and only affect location and heading""" self.pen = False def pendown(self): """Subsequent movements will be visible and drawn""" self.pen = True def forward(self, distance): """Move forward""" if self.pen and (self.path == ''): self.path = "M %.2f,%.2f" % (self.x,self.y) dx = distance*math.cos(self.heading) dy = distance*math.sin(self.heading) self.x += dx self.y += dy if self.pen: if abs(dy) < .01: self.path += " h %.2f" % dx elif abs(dx) < .01: self.path += " v %.2f" % dy else: self.path += " l %.2f,%.2f" % (dx, dy) elif self.path != '': self.path += " m %.2f, %.2f" % (dx, dy) def back(self, distance): """Move backward""" self.forward(-distance) def left(self, angle): """Turn left by angle specified in degrees""" self.right(-angle) def right(self, angle): """Turn right by angle specified in degrees""" self.heading = (self.heading + angle*self.cvtangle) % math.tau def circle(self, radius, extent=360, steps=None): """Draw a circle or arc spanning extent degrees around a center radius units to the left (if radius is positive) or right (if radius is negative). Use a polygon if steps is specified, otherwise a circle.""" if steps: w = 1.0*extent/steps w2 = 0.5*w l = 2.0*radius*math.sin(w2*math.pi/180.0) if radius < 0: l, w, w2 = -l, -w, -w2 self.left(w2) for i in range(steps): self.forward(l) self.left(w) self.right(w2) else: if extent>355: self.circle(radius, 355) extent -= 355 ra = self.cvtangle*(extent if radius < 0 else -extent) cx = self.x+radius*math.cos(self.heading-.5*math.pi) cy = self.y+radius*math.sin(self.heading-.5*math.pi) th = self.heading+.5*math.pi+ra dx = cx+radius*math.cos(th)-self.x dy = cy+radius*math.sin(th)-self.y lg = 1 if extent >= 180 else 0 sw = 0 if radius > 0 else 1 if self.pen: if self.path == '': self.path = "M %.2f,%.2f" % (self.x,self.y) self.path += " a %.2f %.2f %.2f %d %d %.2f %.2f" % (radius, radius, extent, lg, sw, dx, dy) elif self.path != '': self.path += " m %.2f, %.2f" % (dx, dy) self.x += dx self.y += dy self.heading = (self.heading + ra) % math.tau def to_s(self): """Return the generated path, suitable for the d attribute of an SVG element""" return self.path def home(self): """Reset to the initial position and heading""" self.x = self.homex self.y = self.homey self.heading = 0 if self.path != '': self.path += " M %.2f, %.2f" % (self.x, self.y) def reset(self): """Clear the path and return home""" self.path = '' self.pen = True self.home() if __name__ == "__main__": turtle = SvgTurtle(50, 100) turtle.left(90) turtle.forward(50) turtle.left(30) turtle.forward(50) turtle.penup() turtle.back(50) turtle.right(60) turtle.pendown() turtle.forward(50) turtle.penup() turtle.back(50) turtle.left(30) turtle.back(50) turtle.right(90) turtle.forward(70) turtle.pendown() turtle.circle(25) print('') print('' % turtle.to_s()) print('')