""" 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. """frompyprocessingimport*importmathimportgrammar # 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 """globalrepeat pen[ANGLE]+=DELTA*repeat repeat=1returnpendef__turnLeft(pen):""" private left turn function """globalrepeat pen[ANGLE]-=DELTA*repeat repeat=1returnpendef__reverse(pen):""" private reverse direction """ pen[ANGLE]+=math.pireturnpendef__twiceRepeat(pen):""" private set repeat to 2 """globalrepeat repeat=2returnpendef__thriceRepeat(pen):""" private set repeat to 3 """globalrepeat repeat=3returnpendef__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])returnpencopydef__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])returnpencopydef__reduceLength(pen):""" reduce length by dividing by the golden ratio """ pen[DIST]*=IPHIreturnpendef__increaseLength(pen):""" increase length by multiplying by the golden ratio """ pen[DIST]*=PHIreturnpendef__setColorRed(pen):""" using processing stroke function directly """ stroke(RED)returnpendef__setColorYellow(pen):""" using processing stroke function directly """ stroke(YELLOW)returnpendef__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)returnpendef__popStack(pen):""" you can also pop from the module stack without calling global NB: the pen parameter is only required for signature consistency """returnstack.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 }defevaluate(key, pen):""" Is a wrapper controlling access to the dict of functions """iflsys_op.has_key(key):pen=lsys_op[key](pen)else:ifnotRULES.has_key(key):# for debugging you could comment out this line%key # key is a substituted key without an actionreturnpendefrender(production):""" Render evaluates the production string and calls evaluate which draws the lines etc """ pen=[0, height/2, 0, 1000]forruleinproduction:pen=evaluate(rule, pen)defsetup():""" 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. """importrandomdef__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 probabilitiesforruleinrules.keys():# iterate over rule choices prob+=rules.get(rule)# add assigned probalitiesif((rand*tot)<prob):# compare running total with scaled random valuereturn(rule)defproduce(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 concatenationforiinaxiom:temp=rules.get(i, i)if(type(temp)isdict):str_buf.append(__weightedRule(temp))elif(type(temp)isstr):str_buf.append(temp)else:error="Unknown rule type %s\n"%type(temp)(error)return''.join(str_buf)# join str_buf list as a single stringdefrepeat(rpx, axiom, rules):""" Repeat rule substitution in a recursive fashion rpx times """ production=axiomfromitertoolsimportrepeatfor_inrepeat(None, rpx):production=produce(production, rules)returnproductiondef__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 rulesforzinrange(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={}forword2inwordList:freqD2[word2]=freqD2.get(word2, 0)+1 keyList=freqD2.keys()keyList.sort()forkey2inkeyList:%(key2, freqD2[key2])deftoRuleString(axiom, rules):""" Creates a string representing the pythonic rules in a more conventional manner """ output="Axiom:\t%s\n"%axiom keys=rules.keys()forkeyinkeys:temp=rules.get(key)type_temp=type(temp)if(type_tempisdict):keys2=temp.keys()forkey2inkeys2:output+="Rule:\t%s => %s\t%d\n"%(key, key2, temp.get(key2))elif(type_tempisstr):output+="Rule:\t%s => %s\n"%(key, temp)else:output+="Key:\t%s => %s an unknown rule type\n"%(key, type_temp)returnoutput

## No comments:

## Post a Comment