From: Mikko Rasa Date: Tue, 11 Jun 2019 08:31:44 +0000 (+0300) Subject: Add exporter for animations X-Git-Url: http://git.tdb.fi/?p=libs%2Fgl.git;a=commitdiff_plain;h=f2cab83d5704f3f4a91f551f402187637e5063d2 Add exporter for animations --- diff --git a/blender/io_mspgl/__init__.py b/blender/io_mspgl/__init__.py index 87aedae5..d555ab4b 100644 --- a/blender/io_mspgl/__init__.py +++ b/blender/io_mspgl/__init__.py @@ -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 index 00000000..5ad937df --- /dev/null +++ b/blender/io_mspgl/animation.py @@ -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 index 00000000..b990e054 --- /dev/null +++ b/blender/io_mspgl/export_animation.py @@ -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<