Add exporter for animations
authorMikko Rasa <tdb@tdb.fi>
Tue, 11 Jun 2019 08:31:44 +0000 (11:31 +0300)
committerMikko Rasa <tdb@tdb.fi>
Tue, 11 Jun 2019 11:00:03 +0000 (14:00 +0300)
blender/io_mspgl/__init__.py
blender/io_mspgl/animation.py [new file with mode: 0644]
blender/io_mspgl/export_animation.py [new file with mode: 0644]

index 87aedae5b96d0bd182bce1e54d77da96732097d8..d555ab4b30d54e59f48f5453c2612dfef73ec2ba 100644 (file)
@@ -7,7 +7,7 @@ bl_info = {
 
 if "bpy" in locals():
        import imp
-       for sub in "armature", "datafile", "export_armature", "export_camera", "export_material", "export_mesh", "export_object", "export_scene", "export_texture", "material", "mesh", "properties", "util":
+       for sub in "animation", "armature", "datafile", "export_animation", "export_armature", "export_camera", "export_material", "export_mesh", "export_object", "export_scene", "export_texture", "material", "mesh", "properties", "util":
                if sub in locals():
                        imp.reload(locals()[sub])
 
@@ -90,6 +90,16 @@ class ExportMspGLArmature(bpy.types.Operator, ExportMspGLBase):
                from .export_armature import ArmatureExporter
                return ArmatureExporter()
 
+class ExportMspGLAnimation(bpy.types.Operator, ExportMspGLBase):
+       bl_idname = "export.mspgl_animation"
+       bl_label = "Export Msp GL animation"
+
+       filename_ext = ".anim"
+
+       def create_exporter(self):
+               from .export_animation import AnimationExporter
+               return AnimationExporter()
+
 class ExportMspGLScene(bpy.types.Operator, ExportMspGLBase):
        bl_idname = "export_scene.mspgl_scene"
        bl_label = "Export Msp GL scene"
@@ -120,6 +130,7 @@ def menu_func_export(self, context):
        self.layout.operator(ExportMspGLMesh.bl_idname, text="Msp GL mesh")
        self.layout.operator(ExportMspGLObject.bl_idname, text="Msp GL object")
        self.layout.operator(ExportMspGLArmature.bl_idname, text="Msp GL armature")
+       self.layout.operator(ExportMspGLAnimation.bl_idname, text="Msp GL animation")
        self.layout.operator(ExportMspGLScene.bl_idname, text="Msp GL scene")
        self.layout.operator(ExportMspGLCamera.bl_idname, text="Msp GL camera")
 
diff --git a/blender/io_mspgl/animation.py b/blender/io_mspgl/animation.py
new file mode 100644 (file)
index 0000000..5ad937d
--- /dev/null
@@ -0,0 +1,88 @@
+import mathutils
+
+class Curve:
+       def __init__(self, curve):
+               self._curve = curve
+               self.knots = []
+
+               for i in range(len(self.keyframe_points)-1):
+                       kf1 = self.keyframe_points[i]
+                       kf2 = self.keyframe_points[i+1]
+                       dx = (kf2.co[0]-kf1.co[0])/3
+                       slope1 = (kf1.handle_right[1]-kf1.co[1])/(kf1.handle_right[0]-kf1.co[0])
+                       slope2 = (kf2.co[1]-kf2.handle_left[1])/(kf2.co[0]-kf2.handle_left[0])
+
+                       if i==0:
+                               self.knots.append(mathutils.Vector(kf1.co))
+                       self.knots.append(kf1.co+mathutils.Vector((dx, slope1*dx)))
+                       self.knots.append(kf2.co-mathutils.Vector((dx, slope2*dx)))
+                       self.knots.append(mathutils.Vector(kf2.co))
+
+       def __getattr__(self, attr):
+               return getattr(self._curve, attr)
+
+
+class Keyframe:
+       def __init__(self, time):
+               self.time = time
+               self.control = False
+               self.curves = []
+
+
+class Animation:
+       def __init__(self, action):
+               self._action = action
+               self.curves = [Curve(c) for c in action.fcurves]
+               self.fps = 1
+
+               for c in self.curves:
+                       for i in range(0, len(c.knots)-1, 3):
+                               p0 = c.knots[i]
+                               p1 = c.knots[i+1]
+                               p2 = c.knots[i+2]
+                               p3 = c.knots[i+3]
+                               for j in range(50):
+                                       t = (p0[0]*(50-j)+p3[0]*j)/50
+                                       x = (t-p0[0])/(p3[0]-p0[0])
+                                       v1 = c._curve.evaluate(t)
+                                       v2 = p0[1]*(1-x)**3+3*p1[1]*x*(1-x)**2+3*p2[1]*x**2*(1-x)+p3[1]*x**3
+
+               keyframes_by_time = {}
+               controls_by_time = {}
+               self.keyframes = []
+               for c in self.curves:
+                       for i, k in enumerate(c.knots):
+                               x = k[0]
+                               control = i%3!=0
+                               kf_map = controls_by_time if control else keyframes_by_time
+                               if x in kf_map:
+                                       kf = kf_map[x]
+                               else:
+                                       kf = Keyframe(x)
+                                       kf.control = control
+                                       self.keyframes.append(kf)
+                                       kf_map[x] = kf
+                               kf.curves.append((c, i))
+
+               self.keyframes.sort(key=lambda k: k.time)
+
+       def __getattr__(self, attr):
+               return getattr(self._curve, attr)
+
+       def change_fps(self, fps):
+               scale = self.fps/fps
+               self.fps = fps
+
+               for c in self.curves:
+                       for k in c.knots:
+                               k[0] *= scale
+
+               for k in self.keyframes:
+                       k.time *= scale
+
+
+def create_animation_from_action(context, action):
+       anim = Animation(action)
+       render = context.scene.render
+       anim.change_fps(render.fps/render.fps_base)
+       return anim
diff --git a/blender/io_mspgl/export_animation.py b/blender/io_mspgl/export_animation.py
new file mode 100644 (file)
index 0000000..b990e05
--- /dev/null
@@ -0,0 +1,65 @@
+import math
+
+class AnimationExporter:
+       def export_to_file(self, context, out_fn):
+               anim_data = context.active_object.animation_data
+               if not anim_data:
+                       raise Exception("Object has no animation data")
+               if not anim_data.action:
+                       raise Exception("No active action")
+
+               resource = self.export_animation(context, anim_data.action)
+
+               with open(out_fn, "w") as out_file:
+                       for s in resource.statements:
+                               s.write_to_file(out_file)
+
+       def export_animation(self, context, action):
+               from .animation import create_animation_from_action
+               anim = create_animation_from_action(context, action)
+
+               from .datafile import Resource, Statement
+               resource = Resource(action.name+".anim")
+
+               components = [(0, "location", "position"), (1, "rotation_euler", "euler"), (2, "scale", "scale")]
+               coords = "xyz"
+               prev_time = 0.0
+               for k in anim.keyframes:
+                       if k.time>prev_time:
+                               resource.statements.append(Statement("interval", k.time-prev_time))
+                               prev_time = k.time
+
+                       st = Statement("control_keyframe" if k.control else "keyframe")
+
+                       transform = [0.0]*9
+                       mask = 0
+                       for c, i in k.curves:
+                               for j, dp, kw in components:
+                                       if c.data_path==dp:
+                                               transform[j*3+c.array_index] = c.knots[i][1]
+                                               mask |= 1<<(j*3+c.array_index)
+                                               break
+
+                       if mask:
+                               ss = Statement("transform")
+
+                               for i, dp, kw in components:
+                                       v = transform[i*3:i*3+3]
+                                       if i==1:
+                                               v = [c*180/math.pi for c in v]
+
+                                       m = 7<<(i*3)
+                                       if (mask&m)==m:
+                                               ss.sub.append(Statement(kw, *v))
+                                       else:
+                                               m &= m>>2
+                                               for j in range(3):
+                                                       if mask&(m<<j):
+                                                               ss.sub.append(Statement("{}_{}".format(kw, coords[j]), v[j]))
+
+                               st.sub.append(ss)
+
+                       resource.statements.append(st)
+
+               return resource
+