File: bender.py

package info (click to toggle)
mutatormath 3.0.1-4
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 1,820 kB
  • sloc: python: 2,581; makefile: 10
file content (202 lines) | stat: -rw-r--r-- 8,195 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202

import sys
from mutatorMath.objects.error import MutatorError
from mutatorMath.objects.location import Location, biasFromLocations
import mutatorMath.objects.mutator

def noBend(loc): return loc

class WarpMutator(mutatorMath.objects.mutator.Mutator):
    def __call__(self, value):
        if isinstance(value, tuple):
            # handle split location
            return self.makeInstance(Location(w=value[0])), self.makeInstance(Location(w=value[1]))
        return self.makeInstance(Location(w=value))

"""

    A warpmap is a list of tuples that describe non-linear behaviour
    for a single dimension in a designspace.

    Bender is an object that accepts warpmaps and transforms
    locations accordingly.

    For instance:
        w = {'a': [(0, 0), (500, 200), (1000, 1000)]}
        b = Bender(w)
        assert b(Location(a=0)) == Location(a=0)
        assert b(Location(a=250)) == Location(a=100)
        assert b(Location(a=500)) == Location(a=200)
        assert b(Location(a=750)) == Location(a=600)
        assert b(Location(a=1000)) == Location(a=1000)

    A Mutator can use a Bender to transform the locations
    for its masters as well as its instances.
    Great care has to be taken not to mix up transformed / untransformed.
    So the changes in Mutator are small.
   
"""
class Bender(object):
    # object with a dictionary of warpmaps
    # call instance with a location to bend it
    def __init__(self, axes):
        # axes dict:
        #   { <axisname>: {'map':[], 'minimum':0, 'maximum':1000, 'default':0, 'tag':'aaaa', 'name':"longname"}}
        warpDict = {}
        self.maps = {}    # not needed?
        self.warps = {}
        for axisName, axisAttributes in axes.items():
            mapData = axisAttributes.get('map', [])
            if type(mapData)==list:
                if mapData==0:
                    # this axis has no bender
                    self.warps[axisName] = None
                else:
                    self._makeWarpFromList(axisName, mapData, axisAttributes['minimum'], axisAttributes['maximum'])
            elif hasattr(mapData, '__call__'):
                self.warps[axisName] = mapData
    
    def __repr__(self):
        return "<Bender %s>"%(str(self.warps.items()))

    def getMap(self, axisName):
        return self.maps.get(axisName, [])
            
    def _makeWarpFromList(self, axisName, warpMap, minimum, maximum):
        if not warpMap:
            warpMap = [(minimum,minimum), (maximum,maximum)]
        self.warps[axisName] = warpMap
        # check for the extremes, add if necessary
        if not sum([a==minimum for a, b in warpMap]):
            warpMap = [(minimum,minimum)] + warpMap
        if not sum([a==maximum for a, b in warpMap]):
            warpMap.append((maximum,maximum))
        items = []
        for x, y in warpMap:
            items.append((Location(w=x), y))
        m = WarpMutator()
        items.sort()
        bias = biasFromLocations([loc for loc, obj in items], True)
        m.setBias(bias)
        n = None
        ofx = []
        onx = []
        for loc, obj in items:
            if (loc-bias).isOrigin():
                m.setNeutral(obj)
                break
        if m.getNeutral() is None:
            raise MutatorError("Did not find a neutral for this system", m)
        for loc, obj in items:
            lb = loc-bias
            if lb.isOrigin(): continue
            if lb.isOnAxis():
                onx.append((lb, obj-m.getNeutral()))
            else:
                ofx.append((lb, obj-m.getNeutral()))
        for loc, obj in onx:
            m.addDelta(loc, obj, punch=False,  axisOnly=True)
        for loc, obj in ofx:
            m.addDelta(loc, obj, punch=True,  axisOnly=True)
        self.warps[axisName] = m

    def __call__(self, loc):
        # bend a location according to the defined warps
        new = loc.copy()
        for dim, warp in self.warps.items():
            if warp is None:
                new[dim] = loc[dim]
                continue
            if not dim in loc: continue
            try:
                new[dim] = warp(loc.get(dim))
            except:
                ex_type, ex, tb = sys.exc_info()
                raise MutatorError("A warpfunction \"%s\" (for axis \"%s\") raised \"%s\" at location %s"%(str(warp), dim, ex, loc.asString()), loc)
        return new

