diff options
author | rcoh <rcoh@mit.edu> | 2011-02-12 16:40:02 -0500 |
---|---|---|
committer | rcoh <rcoh@mit.edu> | 2011-02-12 16:40:02 -0500 |
commit | 5f54bf57128dbca04a9325670a6ad1e03097d5cb (patch) | |
tree | f7d5a94975c842cec497dd8b301830fd5a30f7e9 | |
parent | 1604f05ec0bad79798bbd764cd62bdb20e55f444 (diff) | |
parent | cf1048df72b845ef7fefd5ec5709f7d1b2c4df79 (diff) |
Merge branch 'objprops'
Conflicts:
Profile.py
-rw-r--r-- | TestAll.py | 8 | ||||
-rw-r--r-- | behaviors/ColorShift.py | 16 | ||||
-rw-r--r-- | behaviors/ModulateColor.py | 16 | ||||
-rw-r--r-- | config/C5Demo.xml | 231 | ||||
-rw-r--r-- | config/Demo.xml | 9 | ||||
-rw-r--r-- | layouts/C5SignLayout.xml | 16 | ||||
-rw-r--r-- | layouts/ZigzagLayout.py | 11 | ||||
-rw-r--r-- | operationscore/Behavior.py | 12 | ||||
-rw-r--r-- | operationscore/Input.py | 2 | ||||
-rw-r--r-- | operationscore/SmootCoreObject.py | 13 | ||||
-rw-r--r-- | tests/TestBQS.py | 40 | ||||
-rw-r--r-- | tests/TestConfigLoaders.py | 14 | ||||
-rw-r--r-- | tests/__init__.py | 3 | ||||
-rw-r--r-- | util/BehaviorQuerySystem.py | 39 | ||||
-rw-r--r-- | util/ColorOps.py | 4 | ||||
-rw-r--r-- | util/ComponentRegistry.py | 25 | ||||
-rw-r--r-- | util/Config.py | 15 | ||||
-rw-r--r-- | util/Geo.py | 12 |
18 files changed, 451 insertions, 35 deletions
@@ -1,9 +1,7 @@ import unittest from unittest import TestLoader -import tests.TestConfigLoaders -import tests.TestComponentRegistry -testSuite = TestLoader().loadTestsFromModule(tests.TestConfigLoaders) -unittest.TextTestRunner(verbosity=2).run(testSuite) +import tests -testSuite = TestLoader().loadTestsFromModule(tests.TestComponentRegistry) +testSuite = TestLoader().loadTestsFromModule(tests) unittest.TextTestRunner(verbosity=2).run(testSuite) + diff --git a/behaviors/ColorShift.py b/behaviors/ColorShift.py new file mode 100644 index 0000000..f185b40 --- /dev/null +++ b/behaviors/ColorShift.py @@ -0,0 +1,16 @@ +import util.ColorOps as colorOps +from operationscore.Behavior import * +import colorsys +class ColorShift(Behavior): + def processResponse(self, sensor, recurs): + ret = [] + for data in sensor: + if not 'HSV' in data: + data['HSV'] = list(colorsys.rgb_to_hsv(*data['Color'])) + + data['HSV'][0] += .01 + if data['HSV'][0] >= 360: + data['HSV'][0] = 0 + data['Color'] = colorsys.hsv_to_rgb(*data['HSV']) + ret.append(data) + return (ret,[]) diff --git a/behaviors/ModulateColor.py b/behaviors/ModulateColor.py new file mode 100644 index 0000000..904526e --- /dev/null +++ b/behaviors/ModulateColor.py @@ -0,0 +1,16 @@ +import util.ColorOps as colorOps +from operationscore.Behavior import * +import colorsys +class ColorShift(Behavior): + def processResponse(self, sensor, recurs): + ret = [] + for data in sensor: + if not 'HSV' in data: + data['HSV'] = colorsys.rgb_to_hsv(data['Color']) + + data['HSV'][0] += .5 + if data['HSV'][0] >= 360: + data['HSV'][0] = 0 + data['Color'] = colorsys.hsv_to_rgb(data['HSV']) + ret.append(data) + return (ret,[]) diff --git a/config/C5Demo.xml b/config/C5Demo.xml new file mode 100644 index 0000000..c0b4402 --- /dev/null +++ b/config/C5Demo.xml @@ -0,0 +1,231 @@ +<!---All configuration items contain a "Class" tag specifying the python class they represent, and an "Args" tag specifying the args to be passed in.--> +<LightInstallation> + <InstallationConfiguration> + <Defaults> + <PixelMapper>simplemap</PixelMapper> + </Defaults> + </InstallationConfiguration> + <PixelConfiguration> + <InheritsFrom>layouts/C5SignLayout.xml</InheritsFrom> + </PixelConfiguration> + <PixelMapperConfiguration> + <PixelMapper> + <Class>pixelmappers.SimpleMapper</Class> + <Args> + <Id>simplemap</Id> + <CutoffDist>20</CutoffDist> + </Args> + </PixelMapper> + <PixelMapper> + <Class>pixelmappers.GaussianMapper</Class> + <Args> + <Id>gaussmap</Id> + <CutoffDist>30</CutoffDist> + <MinWeight>0.1</MinWeight> + <Width>10</Width> + <Height>1</Height> + </Args> + </PixelMapper> + </PixelMapperConfiguration> + <RendererConfiguration> + <Renderer> + <InheritsFrom>renderers/Pygame.xml</InheritsFrom> + </Renderer> + </RendererConfiguration> + <InputConfiguration> + <InputElement> + <Class>inputs.PygameInput</Class> + <Args> + <Id>pygameclick</Id> + <RefreshInterval>10</RefreshInterval> + <Clicks>True</Clicks> + </Args> + </InputElement> + <InputElement> + <Class>inputs.PygameInput</Class> + <Args> + <Id>pygamekey</Id> + <RefreshInterval>10</RefreshInterval> + <Keyboard>True</Keyboard> + </Args> + </InputElement> + <InputElement> + <Class>inputs.UDPInput</Class> + <Args> + <Id>udp</Id> + <Port>3344</Port> + <RefreshInterval>50</RefreshInterval> + </Args> + </InputElement> + <!--<InputElement> + <Class>inputs.TCPInput</Class> + <Args> + <Id>tcp</Id> + <Port>20120</Port> + <RefreshInterval>10</RefreshInterval> + </Args> + </InputElement>--> + <InputElement Id="followmouse" RefreshInterval="1000"> + <InheritsFrom>inputs/MouseFollower.xml</InheritsFrom> + </InputElement> + </InputConfiguration> + <BehaviorConfiguration> + <Behavior Id="colorchange"> + <InheritsFrom>behaviors/RandomColor.xml</InheritsFrom> + <Args> + <ColorList> + <Val>(255,0,0)</Val> + <Val>(0,0,255)</Val> + </ColorList> + </Args> + </Behavior> + <Behavior Id="decay"> + <InheritsFrom>behaviors/PixelDecay.xml</InheritsFrom> + </Behavior> + <Behavior Id="singleframe"> + <InheritsFrom>behaviors/SingleFrame.xml</InheritsFrom> + </Behavior> + <Behavior Id="slowdecay"> + <InheritsFrom>behaviors/PixelDecay.xml</InheritsFrom> + <Args> + <Coefficient>.01</Coefficient> + </Args> + </Behavior> + <Behavior> + <Class>behaviors.XYMove</Class> + <Args> + <Id>xymove</Id> + <XStep>5</XStep> + <YStep>2</YStep> + </Args> + </Behavior> + <Behavior> + <Class>behaviors.RestrictLocation</Class> + <Args> + <Id>xbounce</Id> + <Action>{val}*-1</Action> + <ParamName>XStep</ParamName> + <LocationRestriction>{x}<0 or {x}>200</LocationRestriction> + </Args> + </Behavior> + <Behavior> + <Class>behaviors.RestrictLocation</Class> + <Args> + <Id>ybounce</Id> + <Action>{val}*-1</Action> + <ParamName>YStep</ParamName> + <LocationRestriction>{y}<0 or {y}>100</LocationRestriction> + </Args> + </Behavior> + <Behavior> + <Class>behaviors.BehaviorChain</Class> + <Args> + <Id>movebounce</Id> + <ChainedBehaviors> + <Id>xymove</Id> + <Id>colorshift</Id> + <Id>ybounce</Id> + <Id>xbounce</Id> + </ChainedBehaviors> + </Args> + </Behavior> + <Behavior> + <Class>behaviors.ModifyParam</Class> + <Args> + <Id>ysin</Id> + <ParamName>YStep</ParamName> + <ParamType>Sensor</ParamType> + <ParamOp>4*math.sin({x}/float(40))</ParamOp> + </Args> + </Behavior> + <Behavior> + <Class>behaviors.DebugBehavior</Class> + <Args> + <Id>debug</Id> + <z-index>0</z-index> + <Inputs> + <Id>pygamekey</Id> + <Id>udp</Id> + </Inputs> + </Args> + </Behavior> + <Behavior> + <Class>behaviors.Square</Class> + <Args> + <Id>square</Id> + <Width>20</Width> + </Args> + </Behavior> + <Behavior Id="recursivedecay"> + <InheritsFrom>behaviors/LoopAndDie.xml</InheritsFrom> + <Args> + <InitialResponseCount>80</InitialResponseCount> + </Args> + </Behavior> + <Behavior> + <Class>behaviors.BehaviorChain</Class> + <Args> + <Id>runcolordecay</Id> + <Inputs> + <Id>pygameclick</Id> + </Inputs> + <ChainedBehaviors> + <Id>colorchange</Id> + <Id>mover</Id> + <Id>decay</Id> + </ChainedBehaviors> + <RecursiveHooks>{'mover':'movebounce'}</RecursiveHooks> + <RenderToScreen>True</RenderToScreen> + <Mapper>gaussmap</Mapper> + </Args> + </Behavior> + <Behavior> + <Class>behaviors.ResponseMover</Class> + <Args> + <Id>mover</Id> + </Args> + </Behavior> + <Behavior> + <Class>behaviors.RandomWalk</Class> + <Args> + <Id>randmovement</Id> + <StepSize>2</StepSize> + </Args> + </Behavior> + <Behavior Id="accelerate"> + <InheritsFrom>behaviors/Accelerate.xml</InheritsFrom> + </Behavior> + <Behavior> + <Class>behaviors.EchoBehavior</Class> + <Args> + <Id>echo</Id> + <z-index>0</z-index> + <RenderToScreen>False</RenderToScreen> + </Args> + </Behavior> + <Behavior> + <Class>behaviors.ColorShift</Class> + <Args> + <Id>colorshift</Id> + </Args> + </Behavior> + <Behavior> + <Class>behaviors.BehaviorChain</Class> + <Args> + <Id>mousechaser</Id> + <Inputs> + <Id>followmouse</Id> + </Inputs> + <ChainedBehaviors> + <Id>echo</Id> + <Id>square</Id> + <Id>singleframe</Id> + </ChainedBehaviors> + <RenderToScreen>True</RenderToScreen> + </Args> + </Behavior> + <Behavior Id="running"> + <InheritsFrom>behaviors/RunningBehavior.xml</InheritsFrom> + </Behavior> + </BehaviorConfiguration> +</LightInstallation> diff --git a/config/Demo.xml b/config/Demo.xml index 4e811ba..67e9811 100644 --- a/config/Demo.xml +++ b/config/Demo.xml @@ -126,6 +126,7 @@ <Id>movebounce</Id> <ChainedBehaviors> <Id>xymove</Id> + <Id>colorshift</Id> <Id>ybounce</Id> <Id>xbounce</Id> </ChainedBehaviors> @@ -206,6 +207,12 @@ </Args> </Behavior> <Behavior> + <Class>behaviors.ColorShift</Class> + <Args> + <Id>colorshift</Id> + </Args> + </Behavior> + <Behavior> <Class>behaviors.BehaviorChain</Class> <Args> <Id>mousechaser</Id> @@ -217,7 +224,7 @@ <Id>square</Id> <Id>singleframe</Id> </ChainedBehaviors> - <RenderToScreen>True</RenderToScreen> + <RenderToScreen>False</RenderToScreen> </Args> </Behavior> <Behavior Id="running"> diff --git a/layouts/C5SignLayout.xml b/layouts/C5SignLayout.xml new file mode 100644 index 0000000..58310d7 --- /dev/null +++ b/layouts/C5SignLayout.xml @@ -0,0 +1,16 @@ + <PixelConfiguration> + <PixelStrip> + <Class>layouts.ZigzagLayout</Class> + <Args> + <Id>strip1</Id> + <zigLength>10</zigLength> + <zigAxis>X</zigAxis> + <yDirection>1</yDirection> + <pixelToPixelSpacing>20</pixelToPixelSpacing> + <spacing>20</spacing> <!--we can space at any value less the + l2lspacing--> + <numPixels>50</numPixels> + <originLocation>(0,0)</originLocation> + </Args> + </PixelStrip> + </PixelConfiguration> diff --git a/layouts/ZigzagLayout.py b/layouts/ZigzagLayout.py index 3fc2ea1..665d14e 100644 --- a/layouts/ZigzagLayout.py +++ b/layouts/ZigzagLayout.py @@ -1,14 +1,5 @@ from operationscore.PixelAssembler import * import pdb -#Slightly more complex layout class that makes a zig-Zag Led Pattern -#Inheriting classes must specify zigLength, the length in lights of a of a zig -#and zig Axis, the direction of the long X axis (X or Y). -#EG: zig length = 4, zig Axis = X would give: -# X-X-X-X -# | -# X-X-X-X -# | -# X-X-X-X etc. class ZigzagLayout(PixelAssembler): """ZigZagLayout is a slightly more complex layout class that makes a zig-Zag Led Pattern Inheriting classes must specify zigLength, the length in lights of a of a zig @@ -18,7 +9,7 @@ class ZigzagLayout(PixelAssembler): | X-X-X-X | - X-X-X-X etc. + X-X-X-X etc.""" def initLayout(self): if not 'zigLength' in self.argDict: raise Exception('zigLength must be defined in argDict') diff --git a/operationscore/Behavior.py b/operationscore/Behavior.py index 6424842..7090a23 100644 --- a/operationscore/Behavior.py +++ b/operationscore/Behavior.py @@ -1,9 +1,6 @@ - import pdb from operationscore.SmootCoreObject import * from logger import main_log -#timeStep is called on every iteration of the LightInstallation -#addInput is called on each individual input received, and the inputs queue class Behavior(SmootCoreObject): """Abstract class for a behavior. On every time step, the behavior is passed the inputs from all sensors it is bound to as well as any recursive inputs that it @@ -22,6 +19,7 @@ class Behavior(SmootCoreObject): self.recursiveResponseQueue = [] self.sensorResponseQueue = [] self.outGoingQueue = [] + self.lastState = None self.behaviorInit() def behaviorInit(self): pass @@ -44,6 +42,13 @@ class Behavior(SmootCoreObject): else: self.addInput(sensorInputs) #private + def getLastOutput(self): + return self.lastState + def setLastOutput(self, output): + """Override to modify state. For example: if you are using a behavior that does uses + strings for location specification, you will want to override this to point to a single + location. Make sure you keep lastState as a [] of {}. (List of dicts)""" + self.lastState = output def addMapperToResponse(self, responses): if self['Mapper'] != None: if type(responses) == type(tuple): @@ -59,5 +64,6 @@ class Behavior(SmootCoreObject): self.recursiveResponseQueue) self.sensorResponseQueue = [] self.recursiveResponseQueue = recursions + self.setLastOutput(outputs) main_log.debug(self['Id'] + ' Ouputs ' + str(outputs)) return self.addMapperToResponse(outputs) diff --git a/operationscore/Input.py b/operationscore/Input.py index d3d5644..5a835ec 100644 --- a/operationscore/Input.py +++ b/operationscore/Input.py @@ -17,7 +17,7 @@ class Input(ThreadedSmootCoreObject): self.inputInit() def respond(self, eventDict): - #if eventDict != []: + eventDict['InputId'] = self['Id'] self.parentScope.lock.acquire() self.parentScope.processResponse(self.argDict, eventDict) self.parentScope.lock.release() diff --git a/operationscore/SmootCoreObject.py b/operationscore/SmootCoreObject.py index 0d32773..6addb9c 100644 --- a/operationscore/SmootCoreObject.py +++ b/operationscore/SmootCoreObject.py @@ -2,6 +2,7 @@ import pdb import threading import thread import util.Config as configGetter +import types class SmootCoreObject(object): """SmootCoreObject is essentially a super-object class which grants us some niceties. It allows @@ -34,9 +35,15 @@ class SmootCoreObject(object): def __setitem__(self,k, item): self.argDict[k] = item - def __getitem__(self, item): - if item in self.argDict: - return self.argDict[item] + def __getitem__(self, key): + if key in self.argDict: + item = self.argDict[key] + if isinstance(item, types.FunctionType): + return item(self.argDict) #resolve the lambda function, if it exists + #elif isinstance(item, list): #if its a list of items + # pass #TODO: consider doing resolution of lambda funcs for items in lists + else: + return item else: return None def __contains__(self, item): diff --git a/tests/TestBQS.py b/tests/TestBQS.py new file mode 100644 index 0000000..7316c31 --- /dev/null +++ b/tests/TestBQS.py @@ -0,0 +1,40 @@ +import unittest +import util.BehaviorQuerySystem as bqs +from behaviors.ColorChangerBehavior import * +import util.Geo as geo +class TestBQS(unittest.TestCase): + def setUp(self): + bqs.initBQS() + b = ColorChangerBehavior({'Id': 'color','ColorList':[(255,0,0)]}) + c = ColorChangerBehavior({'Id': 'color2', 'ColorList':[(0,0,255)]}) + bqs.addBehavior(b) + bqs.addBehavior(c) + b.addInput({'Location':(3,4)}) + c.addInput({'Location':(5,12)}) + b.timeStep() + c.timeStep() + def tearDown(self): + bqs.initBQS() + def test_simple_query(self): + validQuery = lambda args:args['Color']==(255,0,0) + invalidQuery = lambda args:args['Color']==(254,0,0) + assert bqs.query(validQuery) == [{'Color':(255,0,0), 'Location':(3,4)}] + assert bqs.query(invalidQuery) == [] + def test_dist_query(self): + validDist = lambda args:geo.dist(args['Location'], (0,0)) <= 5 + invalidDist = lambda args:geo.dist(args['Location'], (0,0)) <= 2 + doubleDist = lambda args:geo.dist(args['Location'], (0,0)) <= 20 + + assert bqs.query(validDist) == [{'Color':(255,0,0), 'Location':(3,4)}] + assert bqs.query(invalidDist) == [] + assert bqs.query(doubleDist) == [{'Color':(255,0,0), 'Location':(3,4)}, {'Color':(0,0,255),\ + 'Location':(5,12)}] + def test_complex_queries(self): + + validQuery = lambda args:args['Color']==(255,0,0) + doubleDist = lambda args:geo.dist(args['Location'], (0,0)) <= 20 + + twoPartPredicate = lambda args:doubleDist(args) and validQuery(args) + assert bqs.query(twoPartPredicate) == [{'Color':(255,0,0), 'Location':(3,4)}] + assert bqs.query([validQuery, doubleDist]) == [{'Color':(255,0,0), 'Location':(3,4)}] + diff --git a/tests/TestConfigLoaders.py b/tests/TestConfigLoaders.py index c79bbf1..02c8865 100644 --- a/tests/TestConfigLoaders.py +++ b/tests/TestConfigLoaders.py @@ -29,6 +29,20 @@ class TestConfigLoaders(unittest.TestCase): result.write('tests/testdata/inheritanceTESTout.xml') assert filecmp.cmp('tests/testdata/inheritanceTESTout.xml',\ 'tests/testdata/inheritanceTRUTH.xml') + #Tests our fancy new XML Eval Function + def test_eval(self): + assert Config.attemptEval('5') == 5 + assert Config.attemptEval('{5:10, 12:15}') == {5:10, 12:15} + singleLayerLambda = Config.attemptEval('${Val}$*5') + assert singleLayerLambda({'Val':2}) == 10 + doubleLayerLambda = Config.attemptEval("${Val1}$*'${Val2}$'") + assert doubleLayerLambda({'Val1':3})({'Val2':7}) == 21 + conditional = Config.attemptEval("${Val1}$*5=='${Val2}$'") + assert conditional({'Val1':5})({'Val2':25}) == True + assert conditional({'Val1':5})({'Val2':26}) == False + + onlyDouble = Config.attemptEval("'${Val1}$'*'${Val2}$'") + assert onlyDouble({})({'Val1':3, 'Val2':7}) == 21 if __name__ == '__main__': unittest.main() diff --git a/tests/__init__.py b/tests/__init__.py index e69de29..0365616 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -0,0 +1,3 @@ +from TestComponentRegistry import TestComponentRegistry +from TestConfigLoaders import TestConfigLoaders +from TestBQS import TestBQS diff --git a/util/BehaviorQuerySystem.py b/util/BehaviorQuerySystem.py new file mode 100644 index 0000000..643b95c --- /dev/null +++ b/util/BehaviorQuerySystem.py @@ -0,0 +1,39 @@ +import types +"""The behavior query system is a module that allows querying behaviors based on lambda-function +predicates.""" +def initBQS(): + global behaviorList, initialized + behaviorList = [] + initialized = True + +def addBehavior(behavior): + behaviorList.append(behavior) + +def query(predicateList): + """BehaviorQuerySystem.query takes a list of predicates (functions with signature: + (behavior,output)), and + optionally a behavior to be compared to.""" + #want to do queries wrt: behavior itself, the behavior packet, the querying behavior + if isinstance(predicateList, types.FunctionType): + predicateList = [predicateList] + elif not isinstance(predicateList, list): + raise Exception('Predicate list must be a function or list of functions') + global behaviorList, initialized + ret = [] + if not initialized: + initBQS() + + for behavior in behaviorList: #Consider every behavior + lastOutput = behavior.getLastOutput() + for output in lastOutput: #Look at every element it has output + validOutput = True + for pred in predicateList: #Evaluate every predicate. A predicate is a lambda function that + #takes a dict and returns a bool. + if not pred(output): + validOutput = False + break + if validOutput: + ret.append(output) + return ret + + diff --git a/util/ColorOps.py b/util/ColorOps.py index 4b1162a..796a902 100644 --- a/util/ColorOps.py +++ b/util/ColorOps.py @@ -40,3 +40,7 @@ def randomBrightColor(): hue, sat, val = colorsys.hsv_to_rgb(hue, sat, val) ret = [hue, sat, val] return floatToIntColor(ret) + +class Color(object): + def __init__(self, r,g,b): + self.rep = [r,g,b] diff --git a/util/ComponentRegistry.py b/util/ComponentRegistry.py index 776cd17..be913df 100644 --- a/util/ComponentRegistry.py +++ b/util/ComponentRegistry.py @@ -11,16 +11,19 @@ def clearRegistry(): initRegistry() def removeComponent(cid): - globals()['Registry'].pop(cid) + global Registry + Registry.pop(cid) def getComponent(cid): - return globals()['Registry'][cid] + global Registry + return Registry[cid] #Registry of all components of the light system #TODO: pick a graceful failure behavior and implement it def registerComponent(component, cid=None): + global Registry if cid != None: - globals()['Registry'][cid] = component + Registry[cid] = component else: try: cid = component['Id'] @@ -28,22 +31,26 @@ def registerComponent(component, cid=None): cid = getNewId() component['Id'] = cid main_log.debug(cid + 'automatically assigned') - globals()['Registry'][cid] = component + Registry[cid] = component return cid def verifyUniqueId(cid): - return not cid in globals()['Registry'] + global Registry + return not cid in Registry def removeComponent(cid): - globals()['Registry'].pop(cid) + global Registry + Registry.pop(cid) def getComponent(cid): - return globals()['Registry'][cid] + global Registry + return Registry[cid] def getNewId(): - trialKey = len(globals()['Registry']) + global Registry + trialKey = len(Registry) trialId = hashlib.md5(str(trialKey)).hexdigest() - while trialId in globals()['Registry']: + while trialId in Registry: trialKey += 1 trialId = hashlib.md5(str(trialKey)).hexdigest() return trialId diff --git a/util/Config.py b/util/Config.py index 6fdb0d4..25018a8 100644 --- a/util/Config.py +++ b/util/Config.py @@ -1,4 +1,5 @@ from xml.etree.ElementTree import * +import re import sys import xml import pdb @@ -112,8 +113,20 @@ def pullArgsFromItem(parentNode): return args def attemptEval(val): + """Runs an eval if possible, or converts into a lambda expression if indicated. Otherwise, + leaves as a string.""" try: - val = eval(val) + if '${' in val and '}$' in val: #TODO: this could be a little cleaner + dictVal = re.sub("'\$\{(.+?)\}\$'", "b['\\1']", val) #replace expressions '${blah}$' with b['blah'] + dictVal = re.sub("\$\{(.+?)\}\$", "a['\\1']", dictVal) #replace all expressions like {blah} with a['blah'] + if "'${" and "}$'" in val: #nested lambda madness + lambdaVal = eval('lambda a: lambda b: ' + dictVal) + else: + lambdaVal = eval('lambda a:'+dictVal) #TODO: nested lambdas + return lambdaVal #convert referential objects to lambda expressions which can be + #resolved dynamically. + else: + val = eval(val) except (NameError, SyntaxError): val = str(val) return val diff --git a/util/Geo.py b/util/Geo.py index 0dde80b..211e89d 100644 --- a/util/Geo.py +++ b/util/Geo.py @@ -2,8 +2,9 @@ import math from bisect import * import random -def pointWithinBoundingBox(point, bb): #this could be in 4 lines, but I'm lazy. - return sum([(point[i % 2] <= bb[i]) == (i>1) for i in range(4)]) == 4 +def pointWithinBoundingBox(point, bb): + """Returns whether or not a point (x,y) is within a bounding box (xmin, ymin, xmax, ymax)""" + return all([(point[i % 2] <= bb[i]) == (i>1) for i in range(4)]) def addLocations(l1,l2): return tuple([l1[i]+l2[i] for i in range(len(l1))]) @@ -32,3 +33,10 @@ def windtrail(x,y,height,center,width): b=center c=width return a*((math.exp(-((x-b))/(c)))**2)*(math.exp(-((y))/(0.2*c)))**2 + +class Location(object): + def __init__(self,x=0,y=0): + self.x = x + self.y = y + def __add__(self, b): + return Location(self.x+b.x, self.y+b.y) |