#!/usr/bin/python import turtle from sys import argv from math import * x = 500 y = 500 def turtle_draw_line(x1, y1, x2, y2, color=(0, 0, 0)): turtle.color(color) turtle.penup() turtle.setx(x1) turtle.sety(y1) turtle.pendown() turtle.goto(x2, y2) def turtle_init(thickness, background): turtle.speed = 0 turtle.bgcolor(background) turtle.width(thickness) def turtle_end(): input() def getend(r, theta, x1, y1): mx = r * cos(theta / (180/pi)) my = r * sin(theta / (180/pi)) return x1 + mx, y1 + my svg_output_file = 0 def run_with_turtle(fractal): draw_fractal(fractal, turtle_draw_line, turtle_init) turtle_end() def svg_draw_line(x1, y1, x2, y2, color=(0, 0, 0)): x1 += x/2 x2 += x/2 y1 = -y1 + y/2 y2 = -y2 + y/2 line = '\n'%(x1, y1, x2, y2, str(tuple([int(c*255) for c in color]))) svg_output_file.write(line) def svg_end(): svg_output_file.write("\n") def svg_init(thickness, background): # This should be provided by draw_fractal header = " "%(x,y,thickness) svg_output_file.write(header) def run_with_svg(fractal, outputfile = ""): global svg_output_file if outputfile == "": outputfile = raw_input("Output filename: ") svg_output_file = open(outputfile, "w") draw_fractal(fractal, svg_draw_line, svg_init) svg_end() svg_output_file.close() def draw_fractal(fractal, draw_line, init): global x,y lines = fractal.split("\n") level = 2 size = 70 mode = "a" bgcolor = (1, 1, 1) insidecolor = (0, 0, 0) outsidecolor = (0, 0, 0) thickness = 1 function = "" functions = {} for l in lines: if not len(l.split()): continue if l[0].lower() == 'x': x = float(l.split()[1]) elif l[0].lower() == 'y': y = float(l.split()[1]) elif l.split()[0].lower() == "level": level = int(l.split()[1]) elif l.split()[0].lower() == "mode": mode = l.split()[1] elif l.split()[0].lower() == "sides": sides = int(l.split()[1]) elif l.split()[0].lower() == "size": size = float(l.split()[1]) elif ':' in l: function = l.strip().strip(":") functions[function] = [] elif l.split()[0].lower() == "backgroundcolor": bgcolor = [float(c) for c in l.split()[1:]] elif l.split()[0].lower() == "insidecolor": insidecolor = [float(c) for c in l.split()[1:]] elif l.split()[0].lower() == "outsidecolor": outsidecolor = [float(c) for c in l.split()[1:]] elif l.split()[0].lower() == "line" or l.split()[0].lower() == "thickness": thickness = float(l.split()[1]) else: print l functions[function].append(l.strip()) maxr = sqrt((x/2)**2 + (y/2)**2) def colorblend(xl, yl): r = sqrt(xl**2 + yl**2) bw = r / maxr def avgw(a, b): return (a*(1-bw)+b*bw)/2.0 return [avgw(ic, oc) for ic, oc in zip(insidecolor, outsidecolor)] def linelen(x1, y1, x2, y2): return sqrt((x1-x2)**2 + (y1+y2) ** 2) def recur_draw(rlvl, x1, y1, x2, y2, function, draw_line=draw_line): length = sqrt((x1 - x2)**2 + (y1 - y2)**2) heading = atan2(y2 - y1, x2 - x1) * (180/pi) # This seems nothing but bad...but maybe I need it for some reason? # if x2 < x1: # quadrants 2 and 3, with y running in the "math" direction # heading += 180 # elif y2 < y1: # heading += 360 code = function nextfunction = mode[0] modespot = 0 for instruction in code: isp = instruction.split() if isp[0] == "forward" or isp[0] == "fwd": movedist = length if len(isp) < 2 else float((float(isp[1])/100.0) * length) newx, newy = getend(movedist, heading, x1, y1) if rlvl and linelen(x1, y1, newx, newy) > 1: recur_draw(rlvl-1, x1, y1, newx, newy, functions[nextfunction]) modespot += 1 if modespot >= len(mode): modespot -= len(mode) nextfunction = mode[modespot] else: draw_line(x1, y1, newx, newy, colorblend(x1, y1)) x1 = newx y1 = newy if isp[0] == "right": heading -= 90 if len(isp) < 2 else float(isp[1]) if isp[0] == "left": heading += 90 if len(isp) < 2 else float(isp[1]) return x1, y1 def resolve_auto(f): nothing = lambda a, b, c, d, e : 0 def try_a(val): testfunction = [l.replace(" a", " " + str(val)) for l in functions[f]] ex, ey = recur_draw(0, 0, 0, 100, 100, testfunction, nothing) return sqrt((ex-100.0)**2 * (ey-100)**2) print("Resolving auto reference...") best = 0, 100 for i in range(1000): dist = try_a(i) if dist < best[1]: best = i, dist newbest = best for i in range(-10, 10): adj = i/10.0 dist = try_a(best[0]+adj) if dist < newbest[1]: newbest = best[0]+adj, dist print "Resolved: Final value ", newbest[0] return newbest[0] for f in functions: for line in functions[f]: if "forward" in line and " a" in line: a = resolve_auto(f) functions[f] = [l.replace(" a", " " + str(a)) for l in functions[f]] break print "Beginning level ", level, " draw" init(thickness, bgcolor) diameter = x * size/100.0 xi1, yi1, xi2, yi2 = [-diameter/2.0, 0, diameter/2.0, 0] print xi1, yi1, xi2, yi2 degstep = 360 / sides sidelen = diameter * sin( (180/sides) * (pi/180)) xc, yc = xi1, yi1 cangle = -90-degstep/2 if sides < 3: recur_draw(level, xi1, yi1, xi2, yi2, functions["a"]) for i in range(sides): cangle += degstep endx, endy = getend(sidelen, cangle, xc, yc) recur_draw(level, xc, yc, endx, endy, functions["a"]) xc, yc = endx, endy if __name__ == "__main__": import argparse parser = argparse.ArgumentParser(description="Creates Fractals from fdl files") parser.add_argument("files", metavar="filename", help="Fractal to draw", nargs="+") parser.add_argument("-d", metavar = "turtle|svg", help="Specify drawing method, turtle or svg", default="turtle") parser.add_argument("-o", metavar="filename", help="Specify output file (for SVG only)", default="") args = parser.parse_args() for ff in args.files: if args.d == "svg": run_with_svg(open(ff).read(), args.o) elif args.d == "turtle": run_with_turtle(open(ff).read()) else: print "Unknown drawing method: %s"%args.d