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

Friday, 3 June 2011

Colored Penrose Tiling in pyprocessing

Pyprocessing is "pure" python implementation of vanilla processing (based on pyglet to provide the graphics). I find I can use jEdit as my ide for pyprocessing, make sure you install the python plugin and download the community python commando file for best experience. Also be sure to create a simple macro to launch the python executable in the console, and you then you will be running your sketch with two mouse clicks. For screen-shots see an earlier blog entry discussing the carrot and coriander linux distro.

"""
lozenges.py by Martin Prout after a 'Fractint' sketch by Philippe Hurbain
You will need to wait a bit for this to render...(modified to run in pyprocessing)
Features the use of dict of functions (as a pythonic version of switch)
Uses a grammar module to create production, and uses algebra in place
of processing affine transforms to calculate line coordinates. 
"""
from pyprocessing import *
import math
import grammar

# some lsystem constants
XPOS = 0
YPOS = 1
ANGLE = 2
DIST = 3
RED = color(200, 0, 0, 80)
YELLOW = color(200, 200, 0, 80)
DELTA = math.pi/5
PHI = (math.sqrt(5) - 1)/2
IPHI = 1/PHI

AXIOM = 'bX'

RULES = {
    'X' : '@+bF[|Y]2-F[|X][|+@GIX]3-[X]bF2-[Y]bF', 
    'Y' : '@2+[X]rF|+rF[|Y]-[Y]rF|+rF[|X]',
    'F' : 'G'
}

# some module variables

repeat = 1
stack = []

##
# defining the actions as functions with a common signature
##

def __turnRight(pen):
    """
    private right turn function
    """
    global repeat
    pen[ANGLE] += DELTA * repeat
    repeat = 1
    return pen
    
def __turnLeft(pen):
    """
    private left turn function
    """
    global repeat
    pen[ANGLE] -= DELTA * repeat
    repeat = 1
    return pen 
    
def __reverse(pen):
    """
    private reverse direction
    """
    pen[ANGLE] += math.pi
    return pen
    
def __twiceRepeat(pen):
    """
    private set repeat to 2
    """
    global repeat
    repeat = 2
    return pen
    
def __thriceRepeat(pen):
    """
    private set repeat to 3
    """
    global repeat
    repeat = 3
    return pen  
    
def __drawLine(pen):
    """
    private draw line function uses processing 'line' function to draw lines
    takes pen dictionary input returns a pen with new position
    """
    pencopy = []
    pencopy[:] = pen
    pencopy[XPOS] = pen[XPOS] + pen[DIST] * math.cos(pen[ANGLE])
    pencopy[YPOS] = pen[YPOS] + pen[DIST] * math.sin(pen[ANGLE])
    line(pen[XPOS], pen[YPOS], pencopy[XPOS], pencopy[YPOS])
    return pencopy
    
def __moveForward(pen):
    """
    private move forward without drawing lines
    takes pen dictionary input returns a pen with new position
    """
    stroke(YELLOW)
    pencopy = []
    pencopy[:] = pen
    pencopy[XPOS] = pen[XPOS] + pen[DIST] * math.cos(pen[ANGLE])
    pencopy[YPOS] = pen[YPOS] + pen[DIST] * math.sin(pen[ANGLE])
    return pencopy  
    
def __reduceLength(pen): 
    """
    reduce length by dividing by the golden ratio
    """
    pen[DIST] *= IPHI
    return pen
    
def __increaseLength(pen):
    """
    increase length by multiplying by the golden ratio
    """ 
    pen[DIST] *= PHI
    return pen
    
def __setColorRed(pen):
    """
    using processing stroke function directly
    """ 
    stroke(RED)
    return pen      
    
def __setColorYellow(pen):
    """
    using processing stroke function directly
    """
    stroke(YELLOW)
    return pen
    
def __pushStack(pen):
    """
    surprisingly you can push to the module stack without calling global
    in the absence of a convenient list copy function, create a new list
    """
    pencopy = []
    pencopy[:] = pen
    stack.append(pencopy)
    return pen
    
def __popStack(pen):
    """
    you can also pop from the module stack without calling global
    NB: the pen parameter is only required for signature consistency 
    """
    return stack.pop()
    
####
# A dictionary of lsystem operations 
####

lsys_op ={
    '+' : __turnRight,
    '-' : __turnLeft,
    '|' : __reverse,
    '2' : __twiceRepeat,
    '3' : __thriceRepeat,
    'F' : __drawLine,
    'G' : __moveForward,
    '@' : __increaseLength,
    'I' : __reduceLength,
    'b' : __setColorRed,
    'r' : __setColorYellow,
    '[' : __pushStack,
    ']' : __popStack    
}

