# ##### BEGIN GPL LICENSE BLOCK ##### # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software Foundation, # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # # ##### END GPL LICENSE BLOCK ##### bl_info = { "name": "Export Pose as JSON (.json)", "description": "Export Pose as JSON (.json)", "author": "Josh Vanderhoof", "version": (0, 1), "blender": (2, 80, 0), "location": "File > Export > JSON Pose (.json)", "warning": "", "category": "Import-Export" } import bpy from bpy.props import * import mathutils, math, struct from mathutils import * from os import remove from bpy_extras.io_utils import ExportHelper def get_deform_bones(arm): keys = [] for k in arm.data.bones.keys(): if arm.data.bones[k].use_deform: keys.append(k) return keys def export_poses(file, obj, rot): if obj.type != 'ARMATURE': return bones = get_deform_bones(obj) bones.sort() file.write("{\n") file.write(" bone_count: %d,\n" % (len(bones))) file.write(" frame_count: %d,\n" % (bpy.context.scene.frame_end - bpy.context.scene.frame_start)) file.write(" fps: %f,\n" % (bpy.context.scene.render.fps / bpy.context.scene.render.fps_base)) file.write(" rest_centers: [\n") # output bone rest positions for k in bones: m = obj.data.bones[k].matrix_local b = rot @ Vector((m[0][3], m[1][3], m[2][3])) file.write(" %f, %f, %f,\n" % (b[0], b[1], b[2])) file.write(" ],\n") file.write(" poses: [\n") # output poses for f in range(bpy.context.scene.frame_start, bpy.context.scene.frame_end): file.write(" {\n") bpy.context.scene.frame_set(f) file.write(" centers: [\n") for k in bones: m = obj.pose.bones[k].matrix b = rot @ Vector((m[0][3], m[1][3], m[2][3])) file.write(" %f, %f, %f,\n" % (b[0], b[1], b[2])) file.write(" ],\n") file.write(" rotations: [\n") for k in bones: r = obj.data.bones[k].matrix_local r = Matrix(( (r[0][0], r[0][1], r[0][2]), (r[1][0], r[1][1], r[1][2]), (r[2][0], r[2][1], r[2][2]))) m = obj.pose.bones[k].matrix m = Matrix(( (m[0][0], m[0][1], m[0][2]), (m[1][0], m[1][1], m[1][2]), (m[2][0], m[2][1], m[2][2]))) m = rot @ m r = rot @ r r.transpose() m = m @ r # Output in OpenGL column major order file.write(" %f, %f, %f, %f, %f, %f, %f, %f, %f,\n" % (m[0][0], m[1][0], m[2][0], m[0][1], m[1][1], m[2][1], m[0][2], m[1][2], m[2][2])) file.write(" ]\n") file.write(" },\n") file.write(" ],\n") file.write("}\n") return def do_export(context, props, filepath): file = open(filepath, "w", encoding="ascii", newline="") if props.rot_x90: rot = Matrix.Rotation(-math.pi * 0.5, 3, 'X') else: rot = Matrix.Identity(3) for obj in bpy.context.selected_objects: export_poses(file, obj, rot) file.flush() file.close() return True class Export_jsonpose(bpy.types.Operator, ExportHelper): '''Exports the active Object as a .json file.''' bl_idname = "export_shape.jsonpose" bl_label = "Export JSON pose (.json)" filename_ext = ".json" rot_x90: BoolProperty(name="Convert to Y-up", description="Rotate 90 degrees around X to convert to y-up", default=True) # "poll" reports if this operator makes sense in the current conditions. # (I think.) @classmethod def poll(cls, context): return context.active_object.type == 'ARMATURE' # "execute" is called non interactively. def execute(self, context): props = self.properties filepath = self.filepath filepath = bpy.path.ensure_ext(filepath, self.filename_ext) exported = do_export(context, props, filepath) return {'FINISHED'} # "invoke" is called because of user interaction. def invoke(self, context, event): wm = context.window_manager if True: # File selector wm.fileselect_add(self) # will run self.execute() return {'RUNNING_MODAL'} elif True: # search the enum wm.invoke_search_popup(self) return {'RUNNING_MODAL'} elif False: # Redo popup return wm.invoke_props_popup(self, event) elif False: return self.execute(context) def menu_func(self, context): self.layout.operator(Export_jsonpose.bl_idname, text="JSON pose (.json)") def register(): from bpy.utils import register_class register_class(Export_jsonpose) bpy.types.TOPBAR_MT_file_export.append(menu_func) def unregister(): from bpy.utils import unregister_class unregister_class(Export_jsonpose) bpy.types.TOPBAR_MT_file_export.remove(menu_func) if __name__ == "__main__": register()