Source code for morse.blender.main

import logging; logger = logging.getLogger("morse." + __name__)
from morse.helpers.morse_logging import SECTION, ENDSECTION
import sys
import os
import imp
from subprocess import Popen, PIPE

# Force the full import of blenderapi so python computes correctly all
# values in its  namespace
import morse.core.blenderapi

# force a reload, since 'blenderapi' may have been already loaded
# *outside* the GameEngine
imp.reload(morse.core.blenderapi)
persistantstorage = morse.core.blenderapi.persistantstorage()

# The service management
from morse.core.services import MorseServices
from morse.core.sensor import Sensor
from morse.core.actuator import Actuator
from morse.core.modifier import register_modifier
from morse.helpers.loading import create_instance, create_instance_level
from morse.core.morse_time import TimeStrategies
from morse.core.zone import ZoneManager

# Override the default Python exception handler
[docs]def morse_excepthook(*args, **kwargs): logger.error("[ERROR][MORSE] Uncaught exception, quit Blender.", exc_info = tuple(args)) # call default python exception hook # on Ubuntu/Python3.4 sys.excepthook is overriden by `apport_excepthook` sys.__excepthook__(*args, **kwargs) import os os._exit(-1)
# Uncaught exception quit BGE sys.excepthook = morse_excepthook # Constants for stream directions IN = 'IN' OUT = 'OUT' # The file component_config.py is at the moment included # in the .blend file of the scene try: import component_config except ImportError as detail: logger.warning("%s.\nNo datastream/services/modifiers will be configured." "\nMake sure the script 'component_config.py' is present" "in the .blend file." % detail) MULTINODE_SUPPORT = False # The file multinode_config.py is at the moment included # in the .blend file of the scene # Used to setup the multinode information try: import multinode_config MULTINODE_SUPPORT = True except ImportError as detail: logger.info("No multi-node scene configuration file found. " "Multi-node support disabled.") from morse.core.exceptions import MorseServiceError
[docs]def no_op(): pass
def _associate_child_to_robot(obj, robot_instance, unset_default): """ Reference the link of all obj to their associated robot_instance. If it is an external robot_instance, unset default_action """ # Create an empty list for the components of this robot robot_instance.components = [] for child in obj.childrenRecursive: try: # Look for the components tagged as such child['Component_Tag'] except KeyError: continue robot_instance.components.append(child) if not 'classpath' in child: logger.error("No 'classpath' in child %s\n Please make sure you " "are using the new builder classes"%str(child.name)) return False # Create an instance of the component class # and add it to the component list of persistantstorage() instance = create_instance_level(child['classpath'], child.get('abstraction_level'), child, robot_instance) if instance: persistantstorage.componentDict[child.name] = instance else: logger.error("INITIALIZATION ERROR: the component '%s'" " could not be properly initialized. Error when " "creating the class instance", obj.name) return False # Unset the default action of components of external robots if unset_default: instance.default_action = no_op logger.info("Component " + child.name + " disabled: parent " \ + obj.name + " is an External robot.") else: logger.info("Component %s %s added to %s" % (child.name, "(level: %s)" % child.get("abstraction_level") \ if child.get("abstraction_level") else "", obj.name) ) return True # Create a list of the robots in the scene
[docs]def create_dictionaries (): """Creation of a list of all the robots and components in the scene. Uses the properties of the objects to determine what they are.""" # Create a dictionary that stores initial positions of all objects # in the simulation, used to reset the simulation. persistantstorage.blender_objects = {} # Create a dictionary of the components in the scene persistantstorage.componentDict = {} # Create a dictionary of the robots in the scene persistantstorage.robotDict = {} # Create a dictionary of the external robots in the scene # Used for the multi-node simulation persistantstorage.externalRobotDict = {} # Create a dictionnary with the passive, but interactive (ie, with an # 'Object' property) objects in the scene. persistantstorage.passiveObjectsDict = {} # Create a dictionary with the modifiers persistantstorage.modifierDict = {} # Create a dictionary with the datastream interfaces used persistantstorage.stream_managers = {} # this dictionary stores, for each components, the direction and the # configured datastream interfaces. Direction is 'IN' for streams # that are read by MORSE (typically, for actuators), and 'OUT' # for streams published by MORSE (typically, for sensors) persistantstorage.datastreams = {} # Create a dictionnary with the overlaid used persistantstorage.overlayDict = {} # Create a dictionnary for the 'service object', such as supervision persistantstorage.serviceObjectDict = {} # Create the 'request managers' manager persistantstorage.morse_services = MorseServices() # Create the zone manager persistantstorage.zone_manager = ZoneManager() scene = morse.core.blenderapi.scene() # Store the position and orientation of all objects for obj in scene.objects: if obj.parent is None: import mathutils pos = mathutils.Vector(obj.worldPosition) ori = mathutils.Matrix(obj.worldOrientation) persistantstorage.blender_objects[obj] = [pos, ori] # Get the list of passive interactive objects. # These objects have a 'Object' property set to true # (plus several other optional properties). # See the documentation for the up-to-date list # (doc/morse/user/others/passive_objects.rst) -- or read the code below :-) for obj in scene.objects: # Check the object has an 'Object' property set to true if 'Object' in obj and obj['Object']: details = { 'label': obj['Label'] if 'Label' in obj else str(obj), 'description': obj['Description'] if 'Description' in obj else "", 'type': obj['Type'] if 'Type' in obj else "Object", 'graspable': obj['Graspable'] if 'Graspable' in obj else False } persistantstorage.passiveObjectsDict[obj] = details logger.info("Added {name} as a {graspable}active object".format( name = details['label'], graspable = "graspable " if details['graspable'] else "")) if not persistantstorage.passiveObjectsDict: logger.info("No passive objects in the scene.") # Get the robots for obj in scene.objects: if 'Robot_Tag' in obj or 'External_Robot_Tag' in obj: if not 'classpath' in obj: logger.error("No 'classpath' in %s\n Please make sure you are " "using the new builder classes"%str(obj.name)) return False # Create an object instance and store it instance = create_instance_level(obj['classpath'], obj.get('abstraction_level'), obj) if not instance: logger.error("Could not create %s"%str(obj['classpath'])) return False # store instance in persistant storage dictionary if 'Robot_Tag' in obj: persistantstorage.robotDict[obj] = instance else: persistantstorage.externalRobotDict[obj] = instance if not (persistantstorage.robotDict or persistantstorage.externalRobotDict): # No robot! logger.error("INITIALIZATION ERROR: no robot in your simulation!" "Do not forget that components _must_ belong to a" "robot (you can not have free objects)") return False # Get the zones for obj in scene.objects: if 'Zone_Tag' in obj: persistantstorage.zone_manager.add(obj) # Get the robot and its instance for obj, robot_instance in persistantstorage.robotDict.items(): if not _associate_child_to_robot(obj, robot_instance, False): return False # Get the external robot and its instance for obj, robot_instance in persistantstorage.externalRobotDict.items(): if not _associate_child_to_robot(obj, robot_instance, True): return False # Check we have no 'free' component (they all must belong to a robot) for obj in scene.objects: try: obj['Component_Tag'] if obj.name not in persistantstorage.componentDict.keys(): logger.error("INITIALIZATION ERROR: the component '%s' " "does not belong to any robot: you need to fix " "that by parenting it to a robot." % obj.name) return False except KeyError as detail: pass # Will return true always (for the moment) return True
[docs]def check_dictionaries(): """ Print the contents of the robot and component dictionaries.""" logger.info ("") logger.info("------------------------------------") logger.info("- SIMULATION SUMMARY -") logger.info("------------------------------------") logger.info("Robots in the simulation:") for obj, robot_instance in persistantstorage.robotDict.items(): logger.info("\tROBOT: '{0}'".format(obj)) for component in robot_instance.components: logger.info ("\t\t- Component: '{0}'".format(component)) if MULTINODE_SUPPORT: logger.info ("External robots (from other simulation nodes):") for obj, robot_position in persistantstorage.externalRobotDict.items(): logger.info ("\tROBOT: '{0}'".format(obj)) logger.info ("Available services:") if persistantstorage.morse_services.request_managers(): for name, instance in persistantstorage.morse_services.request_managers().items(): logger.info ("\t- Interface {0}".format(name)) for component, service in instance.services().items(): logger.info ("\t\t- %s: %s" % (component,service)) else: logger.info ("\tNone") logger.info ("Modifiers in use:") if persistantstorage.modifierDict: for obj, modifier_variables in persistantstorage.modifierDict.items(): logger.info ("\t- '{0}'".format(obj)) else: logger.info ("\tNone") logger.info ("") if persistantstorage.stream_managers: logger.info ("Datastream interfaces configured:") for key in persistantstorage.stream_managers.keys(): logger.info ("\t- '%s'" % key) logger.info("------------------------------------") logger.info ("")
[docs]def load_datastream_manager(datastream_name): datastream_instance = persistantstorage.stream_managers.get(datastream_name, None) if not datastream_instance: kwargs = component_config.stream_manager.get(datastream_name, {}) try: datastream_instance = create_instance(datastream_name, None, kwargs) except Exception as e: logger.error("Catched exception %s in the construction of %s" % (e, datastream_name)) return None if datastream_instance: persistantstorage.stream_managers[datastream_name] = datastream_instance logger.info("\tDatastream interface '%s' created" % datastream_name) else: logger.error("INITIALIZATION ERROR: Datastream '%s' module" " could not be found! \n" " Could not import modules required for the " "desired datastream interface. Check that " "they can be found inside your PYTHONPATH " "variable." % datastream_name) return None return datastream_instance
[docs]def load_overlays(): """ Read and initialize overlays from the configuration script. """ try: overlays_list = component_config.overlays except (AttributeError, NameError) as detail: # Exit gracefully if there are no services specified logger.info("No overlay section found in configuration file.") return True for request_manager_name, overlays in overlays_list.items(): for overlaid_name, overlay_details in overlays.items(): overlay_name, kwargs = overlay_details try: overlaid_object = persistantstorage.componentDict[overlaid_name] except KeyError: logger.error("Could not find the object to overlay: %s." % overlaid_name) return False # Instanciate the overlay, passing the overlaid object to # the constructor + any optional arguments instance = create_instance(overlay_name, overlaid_object, **kwargs) persistantstorage.morse_services.register_request_manager_mapping( instance.name(), request_manager_name) instance.register_services() persistantstorage.overlayDict[overlay_name] = instance logger.info("Component '%s' overlaid with '%s' using middleware " "'%s' for services" % (overlaid_object.name(), overlay_name, request_manager_name)) return True
[docs]def add_modifiers(): """ Read the configuration script (inside the .blend file) and assign the correct data modifiers to each component. """ try: component_list = component_config.component_modifier except (AttributeError, NameError) as detail: # Exit gracefully if there are no modifiers specified logger.info("No modifiers section found in configuration file") return True for component_name, mod_list in component_list.items(): # Get the instance of the object try: instance = persistantstorage.componentDict[component_name] except KeyError as detail: logger.warning("Component listed in component_config.py not " "found in scene: %s" % detail) continue for mod_data in mod_list: modifier_name = mod_data[0] direction = mod_data[1] logger.info("Component: '%s' operated by '%s'" % (component_name, modifier_name)) # Make the modifier object take note of the component modifier_instance = register_modifier(modifier_name, instance, direction, mod_data[2]) if not modifier_instance: return False persistantstorage.modifierDict[modifier_name] = modifier_instance return True
[docs]def init_multinode(): """ Initializes the MORSE node in a Multinode configuration. """ logger.log(SECTION, 'MULTINODE INITIALIZATION') # Configuration for the multi-node simulation try: protocol = multinode_config.node_config["protocol"] except (NameError, AttributeError) as detail: protocol = "socket" # Get the correct class reference according to the chosen protocol if protocol == "socket": classpath = "morse.multinode.socket.SocketNode" elif protocol == "hla": classpath = "morse.multinode.hla.HLANode" try: server_address = multinode_config.node_config["server_address"] server_port = int(multinode_config.node_config["server_port"]) except (NameError, AttributeError) as detail: logger.warning("No node configuration found. Using default values for " "this simulation node.\n\tException: ", detail) server_address = "localhost" server_port = 65000 try: node_name = multinode_config.node_config["node_name"] except (NameError, AttributeError) as detail: logger.warning("No node name defined. Using host name.\n" "\tException: ", detail) import socket node_name = socket.gethostname() logger.info ("This is node '%s'" % node_name) # Create the instance of the node class persistantstorage.node_instance = create_instance(classpath, node_name, server_address, server_port)
[docs]class MorseSyncProcess: def __init__(self): args = ['morse_sync', '-p', str(1.0/morse.core.blenderapi.getfrequency())] socket_manager = 'morse.middleware.socket_datastream.SocketDatastreamManager' socket_properties = component_config.stream_manager[socket_manager] if 'sync_port' in socket_properties: args = args + ['-P', str(socket_properties['sync_port'])] self.proc = Popen(args, stdin=PIPE) # always load datastream manager if we use use_internal_syncer load_datastream_manager(socket_manager)
[docs] def set_period(self, new_value): msg = "set_period " + str(new_value) + "\n\n" self.proc.stdin.write(bytes(msg, encoding='ascii')) self.proc.stdin.flush()
def __del__(self): self.proc.communicate(b"quit", timeout = None)
[docs]def init(contr): """ General initialization of MORSE Here, all components, modifiers and middlewares are initialized. """ init_logging() logger.log(SECTION, 'PRE-INITIALIZATION') # Get the version of Python used # This is used to determine also the version of Blender persistantstorage.pythonVersion = sys.version_info logger.info("Python Version: %s.%s.%s" % persistantstorage.pythonVersion[:3]) logger.info("Blender Version: %s.%s.%s" % morse.core.blenderapi.version()) logger.info("Python path: %s" % sys.path) logger.info("PID: %d" % os.getpid()) persistantstorage.morse_initialised = False persistantstorage.time = TimeStrategies.make(morse.core.blenderapi.getssr()['time_management'], morse.core.blenderapi.getssr().get('use_relative_time', False)) # Variable to keep trac of the camera being used persistantstorage.current_camera_index = 0 init_ok = True init_ok = init_ok and create_dictionaries() persistantstorage.internal_syncer = None logger.log(SECTION, 'SUPERVISION SERVICES INITIALIZATION') init_ok = init_ok and init_supervision_services() # Make sure to start the internal syncer after initialization of # time_scale (done in in init_supervision_services) try: use_ = morse.core.blenderapi.getssr()['use_internal_syncer'] if use_: persistantstorage.internal_syncer = MorseSyncProcess() except KeyError: pass logger.log(SECTION, 'SCENE INITIALIZATION') if MULTINODE_SUPPORT: init_multinode() init_ok = init_ok and link_services() init_ok = init_ok and add_modifiers() init_ok = init_ok and link_datastreams() init_ok = init_ok and load_overlays() if init_ok: check_dictionaries() persistantstorage.morse_initialised = True logger.log(ENDSECTION, 'SCENE INITIALIZED') else: logger.critical('INITIALIZATION FAILED!') logger.info("Exiting now.") contr = morse.core.blenderapi.controller() close_all(contr) quit(contr)
# Set the default value of the logic tic rate to 60 #bge.logic.setLogicTicRate(60.0) #bge.logic.setPhysicsTicRate(60.0)
[docs]def init_logging(): from morse.core.ansistrm import ColorizingStreamHandler if "with-colors" in sys.argv: if "with-xmas-colors" in sys.argv: ch = ColorizingStreamHandler(scheme = "xmas") elif "with-reverse-colors" in sys.argv: ch = ColorizingStreamHandler(scheme = "dark") else: ch = ColorizingStreamHandler() else: ch = ColorizingStreamHandler(scheme = "mono") from morse.helpers.morse_logging import MorseFormatter # create logger logger = logging.getLogger('morse') logger.setLevel(logging.INFO) # create console handler and set level to debug ch.setLevel(logging.DEBUG) # create formatter formatter = MorseFormatter('%(message)s') # add formatter to ch ch.setFormatter(formatter) # add ch to logger logger.addHandler(ch)
[docs]def init_supervision_services(): """ This method first loads the socket service manager, map the virtual 'simulation' component to it, loads any other request manager mapped to the 'simulation' component and register all simulation management services declared in :py:mod:`morse.core.supervision_services` """ from morse.services.supervision_services import Supervision from morse.services.communication_services import Communication from morse.services.time_services import TimeServices simulation_service = Supervision() communication_service = Communication() time_service= TimeServices() try: time_scale = morse.core.blenderapi.getssr()['time_scale'] time_service.set_time_scale(time_scale) except KeyError as e: pass persistantstorage.serviceObjectDict[simulation_service.name()] = simulation_service persistantstorage.serviceObjectDict[communication_service.name()] = communication_service persistantstorage.serviceObjectDict[time_service.name()] = time_service # For each entries of serviceObjects, register the service as # requested by configuration + socket middleware i/o. try: for key, services in persistantstorage.serviceObjectDict.items(): request_managers = component_config.component_service.get(key, []) request_managers.append("morse.middleware.socket_request_manager.SocketRequestManager") for request_manager in request_managers: try: # Load required request managers if not persistantstorage.morse_services.add_request_manager(request_manager): return False persistantstorage.morse_services.register_request_manager_mapping(key, request_manager) logger.info("Adding '%s' to the middlewares for %s " "control" % (request_manager, key)) except MorseServiceError as e: #...no request manager :-( logger.critical(str(e)) logger.critical("SUPERVISION SERVICES INITIALIZATION FAILED") return False services.register_services() except (AttributeError, NameError, KeyError): # Nothing to declare: skip to the next step. pass logger.log(ENDSECTION, "SUPERVISION SERVICES INITIALIZED") return True
[docs]def simulation_main(contr): """ This method is called at every simulation step. We do here all homeworks to manage the simulation at whole. """ # Call datastream manager action handler # Call it early at the synchronisation management may be done here if 'stream_managers' in persistantstorage: for ob in persistantstorage.stream_managers.values(): ob.action() # Update the time variable try: persistantstorage.time.update() except AttributeError: # If the 'base_clock' variable is not defined, there probably was # a problem while doing the init, so we'll abort the simulation. logger.critical("INITIALIZATION ERROR: failure during initialization " "of the simulator. Check the terminal for error " "messages, and report them on the morse-dev@laas.fr " "mailing list.") quit(contr) if 'serviceObjectDict' in persistantstorage: for ob in persistantstorage.serviceObjectDict.values(): ob.action() if "morse_services" in persistantstorage: # let the service managers process their inputs/outputs persistantstorage.morse_services.process() if MULTINODE_SUPPORT: # Register the locations of all the robots handled by this node persistantstorage.node_instance.synchronize()
[docs]def switch_camera(contr): """ Cycle through the cameras in the scene during the game. """ sensor = contr.sensors['F9_KEY'] # Activate only once for each key press if sensor.positive and sensor.triggered: scene = morse.core.blenderapi.scene() cameras = [c for c in scene.cameras if not 'NOT_F9_ABLE' in c] index = persistantstorage.current_camera_index next_camera = cameras[index] scene.active_camera = next_camera logger.info("Showing view from camera: '%s'" % next_camera.name) # Update the index for the next call index = (index + 1) % len(cameras) persistantstorage.current_camera_index = index
[docs]def close_all(contr): """ Close the open communication channels from middlewares Call the destructors of all component instances. This should also call the methods to close middlewares """ logger.log(ENDSECTION, 'COMPONENTS FINALIZATION') # Force the deletion of the sensor objects if 'componentDict' in persistantstorage: for component_instance in persistantstorage.componentDict.values(): component_instance.finalize() # Force the deletion of the robot objects if 'robotDict' in persistantstorage: for robot_instance in persistantstorage.robotDict.values(): robot_instance.finalize() logger.log(ENDSECTION, 'CLOSING REQUEST MANAGERS...') del persistantstorage.morse_services del persistantstorage.serviceObjectDict logger.log(ENDSECTION, 'CLOSING DATASTREAMS...') # Force the deletion of the datastream objects if 'stream_managers' in persistantstorage: for datastream_instance in persistantstorage.stream_managers.values(): datastream_instance.finalize() del persistantstorage.stream_managers logger.log(ENDSECTION, 'CLOSING OVERLAYS...') del persistantstorage.overlayDict if MULTINODE_SUPPORT: logger.log(ENDSECTION, 'CLOSING MULTINODE...') persistantstorage.node_instance.finalize()
[docs]def finish(contr): """ Normal exit from the Game Engine, when pressing ESC key """ sensor = contr.sensors['ESC_KEY'] #execute only when the ESC key is released (if we don't test that, #the code get executed two time, when pressed, and when released) if not sensor.positive and sensor.triggered: close_all(contr) quit(contr)
[docs]def restart(contr): """ Call the Game Engine restart funcionality * DOES NOT WORK * """ sensor = contr.sensors['F11_KEY'] # Execute only when the F11 key is released (if we don't test that, # the code get executed two times, when pressed, and when released) if not sensor.positive and sensor.triggered: logger.warning("Replacing everything at initial position") reset_objects(contr) return
[docs]def quit(contr): """ Exit graciously from the simulation """ logger.log(ENDSECTION, 'EXITING SIMULATION') quitActuator = contr.actuators['Quit_sim'] contr.activate(quitActuator)
[docs]def reset_objects(contr): """ Place all objects in the initial position Restore the position and rotation of objects and robots to their original state, during the simulation. """ for b_obj, state in persistantstorage.blender_objects.items(): # Velocities and forces need to be reset while physics is active? b_obj.setLinearVelocity([0.0, 0.0, 0.0], True) b_obj.setAngularVelocity([0.0, 0.0, 0.0], True) b_obj.applyForce([0.0, 0.0, 0.0], True) b_obj.applyTorque([0.0, 0.0, 0.0], True) # Stop physics simulation b_obj.suspendDynamics() logger.debug("%s goes to %s" % (b_obj, state[0])) b_obj.worldPosition = state[0] b_obj.worldOrientation = state[1] # Resume physics simulation b_obj.restoreDynamics()