Source code for morse.core.object

import logging; logger = logging.getLogger("morse." + __name__)
from abc import ABCMeta, abstractmethod
from collections import OrderedDict
from morse.core.abstractobject import AbstractObject
from morse.core.exceptions import MorseRPCInvokationError

import morse.helpers.transformation
from morse.core.services import service

from morse.core import blenderapi

import time
from copy import copy
from morse.core.morse_time import time_isafter

[docs]class Object(AbstractObject): """ Basic Class for all 3D objects (components) used in the simulation. Provides common attributes. """ # Make this an abstract class __metaclass__ = ABCMeta def __init__ (self, obj, parent=None): AbstractObject.__init__(self) # Fill in the data sent as parameters self.bge_object = obj self.robot_parent = parent self.level = self.bge_object.get("abstraction_level", "default") # Variable to indicate the activation status of the component self._active = True self.check_level() # Define the position of sensors with respect # to their robot parent # TODO: implement this using morse.helpers.transformation if parent: self.relative_position = obj.getVectTo(parent.bge_object) # Create an instance of the 3d transformation class self.position_3d = morse.helpers.transformation.Transformation3d(obj) self.initialize_local_data() self.update_properties() # The actual frequency at which the action is called # The frequency of the game sensor specifies how many times # the action is skipped when the logic brick is executed. # e.g. game sensor frequency = 0 -> sensor runs at full logic rate sensors = blenderapi.getalwayssensors(obj) self._frequency = blenderapi.getfrequency() if 'frequency' in self.bge_object: self._frequency = self.bge_object['frequency'] self._last_call = None self._component_period = 1.0 / self._frequency self.__time = blenderapi.persistantstorage().time
[docs] def check_level(self): if self.level == "default": return # fine if hasattr(self, '_levels') and self.level in self._levels: return #fine msg = "Component <%s> has no abstraction level <%s>. Please check your scene." % (self.name(), self.level) logger.error(msg) raise ValueError(msg)
[docs] def initialize_local_data(self): """ Creates and initializes 'local data' fields, according to the current component abstraction level. """ all_data_fields = OrderedDict() for cls in reversed(type(self).__mro__): if hasattr(cls, '_data_fields'): all_data_fields.update(cls._data_fields) for name, details in all_data_fields.items(): default_value, type_, doc, level = details if level == "all" or self.level in level: self.local_data[name] = default_value
[docs] def fetch_properties(self): """ Returns the "_properties" of a component :return: a dictionary of the field "_properties" """ all_properties = OrderedDict() #fetches '_properties' for cls in reversed(type(self).__mro__): if hasattr(cls, '_properties'): all_properties.update(cls._properties) return all_properties
[docs] @service def get_properties(self): """ Returns the properties of a component. :return: a dictionary of the current component's properties """ all_properties = self.fetch_properties() for prop in all_properties.items(): l = list(prop[1]) l[0] = getattr(self, l[3]) all_properties[prop[0]] = tuple(l) return {'properties': all_properties}
[docs] @service def set_property(self, prop_name, prop_val): """ Modify one property on a component :param prop_name: the name of the property to modify (as shown the documentation) :param prop_val: the new value of the property. Note that there is no checking about the type of the value so be careful :return: nothing """ props = self.fetch_properties() if prop_name in props: setattr(self, props[prop_name][3], prop_val) else: raise MorseRPCInvokationError( "Property %s does not exist for object %s" % (prop_name, self.name()))
[docs] @service def get_configurations(self): """ Returns the configurations of a component (parsed from the properties). :return: a dictionary of the current component's configurations """ all_properties = self.fetch_properties() tmp = {} #parses 'all_properties' to get only "key"-"value"-pairs #"key" is python_name and "value" is default_value for item in all_properties.items(): tmp[item[0]] = getattr(self, item[1][3]) transform = self.robot_parent.position_3d.transformation3d_with(self.position_3d) rotation = [ list(vec) for vec in transform.rotation_matrix ] translation = list(transform.translation) tmp['object_to_robot'] = {'rotation': rotation, 'translation': translation} return {'configurations': tmp}
[docs] def update_properties(self): """ Takes all registered properties (see add_property), and update their values according to the values set in Blender object. """ all_properties = self.fetch_properties() for name, details in all_properties.items(): default_value, _, _, python_name = details val = default_value try: val = self.bge_object[name] except KeyError: pass setattr(self, python_name, val)
[docs] def name(self): return self.bge_object.name
[docs] def periodic_call(self): """ Return true if the component must be called on this loop call, False otherwise """ # must be called on each loop occurence if not hasattr(self, '_component_period'): return True # First call if not self._last_call: self._last_call = self.__time.time self._nb_call = 1 return True else: """ We deal with a complete simulated second to deal with frequencies that are not a fraction of the main simulator frequency. For that purpose, we integrate until self._nb_call == self._frequency, i.e. self._nb_call * self._component_period == 1.0 """ must_call = time_isafter(self.__time.time, self._last_call + self._nb_call * self._component_period) if must_call: if (self._nb_call == self._frequency): self._nb_call = 1 self._last_call = copy(self.__time.time) else: self._nb_call += 1 return must_call
[docs] def action(self): """ Call the regular action function of the component. Can be redefined in some of the subclases (sensor and actuator). """ self.default_action()
[docs] def in_zones(self, name = None, type = None): """ Determine which zone(s) contain(s) current object If a :param name: is precised, check only if this specific zone contains the position If a :param type: is precised, only search in the zone of this type. """ zone_manager = blenderapi.persistantstorage().zone_manager return zone_manager.contains(self, name = name, type = type)
[docs] @abstractmethod def default_action(self): """ Base action performed by any object. This method should be implemented by all subclasses that will be instanced (GPS, v_Omega, ATRV, etc.). """ pass
[docs] def toggle_active(self): self._active = not self._active
@property def frequency(self): """ Frequency of the object action in Hz (float). """ return self._frequency