def __init__(self): # Init motion stuff self._motionIndex = 0.0 self._motionIndexDelta = 1.0 self._motionIsCyclic = True self._motionSplineType = 'linear' # Timer for playing self._motionTimer = Timer(self) self._motionTimer.Bind(self._MotionTimerCallback)
class MotionMixin(object): """ MotionMixin(axes) A generic class that represents a wobject that has motion. This class implements the basic methods to control motion. It uses the concept of the motionIndex, which is basically the time parameter. Its value specifies the position in the motion sequence with a scalar (float) value. Some motion wobject implementations may only support integer values though, in which case they should round the motionIndex. Inheriting classes should implement _GetMotionCount() and _SetMotionIndex(index, ii, ww), where ii are four indices, and ww are four weights. Together they specify a spline. """ def __init__(self): # Init motion stuff self._motionIndex = 0.0 self._motionIndexDelta = 1.0 self._motionIsCyclic = True self._motionSplineType = 'linear' # Timer for playing self._motionTimer = Timer(self) self._motionTimer.Bind(self._MotionTimerCallback) @vv.misc.Property def motionIsCyclic(): """ Get/set whether the motion is cyclic. Default True. """ def fget(self): return self._motionIsCyclic def fset(self, value): self._motionIsCyclic = bool(value) self.motionIndex = self.motionIndex return locals() @property def motionCount(self): """ Get the number of temporal instances of this object. For objects that do not have a fixed number of elements, the returned number may be an arbitrary large number above one thousand. """ return self._GetMotionCount() @vv.misc.PropWithDraw def motionIndex(): """ Get/set the motion index; the temporal position in the motion. With N the motionCount, if the motion is cyclic, the valid range is [0, N>, otherwise [0,N-1]. If the given index is not in the valid range, its modulo is taken (cyclic) or the index is clipped (not cyclic). """ # This property is where the "reset" basically takes place def fget(self): return self._motionIndex def fset(self, value): # Make float value = float(value) # Get N and M (M is index limit) N = M = self.motionCount if not self.motionIsCyclic: M = N - 1 # Limit value and set if N < 2: self._motionIndex = 0.0 else: self._motionIndex = self._MotionCorrectIndex(value, M, True) # Create new deform array if N == 0 or N == 1: ii = 0, 0, 0, 0 ww = 1, 0, 0, 0 else: # Get index and factor i1 = int(self._motionIndex) t = self._motionIndex - i1 i0 = self._MotionCorrectIndex(i1 - 1, M, self._motionIsCyclic) i2 = self._MotionCorrectIndex(i1 + 1, M, self._motionIsCyclic) i3 = self._MotionCorrectIndex(i1 + 2, M, self._motionIsCyclic) ii = i0, i1, i2, i3 # Linear combine ww = self._MotionGetCoeff(t, self._motionSplineType) # Apply self._SetMotionIndex(self._motionIndex, ii, ww) return locals() def _MotionCorrectIndex(self, i, N, cyclic): """ _MotionCorrectIndex(i, N, cyclic) Correct the given motion index. If cyclic will take the modulo, otherwise will clip the index to the valid range. """ if cyclic: while i >= N: i -= N while i < 0: i += N else: if i > N: i = N if i < 0: i = 0 return i def _GetMotionCount(self): """ _GetMotionCount() Called to get the number of temporal instances. If there is no limit, just return a very large number above thousand. Should be overloaded. """ raise NotImplementedError() def _SetMotionIndex(self, index, ii, ww): """ _SetMotionIndex(index, ii, ww) Called when the motion index is set. Should be overloaded. """ raise NotImplementedError() def MotionPlay(self, interval=100, delta=1): """ MotionPlay(interval=100, delta=1) Start playing the motion. This starts a timer that is triggered every interval miliseconds. On every trigger the motionIndex is increased by delta. """ self._motionIndexDelta = delta self._motionTimer.Start(interval, False) def MotionStop(self): """ MotionStop() Stop playing the motion. """ self._motionTimer.Stop() def _MotionTimerCallback(self, event): """ _MotionTimerCallback(event) Callback for the motion timer. """ if self._destroyed: # Auto-stop timer if wobject is destroyed self._motionTimer.Stop() return else: # Increase deform index self.motionIndex += self._motionIndexDelta @vv.misc.Property def motionSplineType(): """ Get/set the spline type to interpolate the motion. Can be 'nearest', 'linear', 'cardinal', 'B-spline', or a float between -1 and 1 specifying the tension of the cardinal spline. Note that the B-spline is an approximating spline. Default 'linear'. Note that an implementation may not support interpolation of the temporal instances. """ def fget(self): return self._motionSplineType def fset(self, value): if isinstance(value, (float, int)): if value >= -1 and value <= 1: self._motionSplineType = float(value) else: raise ValueError( 'Tension parameter must be between -1 and 1.') elif isinstance(value, basestring): value = value.lower() if value in [ 'nearest', 'linear', 'cardinal', 'c', 'b-spline', 'b' ]: self._motionSplineType = value else: raise ValueError('Invalid spline type.') else: raise ValueError('motionSplineType must be scalar or string.') self.motionIndex = self.motionIndex return locals() def _MotionGetCoeff(self, t, spline_type): """ _MotionGetCoeff(t, splineType) Get the coefficients to interpolate the motion. """ # Accept tension parameter as spline type tension = 0.0 if isinstance(spline_type, (float, int)): tension = float(spline_type) spline_type = 'cardinal' # Get id spline_type = spline_type.lower() if spline_type in ['c', 'card', 'cardinal', 'catmull–rom']: tau = 0.5 * (1.0 - tension) w0 = -tau * (t**3 - 2 * t**2 + t) w3 = tau * (t**3 - t**2) w1 = 2 * t**3 - 3 * t**2 + 1 - w3 w2 = -2 * t**3 + 3 * t**2 - w0 elif spline_type in ['b', 'basis', 'basic']: w0 = (1 - t)**3 / 6.0 w1 = (3 * t**3 - 6 * t**2 + 4) / 6.0 w2 = (-3 * t**3 + 3 * t**2 + 3 * t + 1) / 6.0 w3 = (t)**3 / 6.0 elif spline_type in ['nn', 'nearest']: w0, w3 = 0.0, 0.0 w1 = float(t < 0.5) w2 = float(not w1) else: # Linear w0, w3 = 0.0, 0.0 w1 = (1.0 - t) w2 = float(t) # Return coefficients return w0, w1, w2, w3