Recently I've been looking at Povray, pyprocessing, and cfdg (version 3.0) as tools for creating digital images. I have branched two separate blogs where I mainly explore jruby + processing and processing.py

Monday, 31 January 2011

PDF export of Penrose Tiling using lsystems grammar module and processing.py

   1 """
   2 penrose2.py is a python script for use in processing.py. 
   3 revised version uses my grammar module, output is a pdf file
   4 wait until penrose appears before closing the applet (text does not
   5 appear in applet, but should be in pdf file)
   6 """
   7 
   8 import math
   9 import grammar
  10 
  11 # some globals
  12 XPOS = 0
  13 YPOS = 1
  14 ANGLE = 2
  15 DELTA = math.pi/5
  16 
  17 RULES = {
  18 'F' : '',
  19 'W' : 'YBF2+ZRF4-XBF[-YBF4-WRF]2+',
  20 'X' : '+YBF2-ZRF[3-WRF2-XBF]+',
  21 'Y' : '-WRF2+XBF[3+YBF2+ZRF]-',
  22 'Z' : '2-YBF4+WRF[+ZRF4+XBF]2-XBF',
  23 }
  24 
  25 AXIOM = '[X]2+[X]2+[X]2+[X]2+[X]'
  26 
  27 
  28 def render(production):        # avoiding switch and globals
  29     """
  30     Render evaluates the production string and calls draw_line
  31     """
  32     turtle = [0, 0, -DELTA]
  33     stack = []
  34     repeat = 1
  35     for val in production:
  36         if val == "F": 
  37             turtle = __drawLine(turtle, 20)
  38         elif val == "+": 
  39             turtle[ANGLE] += DELTA * repeat
  40             repeat = 1
  41         elif val == "-": 
  42             turtle[ANGLE] -= DELTA * repeat
  43             repeat = 1
  44         elif val == "[": 
  45           temp = [turtle[XPOS], turtle[YPOS], turtle[ANGLE]]
  46           stack.append(temp)
  47         elif val == "]": 
  48             turtle = stack.pop() 
  49         elif ((val == '2') or (val == '3') or (val == '4')):
  50             repeat = int(val)     
  51         else: 
  52             pass
  53 
  54 
  55 def __drawLine(turtle, length):
  56     """
  57     private line draw uses processing 'line' function to draw a line
  58     to a heading from the turtle with distance = length returns a new
  59     turtle corresponding to end of the new line
  60     """
  61     new_xpos = turtle[XPOS] + length * math.cos(turtle[ANGLE])
  62     new_ypos = turtle[YPOS] - length * math.sin(turtle[ANGLE])
  63     line(turtle[XPOS], turtle[YPOS], new_xpos, new_ypos)
  64     return [new_xpos, new_ypos, turtle[ANGLE]]     
  65 
  66 def renderToPDF(production):
  67     """
  68     Create a pdf file with penrose drawing and rules, you require a ttf font for use with pdf
  69     standard linux font path below you will probaly need to modify the path to suit your system 
  70     """
  71     myFont = createFont("/usr/share/fonts/truetype/freefont/FreeSans.ttf", 18)
  72     beginRecord(PDF, "penrose.pdf")
  73     translate(width/2, height*0.4)
  74     render(production)
  75     textMode(SHAPE)
  76     fill(0)
  77     textFont(myFont, 18)
  78     text("Penrose Tiling", 300, 50)
  79     text(grammar.toRuleString(AXIOM, RULES), 100, 650)
  80     endRecord()
  81 
  82 
  83 def setup():
  84     """
  85     The processing setup statement
  86     """
  87     size(700, 900, P2D)
  88     background(255)
  89     hint(ENABLE_NATIVE_FONTS)
  90     smooth()
  91     production = grammar.repeat(5, AXIOM, RULES)
  92     strokeWeight(2)
  93     #render(production) # now redundant can be used if pdf render not working?
  94     renderToPDF(production)
  95 
  96 def draw():
  97     """
  98     An empty processing draw statement seems to be required to prevent premature exit()?
  99     """
 100     pass 
 101  

   1 """
   2 grammar.py module supports the parsing of both stochastic and non-stochastic rules
   3 axiom/rules are evaluated by the produce function, which uses the __weightedRule function
   4 to return any stochastic rule according to the input dict, the repeat function is used to repeatedly
   5 iterate the rules in a recursive fashion.
   6 Example Rules:
   7 
   8 Non-Stochastic = { "A": "A+F", "G": "GG", "X" :"G-G"}
   9 
  10 Stochastic = {"A" : {"BCD": 5, "C+C+C":10, "ACA": 40}, "B" : {"DE-F": 5, "CCC":10, "A[C]A": 40}}
  11 
  12 The Stochastic rule may contain non stochastic elements, in the above example there are two stochastic, elements,
  13 with keys "A" and "B". The stochastic rule is detected by checking the 'value' type is dict. The dict needs to of the
  14 form "string substitution" as key with the weighting as value.  A test function is included for the test conscious or
  15 skeptic.
  16 """
  17 
  18 
  19 import random
  20 
  21 def __weightedRule(rules):
  22     """
  23     A private method used to choose a substitution rule from a dict of rules, according to its
  24     weighted probality. 'rules' is expected to be a dict where the substition string is the 'key' 
  25     and the 'value' is the rule weight
  26     """
  27     rand = random.random()
  28     prob = 0
  29     tot = sum(rules.values())     # sum probabilities
  30     for rule in rules.keys():     # iterate over rule choices
  31         prob += rules.get(rule)   # add assigned probalities
  32         if ((rand * tot) < prob): # compare running total with scaled random value
  33             return(rule)
  34 
  35 def produce(axiom, rules):
  36     """
  37     The single rule substitution utility, that uses type to check for dict or str
  38     as key value, else declares 'Unrecognized grammar'. Does not throw exception!!!
  39     """
  40     output = ""
  41 
  42     for i in axiom:
  43         temp = rules.get(i, i)
  44         if (type(temp) is dict):
  45             output += __weightedRule(temp)
  46         elif (type(temp) is str):
  47             output += temp
  48         else:
  49             print("Unrecognized grammar")
  50     return output
  51     
  52  
  53 def repeat(rpx, axiom, rules):
  54     """
  55     Repeat rule substitution in a recursive fashion rpx times
  56     """ 
  57     production = axiom
  58     for i in range(0, rpx):
  59         production = produce(production, rules)
  60     return production
  61     
  62 def __testWeighting(rules, key, total):     
  63     """
  64     Private test function see module header for examples of rules format
  65     Takes a dict containing a stochastic rule with 'key' as key.
  66     Tests the weighted rule function a 'total' number of times.
  67     Frequency result is printed.
  68     """
  69     wordList = [] # create a big test list of replacement rules
  70     for z in range(0, total):    
  71         wordList.append(__weightedRule(rules.get(key)))
  72         
  73     # calculate each word frequency in generated list (NB: does not test the
  74     # randomness of order though)
  75     freqD2 = {}
  76     for word2 in wordList:
  77         freqD2[word2] = freqD2.get(word2, 0) + 1
  78     keyList = freqD2.keys()
  79     keyList.sort()
  80     
  81     print "Frequency of each substitution string in the word list (sorted):"
  82     for key2 in keyList:
  83         print "%-10s %d" % (key2, freqD2[key2])
  84         
  85 def toRuleString(axiom, rules):
  86     """
  87     Creates a string representing the pythonic rules in a more conventional
  88     manner
  89     """
  90     output = "Axiom:\t%s\n" % axiom 
  91     keys = rules.keys()
  92     for key in keys:
  93         temp = rules.get(key)
  94         if (type(temp) is dict):
  95             keys2 = temp.keys()
  96             for key2 in keys2:
  97                 output += "Rule:\t%s => %s\t%d\n" % (key, key2, temp.get(key2))
  98         elif (type(temp) is str):
  99             output += "Rule:\t%s => %s\n" % (key, temp)
 100         else:
 101             output += "Unrecognized grammar\n"
 102     return output        
 103         
 104 
 105 

It probably isn't clear from below PDF output, but the F is substituted with a "",
or as some would write it by a delete, since the default substitution is to return
the key as the value. Which can be read from the code lines 18 (penrose.py), and
43 (grammar.py) respectively.

No comments:

Post a Comment

Followers

Blog Archive

About Me

My photo
Pembrokeshire, United Kingdom
I have developed JRubyArt and propane new versions of ruby-processing for JRuby-9.1.5.0 and processing-3.2.2