"""
This module defines the interface for communicating with the expression.
.. autoclass:: _Expression
:members:
:undoc-members:
:show-inheritance:
"""
from __future__ import with_statement
import math
import os
import random
from functools import partial
import cmath
from opsoro.console_msg import *
from opsoro.robot import Robot
try:
import simplejson as json
except ImportError:
import json
[docs]def constrain(n, minn, maxn): return max(min(maxn, n), minn)
get_path = partial(os.path.join, os.path.abspath(os.path.dirname(__file__)))
[docs]class _Expression(object):
[docs] def __init__(self):
self._emotion = 0 + 0j
self._anim = None
self.expressions = []
self.load_config()
[docs] def set_emotion_e(self, e=0 + 0j, anim_time=-1):
"""
Set an emotion with complex number e, within a certain time.
:param complex e: complex number e
:param float anim_time: time to set the emotion
"""
# Print data to log
# print_info("Emotion; e: " + str(e) + ", time: " + str(anim_time))
# Make sure emotion is restricted to within unity circle.
if abs(e) > 1.0:
e = cmath.rect(1.0, cmath.phase(e))
self._emotion = e
phi = cmath.phase(self._emotion)
r = abs(self._emotion)
Robot.apply_poly(r, phi, anim_time)
[docs] def set_emotion_val_ar(self, valence, arousal, anim_time=-1):
"""
Set an emotion with valence and arousal, within a certain time.
:param float valence: valence
:param float arousal: arousal
:param float anim_time: time to set the emotion
"""
# Print data to log
# print_info("Set Emotion; valence: " + str(valence) + ", arousal: " +
# str(arousal) + ", time: " + str(anim_time))
e = 0 + 0j
# Emotion from valence and arousal
if valence is None or arousal is None:
raise RuntimeError("Bad combination of parameters; valence and arousal need to be provided.")
valence = constrain(valence, -1.0, 1.0)
arousal = constrain(arousal, -1.0, 1.0)
e = valence + arousal * 1j
self.set_emotion_e(e, anim_time)
[docs] def set_emotion_r_phi(self, r, phi, degrees=False, anim_time=-1):
"""
Set an emotion with r and phi, within a certain time.
:param float r: radius of the circumplex
:param float phi: angle of the circumplex
:param bool degrees: is convertion to radians needed?
:param float anim_time: time to set the emotion
"""
# Print data to log
# print_info("Set Emotion; r: " + str(r) + ", phi: " + str(phi) +
# ", deg: " + str(degrees) + ", time: " + str(anim_time))
e = 0 + 0j
# Emotion from r and phi
if r is None or phi is None:
raise RuntimeError("Bad combination of parameters; r and phi need to be provided.")
if degrees:
phi = phi * math.pi / 180.0
phi = constrain(phi, 0.0, 2 * math.pi)
r = constrain(r, 0.0, 1.0)
Robot.apply_poly(r, phi, anim_time)
[docs] def update(self):
"""
Old function, not used in new system
:return: nothing
:rtype: None
"""
# Still here for backwards compatibility
# This is done automatically
return
[docs] def get_emotion_complex(self):
"""
Returns current emotion as a complex number
:return: current emotion
:rtype: complex
"""
return self._emotion
[docs] def set_emotion_name(self, name, anim_time=-1):
"""
Set an emotion with name if defined in expression list, within a certain time.
:param string name: name of the emotion to set
:param float anim_time: time to set the emotion
"""
e = 0 + 0j
# Emotion from name in list
if name is None:
raise RuntimeError("Bad combination of parameters; name needs to be provided.")
index = 0
for exp in self.expressions:
if 'name' in exp:
if exp['name'] == name:
self.set_emotion_index(index, anim_time)
index += 1
[docs] def set_emotion_icon(self, icon, anim_time=-1):
"""
Set an emotion with icon if defined in expression list, within a certain time.
:param string icon: name of the icon to set
:param float anim_time: time to set the emotion
"""
e = 0 + 0j
# Emotion from icon in list
if icon is None:
raise RuntimeError("Bad combination of parameters; icon needs to be provided.")
index = 0
for exp in self.expressions:
if 'icon' in exp:
if exp['icon'] == icon:
self.set_emotion_index(index, anim_time)
index += 1
[docs] def set_emotion_index(self, index, anim_time=-1):
"""
Set an emotion with index in defined expression list, within a certain time.
:param integer index: index of the emotion in the list of emotions
:param float anim_time: time to set the emotion
"""
e = 0 + 0j
# Emotion from list
if index is None:
raise RuntimeError("Bad combination of parameters; index needs to be provided.")
index = constrain(index, 0, len(self.expressions) - 1)
exp = self.expressions[index]
if 'poly' in exp:
# 20 values in poly, (poly * 2*pi/20)
phi = constrain(exp['poly'] * math.pi / 10, 0.0, 2 * math.pi)
Robot.apply_poly(1.0, phi, anim_time)
if 'dofs' in exp:
# send dofs directly to the robot
Robot.set_dof_list(exp['dofs'], anim_time)
[docs] def set_emotion_random(self, all_random=True, anim_time=-1):
"""
Set an emotion with random index in defined expression list, within a certain time. Or set all dofs to a random position between -1 and 1.
:param bool all_random: all dofs random or not
:param float anim_time: time to set the emotion
"""
if all_random:
Robot.set_dof([], 2, anim_time)
else:
self.set_emotion_index(random.randint(0, self.expressions), anim_time)
[docs] def set_config(self, config=None):
if config is not None and len(config) > 0:
save_new_config = (self.expressions != config)
self.expressions = json.loads(config)
# Create all module-objects from data
if save_new_config:
self.save_config()
return self.expressions
[docs] def load_config(self, file_name='robot_expressions.conf'):
"""
Load expressions from a expressions configurations file
:param string file_name: name of the config file
:return: True if file is successfully loaded
:rtype: bool
"""
# Load modules from file
if file_name is None:
return False
try:
with open(get_path("config/" + file_name)) as f:
self.expressions = f.read()
if self.expressions is None or len(self.expressions) == 0:
print_warning("Config contains no data: " + file_name)
return False
self.set_config(self.expressions)
# print module feedback
print_info("%i expressions loaded [%s]" % (len(self.expressions), file_name))
except IOError:
self.expressions = {}
print_warning("Could not open " + file_name)
return False
return True
[docs] def save_config(self, file_name='robot_expressions.conf'):
"""
Save the current expressions configurations
:param string file_name: name of the config file
:return: True if file is successfully saved
:rtype: bool
"""
# Save modules to json file
if file_name is None:
return False
try:
with open(get_path("config/" + file_name), "w") as f:
f.write(json.dumps(self.expressions))
print_info("Expressions saved: " + file_name)
except IOError:
print_warning("Could not save " + file_name)
return False
return True
# Global instance that can be accessed by apps and scripts
Expression = _Expression()