# L-System translator in Python # Author: Jeff Terrell # Date: Thu, 2008-04-10 import sys import re # Read an L-system definition contained in infile # Time: 30 minutes def read_lsystem(infile): try: input = open(infile) except IOError (errno, strerror): print "I/O error(%s): %s" % (errno, strerror) sys.exit() # a simple approach: read the entire file in as a single string cstream = input.read() input.close() lsys = {} r = re.compile(r'Axiom:[\n\s]*(\S+)[\n\s]+Rules:[\n\s]*([\sBFG+\->\[\]\|]+)[\n\s]+Angle:[\n\s]*(\d+)[\n\s]+Initial Length:[\n\s]*(\d+)[\n\s]*$', re.MULTILINE) m = r.match(cstream) if not m: print "Error in L-system definition\n" sys.exit() lsys['axiom'] = m.group(1) rules = m.group(2) lsys['angle'] = int(m.group(3)) lsys['initlen'] = int(m.group(4)) # rules is a string. First, make it into a list of strings, where each rule # is its own string. r = re.compile(r'\n', re.MULTILINE) rulesarray = r.split(rules) lsys['rules'] = {} # Then, translate each rule into a key/value pair, and add it to the # lsys['rules'] dictionary. for i in range(len(rulesarray)): r = re.compile(r'\s*([A-Z]+)\s*->\s*(.+)$') m = r.match(rulesarray[i]) lsys['rules'][m.group(1)] = m.group(2) return lsys # Apply the translation rules of the L-system times # Time: 12 minutes def apply_rules(lsys, depth): if depth == 0: return lsys['axiom'] system = translate(lsys['axiom'], lsys['rules']) i = 1 while i < depth: system = translate(system, lsys['rules']) i += 1 return system # Translate a system (a string) by all the rules in (a dictionary) def translate(system, rules): ret = "" for char in system: if char in rules: ret += rules[char] else: ret += char return ret # Translate the string into Turtle commands. Save result in outfile. def draw_system(angle, length, system, outfile): try: fileout = open(outfile, "w") except IOError (errno, strerror): print "I/O error(%s): %s" % (errno, strerror) sys.exit() fileout.writelines("VAR angle %d\n" % angle) fileout.writelines("VAR len %d\n" % length) for char in system: if char == 'B': pass elif char == 'F': fileout.writelines("DRAW_FORWARD len\n") elif char == 'G': fileout.writelines("MOVE_FORWARD len\n") elif char == '+': fileout.writelines("ROTATE CLOCKWISE angle\n") elif char == '-': fileout.writelines("ROTATE COUNTERCLOCKWISE angle\n") elif char == '[': fileout.writelines("SAVE_STATE\n") elif char == ']': fileout.writelines("RECOVER_STATE\n") elif char == '|': fileout.writelines("MUL len 65\n") fileout.writelines("DIV len 100\n") fileout.writelines("DRAW_FORWARD len\n") fileout.close() def main(argv=None): # parse the Turtle input if argv is None: argv = sys.argv if len(argv) != 4: sys.stderr.write('Error: Usage:\npython lsystem.py infile depth outfile\n') sys.exit() else: lsys = read_lsystem(argv[1]) final_system = apply_rules(lsys, int(argv[2])) # print final_system draw_system(lsys['angle'], lsys['initlen'], final_system, argv[3]) # So, main() will only get called if this program is run directly, but not when # it's imported somewhere else. Nifty, eh? if __name__ == "__main__": main()