1import os
2import sys
3import pkgutil
4import inspect
5import subprocess
6
7try:
8    import bpy # Blender
9except ImportError:
10    print("Could not import bpy, run with Blender\n"
11          "blender -P components_exhibit.py")
12    sys.exit(-1)
13from mathutils import Euler
14
15def ext_exec(cmd, python=None):
16    if not python:
17        python = 'python%i.%i'%(sys.version_info.major, sys.version_info.minor)
18    return subprocess.getoutput('%s -c"%s"' % (python, cmd) )
19
20def fix_python_path(python=None):
21    pythonpath = ext_exec("import os,sys;print(os.pathsep.join(sys.path))")
22    sys.path.extend(pythonpath.split(os.pathsep))
23
24fix_python_path()
25try:
26    import morse # MORSE
27except ImportError:
28    raise ImportError("Could not import morse, set your PYTHONPATH\n"
29        'export PYTHONPATH="/usr/local/lib/python3.3/site-packages/:$PYTHONPATH"')
30from morse.builder import *
31
32#
33# helpers
34#
35
36def get_classes_from_module(module_name):
37    __import__(module_name)
38    # Predicate to make sure the classes only come from the module in question
39    def predicate(member):
40        return inspect.isclass(member) and member.__module__.startswith(module_name)
41    # fetch all members of module name matching 'pred'
42    return inspect.getmembers(sys.modules[module_name], predicate)
43
44def get_submodules(module_name):
45    """ Get a list of submodules from a module name.
46    Not recursive, don't return nor look in subpackages """
47    __import__(module_name)
48    module = sys.modules[module_name]
49    module_path = getattr(module, '__path__')
50    return [name for _, name, ispkg in pkgutil.iter_modules(module_path) if not ispkg]
51
52def get_subclasses(module_name, skip_submodules=[]):
53    subclasses = []
54    submodules = get_submodules(module_name)
55    for submodule_name in submodules:
56        if submodule_name in skip_submodules:
57            pass
58        submodule = "%s.%s"%(module_name, submodule_name)
59        try:
60            submodule_classes = get_classes_from_module(submodule)
61            for _, klass in submodule_classes:
62                subclasses.append(klass)
63        except Exception:
64            # can not import some resources
65            pass
66    return subclasses
67
68modules = [
69    "morse.builder.sensors",
70    "morse.builder.actuators",
71    "morse.builder.robots",
72]
73
74specific_camera_pose_per_component = {
75    # Robots
76    'BasePR2': (.36, -2.25, 1.77),
77    'Morsy': (3.84, -3.12, 2.12, 1.32, 0.0, 0.86),
78    'SegwayRMP400': (.36, -2.25, 1.77),
79    'ATRV': (.36, -2.25, 1.77),
80    'Human': (4, 0, 2.8), # 65°,0,90°
81    'Hummer': (.36, -2.25, 1.77),
82    'Jido': (.36, -2.25, 1.77),
83    'B21': (.36, -2.25, 1.77),
84    'RMax': (.36, -2.25, 1.77),
85    'Submarine': (.36, -2.25, 1.77),
86    'Vicitm': (.36, -2.25, 1.77),
87    'NavPR2': (.36, -2.25, 1.77),
88    'QUAD2012': (.36, -1.25, .77),
89    'Quadrotor': (.36, -1.25, .77),
90    'Pioneer3DX': (.36, -1.25, .77),
91    # Sensors
92    'Accelerometer': (.36, -1.25, .77),
93    'Battery': (.36, -1.25, .77),
94    'DepthCamera': (.36, -1.25, .77),
95    'GPS': (.36, -1.25, .77),
96    'Gyroscope': (.36, -1.25, .77),
97    'Hokuyo': (.36, -1.25, .77),
98    'IMU': (.36, -1.25, .77),
99    'Infrared': (.36, -1.25, .77),
100    'Kinect': (.36, -1.25, .77),
101    'Odometry': (.36, -1.25, .77),
102    'Pose': (.36, -1.25, .77),
103    'Proximity': (.36, -1.25, .77),
104    'SearchAndRescue': (.36, -1.25, .77),
105    'SemanticCamera': (.36, -1.25, .77),
106    'Sick': (.36, -1.25, .77),
107    'SickLDMRS': (.36, -1.25, .77),
108    'StereoUnit': (.36, -1.25, .77),
109    'Thermometer': (.36, -1.25, .77),
110    'VideoCamera': (.36, -1.25, .77),
111    'Velodyne': (.36, -1.25, .77),
112    # Actuators
113    'PTU': (.36, -1.25, .77),
114    'PA10': (.36, -2.25, 1.77),
115    'KukaLWR': (.36, -2.25, 1.77),
116    'Gripper': (.36, -1.25, .77),
117}
118
119def pose_camera(location = (.36, -1.25, .77), rotation = (1.1, 0, 0.25)):
120    if len(location) == 6:
121        rotation = location[3:]
122        location = location[:3]
123    scene = bpy.context.scene
124    scene.camera.rotation_euler = Euler(rotation, 'XYZ')
125    scene.camera.location = location
126
127def setup_scene():
128    scene = bpy.context.scene
129    if 'Cube' in bpy.data.objects:
130        bpymorse.delete('Cube')
131    # Set the scene's camera
132    scene.camera = bpy.data.objects['Camera']
133    # Set the scene's output file format
134    scene.render.image_settings.file_format = 'PNG'
135    # RGBA, Images are saved with RGB and Alpha data (if supported).
136    scene.render.image_settings.color_mode = 'RGBA'
137    if bpy.app.version < (2, 66, 0):
138        # Premultiplied, Transparent RGB pixels are multiplied by the alpha channel.
139        scene.render.alpha_mode = 'PREMUL'
140    else:
141        # Transparent, World background is transparent with premultiplied alpha.
142        scene.render.alpha_mode = 'TRANSPARENT'
143    # Move the default Lamp
144    bpy.data.objects['Lamp'].location = (-2, 3, 6)
145    if len(bpy.data.lamps) < 3:
146        # Add new lamp point
147        bpy.ops.object.lamp_add(type='POINT', location=(5, 2, 1))
148        bpy.data.lamps[-1].energy = 0.6
149        bpy.ops.object.lamp_add(type='POINT', location=(-4, 3, 4))
150        bpy.data.lamps[-1].energy = 0.4
151    # Move the default Camera
152    pose_camera()
153
154def render_component(klass, save_path):
155    scene = bpy.context.scene
156    class_name = klass.__name__
157    if class_name in specific_camera_pose_per_component:
158        pose_camera(specific_camera_pose_per_component[class_name])
159    else:
160        print("#### class not in dict: %s"%str(class_name))
161        pose_camera()
162    origin_objects = [obj.name for obj in bpy.data.objects]
163    try:
164        # load class
165        component = klass()
166        # Refresh the view
167        bpy.ops.wm.redraw_timer(type='DRAW_WIN_SWAP', iterations=1)
168        # Render the scene
169        scene.render.filepath = os.path.join(save_path, klass.__name__ + ".png")
170        bpy.ops.render.render(write_still=True)
171    except Exception as e:
172        print("[ERROR] Could not load %s.\nSet MORSE_ROOT to the prefix " \
173              "used to install MORSE.\n%s"% (str(klass), str(e) ) )
174    finally:
175        # Remove the inserted objects
176        bpymorse.delete([obj for obj in bpy.data.objects \
177                         if obj.name not in origin_objects])
178
179def main():
180    """ Generate "studio" image of MORSE components
181
182    HOWTO
183
184    export PYTHONPATH="/usr/local/lib/python3.3/site-packages/:$PYTHONPATH"
185    blender -P components_exhibit.py
186
187    Et voila!
188    """
189    print ("==== PHOTO STUDIO FOR MORSE COMPONENTS ====\n\n")
190    save_path = "//morse_renders"
191    scene = bpy.context.scene
192    print('Using Scene[%s]'%scene.name)
193    setup_scene()
194
195    # browse morse components modules
196    for module in modules:
197        print("browse %s classes"%module)
198        for _, klass in get_classes_from_module(module):
199            print("process %s"%str(klass))
200            if issubclass(klass, AbstractComponent):
201                try:
202                    render_component(klass, save_path)
203                except Exception as e:
204                    print("[ERROR] Could not render %s : %s"%(str(klass), str(e)))
205            else:
206                print("[ERROR] Not a Component : %s"%str(klass))
207
208            print("\n\n")
209
210    print ("DONE!")
211
212
213if __name__ == '__main__':
214    main()
215
216