if __name__ == "__main__":
    # no bender
    assert noBend(Location(a=1234)) == Location(a=1234)
    assert noBend(Location(a=(12,34))) == Location(a=(12,34))

    # linear map, single axis
    w = {'aaaa':{'map': [(0, 0), (1000, 1000)], 'name':'aaaaAxis', 'tag':'aaaa', 'minimum':0, 'maximum':1000, 'default':0}}
    b = Bender(w)
    assert b(Location(aaaa=0)) == Location(aaaa=0)
    assert b(Location(aaaa=500)) == Location(aaaa=500)
    assert b(Location(aaaa=1000)) == Location(aaaa=1000)

    # linear map, single axis
    #w = {'a': [(0, 100), (1000, 900)]}
    w = {'aaaa':{'map': [(0, 100), (1000, 900)], 'name':'aaaaAxis', 'tag':'aaaa', 'minimum':0, 'maximum':1000, 'default':0}}
    b = Bender(w)
    assert b(Location(aaaa=0)) == Location(aaaa=100)
    assert b(Location(aaaa=500)) == Location(aaaa=500)
    assert b(Location(aaaa=1000)) == Location(aaaa=900)

    # linear map, single axis, not mapped to 1000
    #w = {'a': [(0, 100), (1000, 900)]}
    w = {'aaaa':{'map': [(-1, 2), (0,0), (1, 2)], 'name':'aaaaAxis', 'tag':'aaaa', 'minimum':-1, 'maximum':1, 'default':0}}
    b = Bender(w)
    assert b(Location(aaaa=(-1, 1))) == Location(aaaa=(2,2))
    assert b(Location(aaaa=-1)) == Location(aaaa=2)
    assert b(Location(aaaa=-0.5)) == Location(aaaa=1)
    assert b(Location(aaaa=0)) == Location(aaaa=0)
    assert b(Location(aaaa=0.5)) == Location(aaaa=1)
    assert b(Location(aaaa=1)) == Location(aaaa=2)

    # one split map, single axis
    #w = {'a': [(0, 0), (500, 200), (600, 600)]}
    w = {'aaaa':{'map': [(0, 100), (500, 200), (600, 600)], 'name':'aaaaAxis', 'tag':'aaaa', 'minimum':0, 'maximum':600, 'default':0}}
    b = Bender(w)
    assert b(Location(aaaa=(100, 200))) == Location(aaaa=(120,140))
    assert b(Location(aaaa=0)) == Location(aaaa=100)
    assert b(Location(aaaa=250)) == Location(aaaa=150)
    assert b(Location(aaaa=500)) == Location(aaaa=200)
    assert b(Location(aaaa=600)) == Location(aaaa=600)
    assert b(Location(aaaa=750)) == Location(aaaa=1200)
    assert b(Location(aaaa=1000)) == Location(aaaa=2200)

    # implicit extremes
    w = {'aaaa':{'map': [(500, 200)], 'name':'aaaaAxis', 'tag':'aaaa', 'minimum':0, 'maximum':600, 'default':0}}
    b = Bender(w)
    assert b(Location(aaaa=(250, 100))) == Location(aaaa=(100, 40))
    assert b(Location(aaaa=0)) == Location(aaaa=0)
    assert b(Location(aaaa=250)) == Location(aaaa=100)
    assert b(Location(aaaa=500)) == Location(aaaa=200)
    assert b(Location(aaaa=600)) == Location(aaaa=600)
    assert b(Location(aaaa=750)) == Location(aaaa=1200)
    assert b(Location(aaaa=1000)) == Location(aaaa=2200)

    # now with warp functions
    # warp functions must be able to handle split tuples
    def warpFunc_1(value):
        if isinstance(value, tuple):
            return value[0]*2, value[1]*2
        return value * 2
    def warpFunc_2(value):
        if isinstance(value, tuple):
            return value[0] ** 2, value[1] ** 2
        return value ** 2
    def warpFunc_Error(value):
        return 1/0

    w = {   'aaaa':{'map': warpFunc_1, 'name':'aaaaAxis', 'tag':'aaaa', 'minimum':0, 'maximum':1000, 'default':0},
            'bbbb':{'map': warpFunc_2, 'name':'bbbbAxis', 'tag':'bbbb', 'minimum':0, 'maximum':1000, 'default':0},
        }
    # w = {'a': warpFunc_1, 'b': warpFunc_2, 'c': warpFunc_Error}
    b = Bender(w)
    assert b(Location(aaaa=(100, -100))) == Location(aaaa=(200.000,-200.000))
    assert b(Location(aaaa=100)) == Location(aaaa=200)
    assert b(Location(bbbb=100)) == Location(bbbb=10000)

    # # see if the errors are caught and reported:
    try:
        b(Location(c=-1))
    except:
        ex_type, ex, tb = sys.exc_info()
        err = 'A warpfunction "warpFunc_Error" (for axis "c") raised "integer division or modulo by zero" at location c:-1'
        assert ex.msg == err