1#--- ### Header
2bl_info = {
3    "name": "MORSE scene as Python API (.py)",
4    "author": "Gilberto Echeverria",
5    "version": (1, 0, 0),
6    "blender": (2, 5, 9),
7    "api": 36147,
8    "location": "File>Import-Export",
9    "category": "Import-Export",
10    "description": "Save a MORSE scene as a Python description",
11    "warning": "",
12    "wiki_url": "",
13    "tracker_url": "https://softs.laas.fr/bugzilla/"
14}
15
16import os
17import bpy
18import json
19import re
20from morse.builder.data import *
21from bpy.utils import register_module, unregister_module
22
23"""
24Morse API to save scene files
25
26To test this module you can open this file inside a Text panel in Blender,
27then run the script.
28This will generate a python file in the same directory where Blender was first executed.
29"""
30
31morse_types = {
32    "robots": "Robot",
33    "sensors": "Sensor",
34    "actuators": "Actuator",
35    "middleware": "Middleware",
36    "modifiers": "Modifier",
37}
38
39def save_translation(obj, obj_name):
40    # Set its position
41    position_string = ''
42    text_buffer = ''
43    component_position = obj.location
44    if component_position[0] != 0:
45        position_string += 'x=%.4f' % component_position[0]
46    if component_position[1] != 0:
47        if position_string != '':
48            position_string += ', '
49        position_string += 'y=%.4f' % component_position[1]
50    if component_position[2] != 0:
51        if position_string != '':
52            position_string += ', '
53        position_string += 'z=%.4f' % component_position[2]
54    # Register a translation only if necessary
55    if position_string != '':
56        text_buffer += "%s.translate(%s)\n" % (obj_name, position_string)
57
58    return (text_buffer)
59
60
61def save_rotation(obj, obj_name):
62    # Set its rotation
63    rotation_string = ''
64    text_buffer = ''
65    component_rotation = obj.rotation_euler
66    if component_rotation[0] != 0:
67        rotation_string += 'x=%.4f' % component_rotation[0]
68    if component_rotation[1] != 0:
69        if rotation_string != '':
70            rotation_string += ', '
71        rotation_string += 'y=%.4f' % component_rotation[1]
72    if component_rotation[2] != 0:
73        if rotation_string != '':
74            rotation_string += ', '
75        rotation_string += 'z=%.4f' % component_rotation[2]
76    # Register a translation only if necessary
77    if rotation_string != '':
78        text_buffer += "%s.rotate(%s)\n" % (obj_name, rotation_string)
79
80    return (text_buffer)
81
82
83def save_properties(obj, obj_name):
84    text_buffer = ''
85    # Store the properties of the component
86    for key,prop in obj.game.properties.items():
87        if key not in ['Robot_Tag', 'Component_Tag', 'Middleware_Tag', 'Modifier_Tag', 'Class', 'Path']:
88            if prop.value != '':
89                if prop.type == 'STRING':
90                    text_buffer += "%s.properties(%s = '%s')\n" % (obj_name, key, prop.value)
91                elif prop.type == 'FLOAT' or prop.type == 'TIMER':
92                    text_buffer += "%s.properties(%s = %.4f)\n" % (obj_name, key, prop.value)
93                else:
94                    text_buffer += "%s.properties(%s = %s)\n" % (obj_name, key, prop.value)
95
96    return (text_buffer)
97
98
99def scan_scene (file_out):
100    """ Read all the MORSE components from a Blender file
101
102    Create lists of robots and components to save them as a text file
103    """
104    file_out.write("from morse.builder import *\n\n")
105
106    robot_text = ''
107    component_text = ''
108
109    for obj in bpy.data.objects:
110        try:
111            component_path = obj.game.properties['Path'].value
112        # Exit if the object is not a MORSE component
113        except KeyError as detail:
114            continue
115
116        # Ignore middleware and modifier empties.
117        # These will be added dinamically by the builder
118        if 'middleware' in component_path or 'modifiers' in component_path:
119            continue
120
121        # Read what type of component this is,
122        #  from the source of its python file
123        path_elements = component_path.split('/')
124        component_type = path_elements[-2]
125        component_name = path_elements[-1]
126
127        builder_type = morse_types[component_type]
128
129        # Swap dots for underscores in object names
130        obj_name = re.sub('\.', '_', obj.name)
131        # Create the object instance
132        if component_type == 'robots':
133            robot_text += "%s = %s('%s')\n" % (obj_name, builder_type, component_name)
134            robot_text += save_translation(obj, obj_name)
135            robot_text += save_rotation(obj, obj_name)
136            robot_text += save_properties(obj, obj_name)
137            robot_text += "\n"
138
139        # Assign component to the parent
140        if component_type == 'sensors' or component_type == 'actuators':
141            component_text += "%s = %s('%s')\n" % (obj_name, builder_type, component_name)
142            component_text += save_translation(obj, obj_name)
143            component_text += save_rotation(obj, obj_name)
144            parent_name = re.sub('\.', '_', obj.parent.name)
145            component_text += "%s.append(%s)\n" % (parent_name, obj_name)
146            component_text += save_properties(obj, obj_name)
147            component_text += "\n"
148
149    # Write the buffers to the text file
150    file_out.write("# Robots\n")
151    file_out.write(robot_text)
152    file_out.write("# Components\n")
153    file_out.write(component_text)
154
155
156def scan_config(file_out):
157    """ Parse the contents of 'component_config.py'
158
159    Produce a configuration file that 'morsebuilder' can use to
160    configure the robot/middleware bindings in a scene.
161    """
162    import component_config
163    file_out.write("# Scene configuration\n")
164    for key,value in component_config.component_datastream.items():
165        component = re.sub('\.', '_', key)
166        # If the 'value' variable contains only strings, use that string
167        #  as the name of the middleware.
168        # This is done for backwards compatibility with the previous
169        #  syntax that allowed only one middleware per component
170        if isinstance (value[0], str):
171            mw = value[0]
172            mw = mw.lower()
173            file_out.write("%s.add_stream('%s', %s)\n" % (component, mw, value))
174        # If using the new syntax that allows more than one middleware
175        #  per component
176        else:
177            for item in value:
178                mw = item[0]
179                mw = mw.lower()
180                file_out.write("%s.add_stream('%s', %s)\n" % (component, mw, item))
181
182    try:
183        component_config.component_service
184        file_out.write("\n")
185        for key,value in component_config.component_service.items():
186            component = re.sub('\.', '_', key)
187            mw = re.search('(\w+)_request_manager', value[0])
188            file_out.write("%s.add_service('%s')\n" % (component, mw.group(1)))
189    except AttributeError as detail:
190        print ("\tNo services configured")
191
192    try:
193        component_config.component_modifier
194        file_out.write("\n")
195        for key,value in component_config.component_modifier.items():
196            component = re.sub('\.', '_', key)
197            mod = value[0]
198            file_out.write("%s.alter(%s)\n" % (component, mod))
199    except AttributeError as detail:
200        print ("\tNo modifiers configured")
201
202def get_environment():
203    try:
204        ssh = bpy.data.objects['Scene_Script_Holder']
205        environment_file = ssh.game.properties['environment_file'].value
206    except KeyError as detail:
207        environment_file = 'indoors-1/indoor-1'
208        print ("No environment file specified in 'Scene_Script_Holder'\nUsing '%s' as default" % environment_file)
209
210    return environment_file
211
212
213def save_scene():
214    print ("\nRunning from %s" % bpy.data.filepath)
215    filename = bpy.path.display_name_from_filepath(bpy.data.filepath) + ".py"
216    file_out = open(filename, "w")
217    print ("Saving scene robot configuration to file '%s'" % filename)
218    scan_scene(file_out)
219    scan_config(file_out)
220    env_name = get_environment()
221    file_out.write("\nenv = Environment('%s')" % env_name)
222    file_out.write("\nenv.create()")
223    file_out.close()
224    print ("Configuration saved")
225
226
227#--- ### Operator
228class MorseExporter(bpy.types.Operator):
229    ''' Convert a MORSE scene configuration to a python script '''
230    bl_idname = "export_scene.morse"
231    bl_label = "Save MORSE scene"
232    bl_description = "Convert a MORSE scene configuration to a python script"
233
234    #--- Blender interface methods
235    #@classmethod
236    #def poll(cls,context):
237        #return (context.mode == 'OBJECT')
238
239    def execute(self,context):
240        save_scene()
241        return ('FINISHED')
242
243
244def menu_draw(self, context):
245    self.layout.operator_context = 'INVOKE_REGION_WIN'
246    self.layout.operator(MorseExporter.bl_idname, "Save MORSE scene (.py)")
247
248#--- ### Register
249def register():
250    register_module(__name__)
251    bpy.types.INFO_MT_file_export.prepend(menu_draw)
252def unregister():
253    bpy.types.INFO_MT_file_export.remove(menu_draw)
254    unregister_module(__name__)
255
256#--- ### Main code
257if __name__ == '__main__':
258    register()
259    #save_scene()
260