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

Thursday, 10 February 2011

Exploring python with processing.py

Long before I encountered ruby I dabbled a bit with python, however I never did anything serious with it apart from the odd configuration script. I even installed Pardus (A Turkish developed linux OS) partly because I was impressed with the way the developers had transformed the hotchpot of bash/perl you name it boot script/configuration files into a thing of beauty using Mudur and Comar service and configuration scripts written in python. However my interest in things python waned, but then processing.py came along. However by this stage I have already spent a fair bit of time messing with both vanilla processing and ruby-processing, so to sustain my interest there has to be an angle. Perhaps it would interesting to compare and contrast the different programs/languages.
Owing to the relative ubquity of java and support fo applets vanilla processing wins hands down for something you can show on the web. However opengl applets especially with recent version of java do not play well. An interesting if terribly long winded approach is to create videos, particulary using ray traced images (this the tedious long winded bit), but Amnon Owed has done some very interesting stuff. The requirement to download JRuby or Jython to run the applets (at least a substantial amount thereof) kills both processing.py and ruby-processing stone dead. I'm at early stage rediscovering python, so I shouldn't judge it too harshly yet, but I have been surprised that somethings are easier to do achieve in java (than python). In contrast my experience with ruby is that I can get it to work for me, rather than adapt to it (with I need to find out the 'python' way of doing something, or it won't work at all). There is a certain reward for being permissive, because it encourages you try things out.
Anyway here is something I knocked up earlier in vanilla processing that I've translated to processing.py. I'm sticking with the Jeremy Ashkenas approach (ruby-processing creator) of when in python use python where possible. So instead of of using the processing PI constant I use math.pi etc. Here in place of a switch I have used a dict of functions (I can hear Alex Martelli now explaining why there is absolutely no need for a switch in python), well I think the dict approach has its limitations, and is certainly not performant on this occasion.

   1 """
   2 lozenges.py is a python script for use in processing.py.
   3 Penrose tiling based on some rules I found for Fractint
   4 You will need to wait a bit for this to render...
   5 Features the use of dict of functions
   6 uses a grammar module
   7 """
   8 
   9 import math
  10 import grammar
  11 
  12 # some lsystem constants
  13 XPOS = 0
  14 YPOS = 1
  15 ANGLE = 2
  16 DIST = 3
  17 RED = color(200, 0, 0, 80)
  18 YELLOW = color(200, 200, 0, 80)
  19 DELTA = math.pi/5
  20 PHI = (math.sqrt(5) - 1)/2
  21 IPHI = 1/PHI
  22 
  23 AXIOM = 'bX'
  24 
  25 RULES = {
  26     'X' : '@+bF[|Y]2-F[|X][|+@GIX]3-[X]bF2-[Y]bF', 
  27     'Y' : '@2+[X]rF|+rF[|Y]-[Y]rF|+rF[|X]',
  28     'F' : 'G'
  29 }
  30 
  31 # some module variables
  32 
  33 repeat = 1
  34 stack = []
  35 
  36 # lets use some lambdas to calculate the next x and y positions
  37 
  38 advanceX = (lambda(pen) : pen[XPOS] + pen[DIST] * math.cos(pen[ANGLE]))
  39 advanceY = (lambda(pen) : pen[YPOS] + pen[DIST] * math.sin(pen[ANGLE]))
  40 
  41 ##
  42 # defining the actions as functions with a common signature
  43 ##
  44 
  45 def __turnRight(pen): 
  46     """
  47     private right turn function
  48     """
  49     global repeat
  50     pen[ANGLE] += DELTA * repeat
  51     repeat = 1
  52     return pen
  53     
  54 def __turnLeft(pen):
  55     """
  56     private left turn function
  57     """
  58     global repeat
  59     pen[ANGLE] -= DELTA * repeat
  60     repeat = 1
  61     return pen 
  62     
  63 def __reverse(pen):
  64     """
  65     private reverse direction
  66     """
  67     pen[ANGLE] += math.pi
  68     return pen
  69     
  70 def __twiceRepeat(pen):
  71     """
  72     private set repeat to 2
  73     """
  74     global repeat
  75     repeat = 2
  76     return pen
  77     
  78 def __thriceRepeat(pen):
  79     """
  80     private set repeat to 3
  81     """
  82     global repeat
  83     repeat = 3
  84     return pen  
  85     
  86 def __drawLine(pen):
  87     """
  88     private draw line function uses processing 'line' function to draw lines
  89     takes a pen dictionary input returns a pen with new position
  90     """
  91     pencopy = []
  92     pencopy[:] = pen
  93     pencopy[XPOS] = advanceX(pen)
  94     pencopy[YPOS] = advanceY(pen)
  95     line(pen[XPOS], pen[YPOS], pencopy[XPOS], pencopy[YPOS])
  96     return pencopy
  97     
  98 def __moveForward(pen):
  99     """
 100     private move forward without drawing lines
 101     takes a pen dictionary input returns a pen with new position
 102     """
 103     stroke(YELLOW)
 104     pencopy = []
 105     pencopy[:] = pen
 106     pencopy[XPOS] = advanceX(pen)
 107     pencopy[YPOS] = advanceY(pen)
 108     return pencopy  
 109     
 110 def __reduceLength(pen): 
 111     """
 112     reduce length by dividing by the golden ratio
 113     """
 114     pen[DIST] *= IPHI
 115     return pen
 116     
 117 def __increaseLength(pen):
 118     """
 119     increase length by multiplying by the golden ratio
 120     """ 
 121     pen[DIST] *= PHI
 122     return pen
 123     
 124 def __setColorRed(pen):
 125     """
 126     using processing stroke function directly
 127     """ 
 128     stroke(RED)
 129     return pen      
 130     
 131 def __setColorYellow(pen):
 132     """
 133     using processing stroke function directly
 134     """
 135     stroke(YELLOW)
 136     return pen
 137     
 138 def __pushStack(pen):
 139     """
 140     surprisingly you can push to the module stack without calling global
 141     in the absence of a convenient list copy function, create a new list
 142     """
 143     pencopy = []
 144     pencopy[:] = pen
 145     stack.append(pencopy)
 146     return pen
 147     
 148 def __popStack(pen):
 149     """
 150     you can also pop from the module stack without calling global
 151     NB: the pen parameter is only required for signature consistency 
 152     """
 153     return stack.pop()
 154     
 155 ####
 156 # A dictionary of lsystem operations 
 157 ####
 158 
 159 lsys_op ={
 160     '+' : __turnRight,
 161     '-' : __turnLeft,
 162     '|' : __reverse,
 163     '2' : __twiceRepeat,
 164     '3' : __thriceRepeat,
 165     'F' : __drawLine,
 166     'G' : __moveForward,
 167     '@' : __increaseLength,
 168     'I' : __reduceLength,
 169     'b' : __setColorRed,
 170     'r' : __setColorYellow,
 171     '[' : __pushStack,
 172     ']' : __popStack    
 173 }
 174 
 175 def evaluate(key, pen):
 176     """
 177     Is a wrapper controlling access to the dict of functions
 178     """
 179     if lsys_op.has_key(key):
 180         pen = lsys_op[key](pen)
 181     else:
 182         if not RULES.has_key(key): # for debugging you could comment out this line
 183             print "Unknown rule %s" % key # key is a substituted key without an action 
 184     return pen  
 185 
 186 
 187 def render(production):        
 188     """
 189     Render evaluates the production string and calls evaluate which
 190     draws the lines etc
 191     """
 192     pen = [width*0.2, height/2, 0, 360]
 193     for rule in production:
 194         pen = evaluate(rule, pen)       
 195    
 196     
 197 def setup():
 198     """
 199     The processing setup statement
 200     """
 201     size(600, 600)
 202     production = grammar.repeat(7, AXIOM, RULES) 
 203     background(100, 0, 0)
 204     smooth()
 205     strokeWeight(3)
 206     stroke(YELLOW)
 207     render(production)
 208     
 209     
 210 def draw():
 211     """
 212     Required to prevent premature death of script
 213     """
 214     pass


 
 
See previous posting for the original grammar module and here for improved version. First version with simple += string concatenation is inefficient, revised version stores strings as a list then 'joins' them.

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