You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

anim.py 8.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. # Copyright (c) 2016 Anki, Inc.
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License in the file LICENSE.txt or at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. '''
  15. Animation related classes, functions, events and values.
  16. '''
  17. # __all__ should order by constants, event classes, other classes, functions.
  18. __all__ = ['EvtAnimationsLoaded', 'EvtAnimationCompleted',
  19. 'Animation', 'AnimationTrigger', 'AnimationNames', 'Triggers',
  20. 'animation_completed_filter']
  21. import collections
  22. from . import logger
  23. from . import action
  24. from . import exceptions
  25. from . import event
  26. from ._clad import _clad_to_engine_iface, _clad_to_engine_cozmo
  27. class EvtAnimationsLoaded(event.Event):
  28. '''Triggered when animations names have been received from the engine'''
  29. class EvtAnimationCompleted(action.EvtActionCompleted):
  30. '''Triggered when an animation completes.'''
  31. animation_name = "The name of the animation or trigger that completed"
  32. class Animation(action.Action):
  33. '''An Animation describes an actively-playing animation on a robot.'''
  34. def __init__(self, anim_name, loop_count, ignore_body_track=False,
  35. ignore_head_track=False, ignore_lift_track=False, **kw):
  36. super().__init__(**kw)
  37. #: The name of the animation that was dispatched
  38. self.anim_name = anim_name
  39. #: The number of iterations the animation was requested for
  40. self.loop_count = loop_count
  41. #: bool: True to ignore the body track (i.e. the wheels / treads)
  42. self.ignore_body_track = ignore_body_track
  43. #: bool: True to ignore the head track
  44. self.ignore_head_track = ignore_head_track
  45. #: bool: True to ignore the lift track
  46. self.ignore_lift_track = ignore_lift_track
  47. def _repr_values(self):
  48. all_tracks = {"body":self.ignore_body_track,
  49. "head":self.ignore_head_track,
  50. "lift":self.ignore_lift_track}
  51. ignore_tracks = [k for k, v in all_tracks.items() if v]
  52. return "anim_name=%s loop_count=%s ignore_tracks=%s" % (self.anim_name, self.loop_count, str(ignore_tracks))
  53. def _encode(self):
  54. return _clad_to_engine_iface.PlayAnimation(animationName=self.anim_name, numLoops=self.loop_count, ignoreBodyTrack=self.ignore_body_track,
  55. ignoreHeadTrack=self.ignore_head_track, ignoreLiftTrack=self.ignore_lift_track)
  56. def _dispatch_completed_event(self, msg):
  57. self._completed_event = EvtAnimationCompleted(
  58. action=self, state=self._state,
  59. animation_name=self.anim_name)
  60. self.dispatch_event(self._completed_event)
  61. class AnimationTrigger(action.Action):
  62. '''An AnimationTrigger represents a playing animation trigger.
  63. Asking Cozmo to play an AnimationTrigger causes him to pick one of the
  64. animations represented by the group.
  65. '''
  66. def __init__(self, trigger, loop_count, use_lift_safe, ignore_body_track,
  67. ignore_head_track, ignore_lift_track, **kw):
  68. super().__init__(**kw)
  69. #: An attribute of :class:`cozmo.anim.Triggers`: The animation trigger dispatched.
  70. self.trigger = trigger
  71. #: int: The number of iterations the animation was requested for
  72. self.loop_count = loop_count
  73. #: bool: True to automatically ignore the lift track if Cozmo is carrying a cube.
  74. self.use_lift_safe = use_lift_safe
  75. #: bool: True to ignore the body track (i.e. the wheels / treads)
  76. self.ignore_body_track = ignore_body_track
  77. #: bool: True to ignore the head track
  78. self.ignore_head_track = ignore_head_track
  79. #: bool: True to ignore the lift track
  80. self.ignore_lift_track = ignore_lift_track
  81. def _repr_values(self):
  82. all_tracks = {"body":self.ignore_body_track,
  83. "head":self.ignore_head_track,
  84. "lift":self.ignore_lift_track}
  85. ignore_tracks = [k for k, v in all_tracks.items() if v]
  86. return "trigger=%s loop_count=%s ignore_tracks=%s use_lift_safe=%s" % (
  87. self.trigger.name, self.loop_count, str(ignore_tracks), self.use_lift_safe)
  88. def _encode(self):
  89. return _clad_to_engine_iface.PlayAnimationTrigger(
  90. trigger=self.trigger.id, numLoops=self.loop_count,
  91. useLiftSafe=self.use_lift_safe, ignoreBodyTrack=self.ignore_body_track,
  92. ignoreHeadTrack=self.ignore_head_track, ignoreLiftTrack=self.ignore_lift_track)
  93. def _dispatch_completed_event(self, msg):
  94. self._completed_event = EvtAnimationCompleted(
  95. action=self, state=self._state,
  96. animation_name=self.trigger.name)
  97. self.dispatch_event(self._completed_event)
  98. class AnimationNames(event.Dispatcher, set):
  99. '''Holds the set of animation names (strings) returned from the Engine.
  100. Animation names are dynamically retrieved from the engine when the SDK
  101. connects to it, unlike :class:`Triggers` which are defined at runtime.
  102. '''
  103. def __init__(self, conn, **kw):
  104. super().__init__(self, **kw)
  105. self._conn = conn
  106. self._loaded = False
  107. def __contains__(self, key):
  108. if not self._loaded:
  109. raise exceptions.AnimationsNotLoaded("Animations not yet received from engine")
  110. return super().__contains__(key)
  111. def __hash__(self):
  112. # We want to compare AnimationName instances rather than the
  113. # names they contain
  114. return id(self)
  115. def refresh(self):
  116. '''Causes the list of animation names to be re-requested from the engine.
  117. Attempting to play an animation while the list is refreshing will result
  118. in an AnimationsNotLoaded exception being raised.
  119. Generates an EvtAnimationsLoaded event once completed.
  120. '''
  121. self._loaded = False
  122. self.clear()
  123. self._conn.send_msg(_clad_to_engine_iface.RequestAvailableAnimations())
  124. @property
  125. def is_loaded(self):
  126. '''bool: True if the animation names have been received from the engine.'''
  127. return self._loaded != False
  128. async def wait_for_loaded(self, timeout=None):
  129. '''Wait for the animation names to be loaded from the engine.
  130. Returns:
  131. The :class:`EvtAnimationsLoaded` instance once loaded
  132. Raises:
  133. :class:`asyncio.TimeoutError`
  134. '''
  135. if self._loaded:
  136. return self._loaded
  137. return await self.wait_for(EvtAnimationsLoaded, timeout=timeout)
  138. def _recv_msg_animation_available(self, evt, msg):
  139. name = msg.animName
  140. self.add(name)
  141. def _recv_msg_end_of_message(self, evt, msg):
  142. if not self._loaded:
  143. logger.debug("%d animations loaded", len(self))
  144. self._loaded = evt
  145. self.dispatch_event(EvtAnimationsLoaded)
  146. # generate names for each CLAD defined trigger
  147. _AnimTrigger = collections.namedtuple('_AnimTrigger', 'name id')
  148. class Triggers:
  149. """Playing an animation trigger causes the game engine play an animation of a particular type.
  150. The engine may pick one of a number of actual animations to play based on
  151. Cozmo's mood or emotion, or with random weighting. Thus playing the same
  152. trigger twice may not result in the exact same underlying animation playing
  153. twice.
  154. To play an exact animation, use play_anim with a named animation.
  155. This class holds the set of defined animations triggers to pass to play_anim_trigger.
  156. """
  157. trigger_list = []
  158. for (_name, _id) in _clad_to_engine_cozmo.AnimationTrigger.__dict__.items():
  159. if not _name.startswith('_'):
  160. trigger = _AnimTrigger(_name, _id)
  161. setattr(Triggers, _name, trigger)
  162. Triggers.trigger_list.append(trigger)
  163. def animation_completed_filter():
  164. '''Creates an :class:`cozmo.event.Filter` to wait specifically for an animation completed event.'''
  165. return event.Filter(action.EvtActionCompleted,
  166. action=lambda action: isinstance(action, Animation))