Initial revision
[blender.git] / intern / python / modules / Blender / Ipo.py
1 """The Blender Ipo module
2
3 This module provides access to **Ipo** objects in Blender.
4
5 An Ipo object is a datablock of IpoCurves which control properties of
6 an object in time.
7
8 Note that IpoCurves assigned to rotation values (which must be specified
9 in radians) appear scaled in the IpoWindow (which is in fact true, due
10 to the fact that conversion to an internal unit of 10.0 angles happens).
11
12 Example::
13   
14   from Blender import Ipo, Object
15
16   ipo = Ipo.New('Object', 'ObIpo')                   # Create object ipo with name 'ObIpo'
17   curve = ipo.addCurve('LocY')                       # add IpoCurve for LocY
18   curve.setInterpolation('Bezier')                   # set interpolation type
19   curve.setExtrapolation('CyclicLinear')             # set extrapolation type
20
21   curve.addBezier((0.0, 0.0))                        # add automatic handle bezier point
22   curve.addBezier((20.0, 5.0), 'Free', (10.0, 4.0))  # specify left handle, right auto handle
23   curve.addBezier((30.0, 1.0), 'Vect')               # automatic split handle
24   curve.addBezier((100.0, 1.0))                      # auto handle
25
26   curve.update()                                     # recalculate curve handles
27
28   curve.eval(35.0)                                   # evaluate curve at 35.0
29
30   ob = Object.get('Plane')
31   ob.setIpo(ipo)                                     # assign ipo to object
32 """
33
34 import _Blender.Ipo as _Ipo
35
36 import shadow
37
38 _RotIpoCurves = ["RotX", "RotY", "RotZ", "dRotX", "dRotY", "dRotZ"]
39
40 _radian_factor = 5.72957814 # 18.0 / 3.14159255
41
42 def _convertBPoint(b):
43         f = _radian_factor
44         newb = BezierPoint() 
45         p = b.pt
46         q = newb.pt
47         q[0], q[1] = (p[0], f * p[1])
48         p = b.h1
49         q = newb.h1
50         q[0], q[1] = (p[0], f * p[1])
51         p = b.h2
52         q = newb.h2
53         q[0], q[1] = (p[0], f * p[1])
54         return newb
55
56
57 class IpoBlock(shadow.shadowEx):
58         """Wrapper for Blender Ipo DataBlock
59
60   Attributes
61   
62     curves -- list of owned IpoCurves
63 """
64         def get(self, channel = None):
65                 """Returns curve with channel identifier 'channel', which is one of the properties
66 listed in the Ipo Window, 'None' if not found.
67 If 'channel' is not specified, all curves are returned in a list"""
68                 if channel:
69                         for c in self._object.curves:
70                                 if c.name == channel:
71                                         return IpoCurve(c)
72                         return None
73                 else:
74                         return map(lambda x: IpoCurve(x), self._object.curves)
75
76         def __getitem__(self, k):
77                 """Emulates dictionary syntax, e.g. ipocurve = ipo['LocX']"""
78                 curve = self.get(k)
79                 if not curve:
80                         raise KeyError, "Ipo does not have a curve for channel %s" % k
81                 return curve            
82
83         def __setitem__(self, k, val):
84                 """Emulates dictionary syntax, e.g. ipo['LocX'] = ipocurve"""
85                 c = self.addCurve(k, val)
86                 
87         has_key = get # dict emulation
88
89         items = get   # dict emulation
90
91         def keys(self):
92                 return map(lambda x: x.name, self.get())
93
94         def addCurve(self, channel, curve = None):
95                 """Adds a curve of channel type 'channel' to the Ipo Block. 'channel' must be one of
96 the object properties listed in the Ipo Window. If 'curve' is not specified,
97 an empty curve is created, otherwise, the existing IpoCurve 'curve' is copied and
98 added to the IpoBlock 'self'.
99 In any case, the added curve is returned.
100 """             
101                 if curve:
102                         if curve.__class__.__name__ != "IpoCurve":
103                                 raise TypeError, "IpoCurve expected"
104                         c = self._object.addCurve(channel, curve._object)
105
106                         ### RotIpo conversion hack
107                         if channel in _RotIpoCurves:
108                                 print "addCurve, converting", curve.name
109                                 c.points = map(_convertBPoint, curve.bezierPoints)
110                         else:
111                                 c.points = curve.bezierPoints
112                 else:   
113                         c = self._object.addCurve(channel)
114                 return IpoCurve(c)
115
116         _getters = { 'curves' : get }
117
118 class BezierPoint:
119         """BezierPoint object
120
121   Attributes
122   
123     pt   -- Coordinates of the Bezier point
124
125     h1   -- Left handle coordinates
126
127     h2   -- Right handle coordinates
128
129     h1t  -- Left handle type (see IpoCurve.addBezier(...) )
130
131     h2t  -- Right handle type
132 """
133
134 BezierPoint = _Ipo.BezTriple # override
135
136 class IpoCurve(shadow.shadowEx):
137         """Wrapper for Blender IpoCurve
138
139   Attributes
140
141     bezierPoints -- A list of BezierPoints (see class BezierPoint),
142     defining the curve shape
143 """
144
145         InterpolationTypes = _Ipo.InterpolationTypes
146         ExtrapolationTypes = _Ipo.ExtrapolationTypes
147
148         def __init__(self, object):
149                 self._object = object
150                 self.__dict__['bezierPoints'] = self._object.points
151
152         def __getitem__(self, k):
153                 """Emulate a sequence of BezierPoints"""
154                 print k, type(k)
155                 return self.bezierPoints[k]
156
157         def __repr__(self):
158                 return "[IpoCurve %s]" % self.name
159
160         def __len__(self):
161                 return len(self.bezierPoints)
162
163         def eval(self, time):
164                 """Returns float value of curve 'self' evaluated at time 'time' which
165 must be a float."""
166                 return self._object.eval(time)
167
168         def addBezier(self, p, leftType = 'Auto', left = None, rightType = None, right = None):
169                 """Adds a Bezier triple to the IpoCurve.
170
171 The following values are float tuples (x,y), denoting position of a control vertex:
172
173 p     -- The position of the Bezier point
174
175 left  -- The position of the leftmost handle
176
177 right -- The position of the rightmost handle
178
179 'leftType', 'rightType' must be one of:
180
181 "Auto"  --  automatic handle calculation. In this case, 'left' and 'right' don't need to be specified
182
183 "Vect"  --  automatic split handle calculation. 'left' and 'right' are disregarded.
184
185 "Align" --  Handles are aligned automatically. In this case, 'right' does not need to be specified.
186
187 "Free"  --  Handles can be set freely - this requires both arguments 'left' and 'right'.
188
189 """
190
191                 b = _Ipo.BezTriple()
192                 b.pt[0], b.pt[1] = (p[0], p[1])
193                 b.h1t = leftType
194
195                 if rightType:
196                         b.h2t = rightType
197                 else:
198                         b.h2t = leftType
199
200                 if left:
201                         b.h1[0], b.h1[1] = (left[0], left[1])
202
203                 if right:       
204                         b.h2[0], b.h2[1] = (right[0], right[1])
205
206                 self.__dict__['bezierPoints'].append(b)
207                 return b
208
209         def update(self, noconvert = 0):
210                 # This is an ugly fix for the 'broken' storage of Rotation
211                 # ipo values. The angles are stored in units of 10.0 degrees,
212                 # which is totally inconsistent with anything I know :-)
213                 # We can't (at the moment) change the internals, so we
214                 # apply a conversion kludge..
215                 if self._object.name in _RotIpoCurves and not noconvert:
216                         points = map(_convertBPoint, self.bezierPoints)
217                 else:
218                         points = self.bezierPoints
219                 self._object.points = points
220                 self._object.update()
221
222         def getInterpolationType(self, ipotype):
223                 "Returns the Interpolation type - see also IpoCurve.InterpolationTypes"
224                 return self._object.getInterpolationType()
225                 
226         def setInterpolationType(self, ipotype):
227                 """Sets the interpolation type which must be one of IpoCurve.InterpolationTypes"""
228                 try:
229                         self._object.setInterpolationType(ipotype)
230                 except:
231                         raise TypeError, "must be one of %s" % self.InterpolationTypes.keys()
232
233         def getExtrapolationType(self, ipotype):
234                 "Returns the Extrapolation type - see also IpoCurve.ExtrapolationTypes"
235                 return self._object.getExtrapolationType()
236
237         def setExtrapolationType(self, ipotype):
238                 """Sets the interpolation type which must be one of IpoCurve.ExtrapolationTypes"""
239                 try:
240                         self._object.setInterpolationType(ipotype)
241                 except:
242                         raise TypeError, "must be one of %s" % self.ExtrapolationTypes.keys()
243         
244
245 def New(blocktype, name = None):
246         """Returns a new IPO block of type 'blocktype' which must be one of:
247 ["Object", "Camera", "World", "Material"]
248 """
249         if name:
250                 i = _Ipo.New(blocktype, name)
251         else:
252                 i = _Ipo.New(blocktype)
253         return IpoBlock(i)
254
255 def Eval(ipocurve, time):  # emulation code
256         """This function is just there for compatibility. 
257 Use IpoCurve.eval(time) instead"""
258         return ipocurve.eval(time)
259
260 def Recalc(ipocurve):  # emulation code
261         """This function is just there for compatibility. Note that Ipos
262 assigned to rotation values will *not* get converted to the proper
263 unit of radians.
264 In the new style API, use IpoCurve.update() instead"""
265         return ipocurve.update(1)
266
267 def get(name = None):
268         """If 'name' given, the Ipo 'name' is returned if existing, 'None' otherwise.
269 If no name is given, a list of all Ipos is returned"""
270         if name:
271                 ipo = _Ipo.get(name)    
272                 if ipo:
273                         return IpoBlock(ipo)
274                 else:
275                         return None
276         else:
277                 return shadow._List(_Ipo.get(), IpoBlock)
278
279 Get = get # emulation