bl_info cleanup
[blender-addons-contrib.git] / cmu_mocap_browser / __init__.py
1 # ##### BEGIN GPL LICENSE BLOCK #####
2 #
3 #  This program is free software; you can redistribute it and/or
4 #  modify it under the terms of the GNU General Public License
5 #  as published by the Free Software Foundation; either version 3
6 #  of the License, or (at your option) any later version.
7 #
8 #  This program is distributed in the hope that it will be useful,
9 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
10 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 #  GNU General Public License for more details.
12 #
13 #  You should have received a copy of the GNU General Public License
14 #  along with this program; if not, write to the Free Software Foundation,
15 #  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 #
17 # ##### END GPL LICENSE BLOCK #####
18
19 # <pep8-80 compliant>
20
21 # This script was developed with financial support from the Foundation for
22 # Science and Technology of Portugal, under the grant SFRH/BD/66452/2009.
23
24
25 bl_info = {
26     "name": "Carnegie Mellon University Mocap Library Browser",
27     "author": "Daniel Monteiro Basso <daniel@basso.inf.br>",
28     "version": (2012, 6, 1, 1),
29     "blender": (2, 6, 3),
30     "location": "View3D > Tools",
31     "description": "Assistant for using CMU Motion Capture data",
32     "warning": "",
33     "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/"\
34                 "Scripts/3D_interaction/CMU_Mocap_Library_Browser",
35     "tracker_url": "http://projects.blender.org/tracker/index.php?"\
36                    "func=detail&aid=29086",
37     "category": "3D View"}
38
39
40 import os
41 import bpy
42 import bgl
43 import blf
44 import math
45 from . import library
46
47
48 def initialize_subjects():
49     """
50         Initializes the main object and the subjects (actors) list
51     """
52     while bpy.data.scenes[0].cmu_mocap_lib.subject_list:
53         bpy.data.scenes[0].cmu_mocap_lib.subject_list.remove(0)
54     for k, v in library.subjects.items():
55         n = bpy.data.scenes[0].cmu_mocap_lib.subject_list.add()
56         n.name = "{:d} - {}".format(k, v['desc'])
57         n.idx = k
58
59
60 def update_motions(self, context):
61     """
62         Updates the motions list after a subject is selected
63     """
64     sidx = -1
65     if self.subject_active != -1:
66         sidx = self.subject_list[self.subject_active].idx
67     while self.motion_list:
68         self.motion_list.remove(0)
69     if sidx != -1:
70         for k, v in library.subjects[sidx]["motions"].items():
71             n = self.motion_list.add()
72             n.name = "{:d} - {}".format(k, v["desc"])
73             n.idx = k
74         self.motion_active = -1
75
76
77 class ListItem(bpy.types.PropertyGroup):
78     name = bpy.props.StringProperty()
79     idx = bpy.props.IntProperty()
80
81
82 class CMUMocapLib(bpy.types.PropertyGroup):
83     local_storage = bpy.props.StringProperty(
84         name="Local Storage",
85         subtype='DIR_PATH',
86         description="Location to store downloaded resources",
87         default="~/cmu_mocap_lib")
88     follow_structure = bpy.props.BoolProperty(
89         name="Follow Library Folder Structure",
90         description="Store resources in subfolders of the local storage",
91         default=True)
92     automatically_import = bpy.props.BoolProperty(
93         name="Automatically Import after Download",
94         description="Import the resource after the download is finished",
95         default=True)
96     subject_list = bpy.props.CollectionProperty(
97         name="subjects", type=ListItem)
98     subject_active = bpy.props.IntProperty(
99         name="subject_idx", default=-1, update=update_motions)
100     subject_import_name = bpy.props.StringProperty(
101         name="Armature Name",
102         description="Identifier of the imported subject's armature",
103         default="Skeleton")
104     motion_list = bpy.props.CollectionProperty(name="motions", type=ListItem)
105     motion_active = bpy.props.IntProperty(name="motion_idx", default=-1)
106     frame_skip = bpy.props.IntProperty(name="Fps Divisor", default=4,
107     # usually the sample rate is 120, so the default 4 gives you 30fps
108                           description="Frame supersampling factor", min=1)
109     cloud_scale = bpy.props.FloatProperty(name="Marker Cloud Scale",
110                           description="Scale the marker cloud by this value",
111                           default=1., min=0.0001, max=1000000.0,
112                           soft_min=0.001, soft_max=100.0)
113
114
115 def draw_callback(self, context):
116     mid = int(360 * self.recv / self.fsize)
117     cx = 200
118     cy = 30
119     blf.position(0, 230, 23, 0)
120     blf.size(0, 20, 72)
121     blf.draw(0, "{0:2d}% of {1}".format(
122         100 * self.recv // self.fsize, self.hfsize))
123
124     bgl.glEnable(bgl.GL_BLEND)
125     bgl.glColor4f(.7, .7, .7, 0.8)
126     bgl.glBegin(bgl.GL_TRIANGLE_FAN)
127     bgl.glVertex2i(cx, cy)
128     for i in range(mid):
129         x = cx + 20 * math.sin(math.radians(float(i)))
130         y = cy + 20 * math.cos(math.radians(float(i)))
131         bgl.glVertex2f(x, y)
132     bgl.glEnd()
133
134     bgl.glColor4f(.0, .0, .0, 0.6)
135     bgl.glBegin(bgl.GL_TRIANGLE_FAN)
136     bgl.glVertex2i(cx, cy)
137     for i in range(mid, 360):
138         x = cx + 20 * math.sin(math.radians(float(i)))
139         y = cy + 20 * math.cos(math.radians(float(i)))
140         bgl.glVertex2f(x, y)
141     bgl.glEnd()
142
143     bgl.glDisable(bgl.GL_BLEND)
144     bgl.glColor4f(0.0, 0.0, 0.0, 1.0)
145
146
147 class CMUMocapDownloadImport(bpy.types.Operator):
148     bl_idname = "mocap.download_import"
149     bl_label = "Download and Import a file"
150
151     remote_file = bpy.props.StringProperty(
152         name="Remote File",
153         description="Location from where to download the file data")
154     local_file = bpy.props.StringProperty(
155         name="Local File",
156         description="Destination where to save the file data")
157     do_import = bpy.props.BoolProperty(
158         name="Manual Import",
159         description="Import the resource non-automatically",
160         default=False)
161
162     timer = None
163     fout = None
164     src = None
165     fsize = 0
166     recv = 0
167     cloud_scale = 1
168
169     def modal(self, context, event):
170         context.area.tag_redraw()
171         if event.type == 'ESC':
172             self.fout.close()
173             os.unlink(self.local_file)
174             return self.cancel(context)
175         if event.type == 'TIMER':
176             to_read = min(self.fsize - self.recv, 100 * 2 ** 10)
177             data = self.src.read(to_read)
178             self.fout.write(data)
179             self.recv += to_read
180             if self.fsize == self.recv:
181                 self.fout.close()
182                 return self.cancel(context)
183         return {'PASS_THROUGH'}
184
185     def cancel(self, context):
186         context.window_manager.event_timer_remove(self.timer)
187         context.region.callback_remove(self.handle)
188         if os.path.exists(self.local_file):
189             self.import_or_open()
190         return {'CANCELLED'}
191
192     def execute(self, context):
193         cml = bpy.data.scenes[0].cmu_mocap_lib
194         if not os.path.exists(self.local_file):
195             try:
196                 os.makedirs(os.path.split(self.local_file)[0])
197             except:
198                 pass
199             from urllib.request import urlopen
200             self.src = urlopen(self.remote_file)
201             info = self.src.info()
202             self.fsize = int(info["Content-Length"])
203             m = int(math.log10(self.fsize) // 3)
204             self.hfsize = "{:.1f}{}".format(
205                 self.fsize * math.pow(10, -m * 3),
206                 ['b', 'Kb', 'Mb', 'Gb', 'Tb', 'Eb', 'Pb'][m])  # :-p
207             self.fout = open(self.local_file, 'wb')
208             self.recv = 0
209             self.handle = context.region.\
210                 callback_add(draw_callback, (self, context), 'POST_PIXEL')
211             self.timer = context.window_manager.\
212                 event_timer_add(0.001, context.window)
213             context.window_manager.modal_handler_add(self)
214             return {'RUNNING_MODAL'}
215         else:
216             self.import_or_open()
217         return {'FINISHED'}
218
219     def import_or_open(self):
220         cml = bpy.data.scenes[0].cmu_mocap_lib
221         if cml.automatically_import or self.do_import:
222             if self.local_file.endswith("mpg"):
223                 bpy.ops.wm.path_open(filepath=self.local_file)
224             elif self.local_file.endswith("asf"):
225                 try:
226                     bpy.ops.import_anim.asf(
227                         filepath=self.local_file,
228                         from_inches=True,
229                         use_rot_x=True, use_rot_z=True,
230                         armature_name=cml.subject_import_name)
231                 except AttributeError:
232                     self.report({'ERROR'}, "To use this feature "
233                         "please enable the Acclaim ASF/AMC Importer addon.")
234             elif self.local_file.endswith("amc"):
235                 ob = bpy.context.active_object
236                 if not ob or ob.type != 'ARMATURE' or \
237                     'source_file_path' not in ob:
238                     self.report({'ERROR'}, "Please select a CMU Armature.")
239                     return
240                 try:
241                     bpy.ops.import_anim.amc(
242                         filepath=self.local_file,
243                         frame_skip=cml.frame_skip)
244                 except AttributeError:
245                     self.report({'ERROR'}, "To use this feature please "
246                         "enable the Acclaim ASF/AMC Importer addon.")
247             elif self.local_file.endswith("c3d"):
248                 try:
249                     bpy.ops.import_anim.c3d(
250                         filepath=self.local_file,
251                         from_inches=False,
252                         auto_scale=True,
253                         scale=cml.cloud_scale,
254                         show_names=False,
255                         frame_skip=cml.frame_skip)
256                 except AttributeError:
257                     self.report({'ERROR'}, "To use this feature "
258                         "please enable the C3D Importer addon.")
259
260
261
262 class CMUMocapConfig(bpy.types.Panel):
263     bl_idname = "object.cmu_mocap_config"
264     bl_label = "CMU Mocap Browser Configuration"
265     bl_space_type = 'VIEW_3D'
266     bl_region_type = 'TOOLS'
267     bl_options = {'DEFAULT_CLOSED'}
268
269     def draw(self, context):
270         if not bpy:
271             return
272         cml = bpy.data.scenes[0].cmu_mocap_lib
273         layout = self.layout
274         layout.operator("wm.url_open",
275             text="Carnegie Mellon University Mocap Library",
276             icon='URL').url = 'http://mocap.cs.cmu.edu/'
277         layout.prop(cml, "local_storage")
278         layout.prop(cml, "follow_structure")
279         layout.prop(cml, "automatically_import")
280
281
282 class CMUMocapSubjectBrowser(bpy.types.Panel):
283     bl_idname = "object.cmu_mocap_subject_browser"
284     bl_label = "CMU Mocap Subject Browser"
285     bl_space_type = 'VIEW_3D'
286     bl_region_type = 'TOOLS'
287     bl_options = {'DEFAULT_CLOSED'}
288
289     def draw(self, context):
290         if not bpy:
291             return
292         layout = self.layout
293         cml = bpy.data.scenes[0].cmu_mocap_lib
294         # spare space... layout.label("Subjects")
295         layout.template_list(cml, "subject_list", cml, "subject_active")
296         layout.prop(cml, "subject_import_name")
297         if cml.subject_active != -1:
298             sidx = cml.subject_list[cml.subject_active].idx
299             remote_fname = library.skeleton_url.format(sidx)
300             tid = "{0:02d}".format(sidx)
301             local_path = os.path.expanduser(cml.local_storage)
302             if cml.follow_structure:
303                 local_path = os.path.join(local_path, tid)
304             local_fname = os.path.join(local_path, tid + ".asf")
305             do_import = False
306             if os.path.exists(local_fname):
307                 label = "Import Selected"
308                 do_import = True
309             elif cml.automatically_import:
310                 label = "Download and Import Selected"
311             else:
312                 label = "Download Selected"
313
314             props = layout.operator("mocap.download_import",
315                                     text=label, icon='ARMATURE_DATA')
316             props.remote_file = remote_fname
317             props.local_file = local_fname
318             props.do_import = do_import
319
320
321 class CMUMocapMotionBrowser(bpy.types.Panel):
322     bl_idname = "object.cmu_mocap_motion_browser"
323     bl_label = "CMU Mocap Motion Browser"
324     bl_space_type = 'VIEW_3D'
325     bl_region_type = 'TOOLS'
326     bl_options = {'DEFAULT_CLOSED'}
327
328     def draw(self, context):
329         if not bpy:
330             return
331         layout = self.layout
332         cml = bpy.data.scenes[0].cmu_mocap_lib
333         # spare space... layout.label("Motions for selected subject")
334         layout.template_list(cml, "motion_list", cml, "motion_active")
335         if cml.motion_active == -1:
336             return
337         sidx = cml.subject_list[cml.subject_active].idx
338         midx = cml.motion_list[cml.motion_active].idx
339         motion = library.subjects[sidx]['motions'][midx]
340         fps = motion['fps']
341         ifps = fps // cml.frame_skip
342         # layout.label("Original capture frame rate: {0:d} fps.".format(fps))
343         # layout.label("Importing frame rate: {0:d} fps.".format(ifps))
344         row = layout.row()
345         row.column().label("Original: {0:d} fps.".format(fps))
346         row.column().label("Importing: {0:d} fps.".format(ifps))
347         layout.prop(cml, "frame_skip")
348         layout.prop(cml, "cloud_scale")
349         remote_fname = library.motion_url.format(sidx, midx)
350         tid = "{0:02d}".format(sidx)
351         local_path = os.path.expanduser(cml.local_storage)
352         if cml.follow_structure:
353             local_path = os.path.join(local_path, tid)
354         for target, icon, ext in (
355                 ('Motion Data', 'POSE_DATA', 'amc'),
356                 ('Marker Cloud', 'EMPTY_DATA', 'c3d'),
357                 ('Movie', 'FILE_MOVIE', 'mpg')):
358             action = "Import" if ext != 'mpg' else "Open"
359             fname = "{0:02d}_{1:02d}.{2}".format(sidx, midx, ext)
360             local_fname = os.path.join(local_path, fname)
361             do_import = False
362             if os.path.exists(local_fname):
363                 label = "{0} {1}".format(action, target)
364                 do_import = True
365             elif cml.automatically_import:
366                 label = "Download and {0} {1}".format(action, target)
367             else:
368                 label = "Download {0}".format(target)
369             row = layout.row()
370             props = row.operator("mocap.download_import", text=label, icon=icon)
371             props.remote_file = remote_fname + ext
372             props.local_file = local_fname
373             props.do_import = do_import
374             row.active = ext in motion['files']
375
376
377 def register():
378     bpy.utils.register_module(__name__)
379     bpy.types.Scene.cmu_mocap_lib = bpy.props.PointerProperty(type=CMUMocapLib)
380     initialize_subjects()
381
382
383 def unregister():
384     bpy.utils.unregister_module(__name__)
385
386
387 if __name__ == "__main__":
388     register()