def evaluate(key, pen):
    """
    Is a wrapper controlling access to the dict of functions
    """
    if lsys_op.has_key(key):
        pen = lsys_op[key](pen)
    else:
        if not RULES.has_key(key): # for debugging you could comment out this line
            print "Unknown rule %s" % key # key is a substituted key without an action 
    return pen  


def render(production):        
    """
    Render evaluates the production string and calls evaluate which
    draws the lines etc
    """
    pen = [0, height/2, 0, 1000]
    for rule in production:
        pen = evaluate(rule, pen)       
   
    
def setup():
    """
    The processing setup statement
    """
    size(600, 600)
    production = grammar.repeat(8, AXIOM, RULES) 
    background(100, 0, 0)
    smooth()
    strokeWeight(3)
    stroke(YELLOW)
    render(production)
  
run()  
   


"""
grammar.py module by Martin Prout
Supports the parsing of both stochastic and non-stochastic rules
axiom/rules are evaluated by the produce function, which uses the __weightedRule function
to return any stochastic rule according to the input dict, the repeat function is used to repeatedly
iterate the rules in a recursive fashion.
Example Rules:

Non-Stochastic = { "A": "A+F", "G": "GG", "X" :"G-G"}

Stochastic = {"A" : {"BCD": 5, "C+C+C":10, "ACA": 40}, "B" : {"DE-F": 5, "CCC":10, "A[C]A": 40}}

The Stochastic rule may contain non stochastic elements, in the above example there are two stochastic, elements,
with keys "A" and "B". The stochastic rule is detected by checking the 'value' type is dict. The dict needs to of the
form "string substitution" as key with the weighting as value.  A test function is included for the test conscious or
skeptic.
"""


import random

def __weightedRule(rules):
    """
    A private method used to choose a substitution rule from a dict of rules, according to its
    weighted probality. 'rules' is expected to be a dict where the substition string is the 'key' 
    and the 'value' is the rule weight
    """
    rand = random.random()
    prob = 0
    tot = sum(rules.values())     # sum probabilities
    for rule in rules.keys():     # iterate over rule choices
        prob += rules.get(rule)   # add assigned probalities
        if ((rand * tot) < prob): # compare running total with scaled random value
            return(rule)

def produce(axiom, rules):
    """
    The single rule substitution utility, that uses type to check for dict or str
    as key value, else declares 'Unrecognized grammar'. Does not throw exception!!!
    """
    str_buf = [] # list is much more efficient than premature string concatenation

    for i in axiom:
        temp = rules.get(i, i)
        if (type(temp) is dict):
            str_buf.append(__weightedRule(temp))
        elif (type(temp) is str):
            str_buf.append(temp)
        else:
            error = "Unknown rule type %s\n" % type(temp)
            print(error)
    return ''.join(str_buf) # join str_buf list as a single string
    
 
def repeat(rpx, axiom, rules):
    """
    Repeat rule substitution in a recursive fashion rpx times
    """ 
    production = axiom
    from itertools import repeat
    for _ in repeat(None, rpx):
        production = produce(production, rules)
    return production
    
def __testWeighting(rules, key, total):     
    """
    Private test function see module header for examples of rules format
    Takes a dict containing a stochastic rule with 'key' as key.
    Tests the weighted rule function a 'total' number of times.
    Frequency result is printed.
    """
    wordList = [] # create a big test list of replacement rules
    for z in range(0, total):    
        wordList.append(__weightedRule(rules.get(key)))
        
    # calculate each word frequency in generated list (NB: does not test the
    # randomness of order though)
    freqD2 = {}
    for word2 in wordList:
        freqD2[word2] = freqD2.get(word2, 0) + 1
    keyList = freqD2.keys()
    keyList.sort()
    
    print "Frequency of each substitution string in the word list (sorted):"
    for key2 in keyList:
        print "%-10s %d" % (key2, freqD2[key2])
        
def toRuleString(axiom, rules):
    """
    Creates a string representing the pythonic rules in a more conventional
    manner
    """
    output = "Axiom:\t%s\n" % axiom 
    keys = rules.keys()
    for key in keys:
        temp = rules.get(key)
        type_temp =  type(temp)
        if (type_temp is dict):
            keys2 = temp.keys()
            for key2 in keys2:
                output += "Rule:\t%s => %s\t%d\n" % (key, key2, temp.get(key2))
        elif (type_temp is str):
            output += "Rule:\t%s => %s\n" % (key, temp)
        else:
            output += "Key:\t%s => %s an unknown rule type\n" % (key, type_temp)
    return output        
        

   

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