diff options
-rw-r--r-- | __init__.py | 28 | ||||
-rw-r--r-- | bck_export.py | 125 | ||||
-rw-r--r-- | bck_import.py | 66 | ||||
-rw-r--r-- | collada_superbmd_export.py | 11 | ||||
-rw-r--r-- | math_funcs.py | 69 |
5 files changed, 244 insertions, 55 deletions
diff --git a/__init__.py b/__init__.py index 5b0949c..3eebb3f 100644 --- a/__init__.py +++ b/__init__.py @@ -6,6 +6,7 @@ # that the python interpreter must have built in import bpy, importlib, sys, subprocess, os, shutil import math, mathutils, warnings, struct, site +from . import file_ops # elemental python modules blenxy needs # that the python interpreter probably does not have built in/needs to be updated @@ -14,12 +15,31 @@ new_modules = ["lxml", "numpy"] # check which of the new modules needs to be installed def new_mod_check(mod_names): + # variable to return rtn = [] + + # traverse into all the new_modules for mod_name in mod_names: + # skip numpy if the numpy_bad folder was created + if (mod_name == "numpy"): + for path in site.getsitepackages(): + if ("2.79" in path): + numpy_path = path + "/numpy" + numpy_bad_path = path + "/numpy_bad" + # rename the existing numpy folder to numpy bad and install newer numpy + if (file_ops.f_exists(numpy_bad_path) == False): + file_ops.rename(numpy_path, numpy_bad_path) + rtn.append("numpy") + break + continue + + # try importing the module try: importlib.import_module(mod_name) except: rtn.append(mod_name) + + # done! return rtn # install given modules with pip but be sure to have pip first @@ -42,14 +62,6 @@ def new_mod_install(mod_names): # install the rest of the modules if (mod_names != []): for mod_name in mod_names: - if (mod_name == "numpy"): # specific to the numpy module - # get the numpy package path - numpy_path = None - for path in site.getsitepackages(): - if ("2.79" in path): - numpy_path = path + "/numpy" - break - os.rename(numpy_path, numpy_path + "_bad") # normal module installation subprocess.run(pip_install + [mod_name]) return True diff --git a/bck_export.py b/bck_export.py index 16f6468..105106f 100644 --- a/bck_export.py +++ b/bck_export.py @@ -48,8 +48,8 @@ def export_bck_func(options, context): bck_anim.bone_count = len(armature.data.bones) for i in range(bck_anim.bone_count): # append the bone component animation data - bck_anim.anim_data.append(bck_funcs.smg_bck_anim.anim_data()) - + bck_anim.anim_data.append(bck_funcs.smg_bck_anim.anim_data()) + # start getting the actual animation data for i in range(len(armature.data.bones)): data_bone = armature.data.bones[i] # correct bone index order @@ -62,15 +62,93 @@ def export_bck_func(options, context): bone_fcurves = [None, None, None, None, None, None, None, None, None] # ^ sx, rx, tx, sy (24!), ry, ty, sz, rz and tz in that order (bck order) bone_data_path_str = "pose.bones[\"%s\"]." % (data_bone.name) + foreign_fcurves = [None, None, None, None] # quaternions/axis angles have 4 components + temp_fcurves = [None, None, None] + # ^ for rotation mode conversion to euler selected order mode + # from different euler order, quaternion or axis angle + + # detect the rotation mode string (it will be the rotation mode active on the bone) + bone_rot_mode_str = pose_bone.rotation_mode + if ("Z" in bone_rot_mode_str): # awful but fast check + bone_rot_mode_str = "rotation_euler" + elif (bone_rot_mode_str == "QUATERNION"): + bone_rot_mode_str = "rotation_quaternion" + elif (bone_rot_mode_str == "AXIS_ANGLE"): + bone_rot_mode_str = "rotation_axis_angle" + + # now get the animation data for fcurve in armature.animation_data.action.fcurves: + # scaling if (fcurve.data_path == bone_data_path_str + "scale"): bone_fcurves[int((3 * fcurve.array_index) + 0)] = fcurve - elif (fcurve.data_path == bone_data_path_str + "rotation_euler"): - bone_fcurves[int((3 * fcurve.array_index) + 1)] = fcurve + # rotation (Euler [all its combinations], Quaternions, Axis angle) + elif (fcurve.data_path == bone_data_path_str + bone_rot_mode_str): + # check if it is Euler in the euler order selected, if not, select the curves for later conversion + if (bone_rot_mode_str == "rotation_euler" and bone_rot_euler_order_str == options.euler_mode): + bone_fcurves[int((3 * fcurve.array_index) + 1)] = fcurve + else: + foreign_fcurves[fcurve.array_index] = fcurve + # translation elif (fcurve.data_path == bone_data_path_str + "location"): bone_fcurves[int((3 * fcurve.array_index) + 2)] = fcurve - # generate all the animation points, interpolation stuff will be done later + # if there are foreign rotation modes used + # convert them to the euler order mode requested + # and assign those fcurves to the bone_fcurves list + # (this is tuff) + if (options.euler_mode != pose_bone.rotation_mode): + # generate the temp fcurves + for j in range(3): + temp_fcurves[j] = armature.animation_data.action.fcurves.new("temp_fcurves", j, "temp") + og_rot_values = [None, None, None, None] # to hold original data + + # other euler order + if ("Z" in pose_bone.rotation_mode): + # go through all animation frames + for j in range(options.first_frame, options.anim_length): + # get the og data + for k in range(3): + og_rot_values[k] = foreign_fcurves[k].evaluate(j) + # compile into a matrix, get the desired angles from it in the new euler order + tmp = mathutils.Euler(og_rot_values[0:3], pose_bone.rotation_mode) + tmp = tmp.to_matrix().to_euler(options.euler_mode) + # assign the respective points to the temp fcurves + for k in range(3): + temp_fcurves[k].keyframe_points.insert(j, tmp[k]) + + # quaternions + elif (pose_bone.rotation_mode == "QUATERNION"): + # go through all animation frames + for j in range(options.first_frame, options.anim_length): + # get the og data + for k in range(4): + og_rot_values[k] = foreign_fcurves[k].evaluate(j) + # compile into a quaternion, get the desired angles from it in the new euler order + tmp = mathutils.Quaternion(og_rot_values).to_euler(options.euler_mode) + # assign the respective points to the temp fcurves + for k in range(3): + temp_fcurves[k].keyframe_points.insert(j, tmp[k]) + + # axis angle + elif (pose_bone.rotation_mode == "AXIS_ANGLE"): + # go through all animation frames + for j in range(options.first_frame, options.anim_length): + # get the og data + for k in range(4): + og_rot_values[k] = foreign_fcurves[k].evaluate(j) + # compile into a matrix, get the desired angles from it in the new euler order + tmp = mathutils.Matrix.Rotation(og_rot_values[0], 3, og_rot_values[1:]).to_euler(options.euler_mode) + # assign the respective points to the temp fcurves + for k in range(3): + temp_fcurves[k].keyframe_points.insert(j, tmp[k]) + + # assign the resulting fcurves to the bone_fcurves list + # rotation fcurves + for j in range(3): + bone_fcurves[int((j * 3) + 1)] = temp_fcurves[j] + + # all the fcurves with the correct units were selected + # generate all the new animation points, interpolation stuff will be done later # get the rest pose matrix rest_mat = data_bone.matrix_local.copy() @@ -100,18 +178,25 @@ def export_bck_func(options, context): transl[int((k - 2) / 3)] = value # convert the values to be respect to parent - new_mat = rest_mat.copy() * math_funcs.calc_transf_mat(scale, rot, transl).copy() + new_mat = rest_mat.copy() * math_funcs.calc_transf_mat(scale, rot, transl, + options.euler_mode, + options.mult_order).copy() for k in range(9): value = None # check which is the component to get if (k == 0 or k == 3 or k == 6): value = round(new_mat.to_scale()[int((k - 0) / 3)], options.rounding_vec[0]) elif (k == 1 or k == 4 or k == 7): - value = round(new_mat.to_euler("XYZ")[int((k - 1) / 3)], options.rounding_vec[1]) + value = round(new_mat.to_euler(options.euler_mode)[int((k - 1) / 3)], options.rounding_vec[1]) elif (k == 2 or k == 5 or k == 8): value = round(100 * new_mat.to_translation()[int((k - 2) / 3)], options.rounding_vec[2]) # 100 times because of blenxy's coordinates bck_anim.anim_data[i].comp[k].value.append(value) + + # delete the temp_fcurves generated + for fcurve in temp_fcurves: + if (fcurve != None): + armature.animation_data.action.fcurves.remove(fcurve) # got all the animation points @@ -307,6 +392,32 @@ class export_bck(Operator, ExportHelper): min = 0, max = 9 ) + euler_mode = EnumProperty( + name = "Euler order", + description = "Export rotation animations in the specified Euler angles order", + default = "XYZ", + items = ( + ("XYZ", "XYZ", "X rotation first, Y rotation second, Z rotation last"), + ("XZY", "XZY", "X rotation first, Z rotation second, Y rotation last"), + ("YXZ", "YXZ", "Y rotation first, X rotation second, Z rotation last"), + ("YZX", "YZX", "Y rotation first, Z rotation second, X rotation last"), + ("ZXY", "ZXY", "Z rotation first, X rotation second, Y rotation last"), + ("ZYX", "ZYX", "Z rotation first, Y rotation second, X rotation last") + ) + ) + mult_order = EnumProperty( + name = "Scale/Rot/Transl mult order", + description = "Export animations in the specified matrix multiplication order", + default = "SRT", + items = ( + ("TRS", "TRS", "Translation first, Rotation second, Scaling last"), + ("TSR", "TSR", "Translation first, Scaling second, Rotation last"), + ("RTS", "RTS", "Rotation first, Translation second, Scaling last"), + ("RST", "RST", "Rotation first, Scaling second, Translation last"), + ("STR", "STR", "Scaling first, Translation second, Rotation last"), + ("SRT", "SRT", "Scaling first, Rotation second, Translation last") + ) + ) # what the importer actually does def execute(self, context): return export_bck_func(self, context) diff --git a/bck_import.py b/bck_import.py index e9ec6e8..47e99ad 100644 --- a/bck_import.py +++ b/bck_import.py @@ -10,7 +10,7 @@ import mathutils # import BCK animation into the selected armature object # creates a new "action" and writes the data into that action slot -def import_bck_func(context, filepath, import_type, angle_limit): +def import_bck_func(context, options): # # this thing is always needed for stuff scene = bpy.context.scene @@ -25,11 +25,11 @@ def import_bck_func(context, filepath, import_type, angle_limit): return {"FINISHED"} # open the binary file and read its data - anim = bck_funcs.read_bck_file(filepath) + anim = bck_funcs.read_bck_file(options.filepath) print(anim) if (anim == None): blender_funcs.disp_msg("Animation file: \"%s\" is malformed." - % (file_ops.get_file_name(filepath))) + % (file_ops.get_file_name(options.filepath))) return {"FINISHED"} # select the armature object @@ -40,14 +40,17 @@ def import_bck_func(context, filepath, import_type, angle_limit): # check if the bone count matches (the only check it can be done) if (len(armature.data.bones) != anim.bone_count): blender_funcs.disp_msg("Animation file \"%s\" contains incorrect number of bones" - % (file_ops.get_file_name(filepath))) + % (file_ops.get_file_name(options.filepath))) return {"FINISHED"} # check import_type to know what to do - file_name = file_ops.get_file_name(filepath) + file_name = file_ops.get_file_name(options.filepath) + + # change the bones's rotation mode to match the rotation mode going to be imported? + # (thing to think later) # clear rest pose and import the animation data directly - if (import_type == "OPT_A"): + if (options.import_type == "OPT_A"): # # select the armature object and its children meshes and duplicate it old_armature = armature @@ -202,7 +205,7 @@ def import_bck_func(context, filepath, import_type, angle_limit): # mantain rest pose and import the animation data respect to rest pose # "OPT_B" --> sample everything # "OPT_C" --> sample everything and find "best" interpolator fit - elif (import_type == "OPT_B" or import_type == "OPT_C"): + elif (options.import_type == "OPT_B" or options.import_type == "OPT_C"): # its matrix time @@ -374,10 +377,12 @@ def import_bck_func(context, filepath, import_type, angle_limit): # ~ print(rot) # ~ print(transl) # convert the just got frame anim values to be rest pose relative - mat = rest_mat.inverted() * math_funcs.calc_transf_mat(scale, rot, transl) + mat = rest_mat.inverted() * math_funcs.calc_transf_mat(scale, rot, transl, + options.euler_mode, + options.mult_order) # extract the new animation data new_scale = mat.to_scale() - new_rot = mat.to_euler("XYZ") + new_rot = mat.to_euler(options.euler_mode) new_transl = mat.to_translation() # check if new data must be added @@ -441,7 +446,7 @@ def import_bck_func(context, filepath, import_type, angle_limit): # now, if this has not been hard enough... # find the "best" interpolator fits to be able to simplify the keyframe count - if (import_type == "OPT_C"): + if (options.import_type == "OPT_C"): # lets start for fcurve in action.fcurves: # skip 1 frame animations @@ -453,7 +458,7 @@ def import_bck_func(context, filepath, import_type, angle_limit): for i in range(lowest_anim_frame, greatest_anim_frame + 1): values.append(fcurve.evaluate(i)) print(fcurve.data_path) - new_kfs = math_funcs.find_best_cubic_hermite_spline_fit(lowest_anim_frame, values, angle_limit) + new_kfs = math_funcs.find_best_cubic_hermite_spline_fit(lowest_anim_frame, values, options.angle_limit) print(new_kfs) # remove all keyframe points and add the new ones @@ -496,13 +501,13 @@ def import_bck_func(context, filepath, import_type, angle_limit): # check if nothing touched the type of this property import idprop if (type(armature.data["loop_mode"]) != idprop.types.IDPropertyGroup): - armature.data["loop_mode"] = {file_ops.get_file_name(filepath) : anim.loop_mode} + armature.data["loop_mode"] = {file_ops.get_file_name(options.filepath) : anim.loop_mode} else: - armature.data["loop_mode"][file_ops.get_file_name(filepath)] = anim.loop_mode + armature.data["loop_mode"][file_ops.get_file_name(options.filepath)] = anim.loop_mode else: - armature.data["loop_mode"] = {file_ops.get_file_name(filepath) : anim.loop_mode} + armature.data["loop_mode"] = {file_ops.get_file_name(options.filepath) : anim.loop_mode} # display some message - blender_funcs.disp_msg("Animation file \"%s\" imported." % (file_ops.get_file_name(filepath))) + blender_funcs.disp_msg("Animation file \"%s\" imported." % (file_ops.get_file_name(options.filepath))) # done! return {"FINISHED"} @@ -543,13 +548,36 @@ class import_bck(Operator, ExportHelper): min = 0, max = 180, ) + euler_mode = EnumProperty( + name = "Euler order", + description = "Import rotation animations in the specified Euler angles order", + default = "XYZ", + items = ( + ("XYZ", "XYZ", "X rotation first, Y rotation second, Z rotation last"), + ("XZY", "XZY", "X rotation first, Z rotation second, Y rotation last"), + ("YXZ", "YXZ", "Y rotation first, X rotation second, Z rotation last"), + ("YZX", "YZX", "Y rotation first, Z rotation second, X rotation last"), + ("ZXY", "ZXY", "Z rotation first, X rotation second, Y rotation last"), + ("ZYX", "ZYX", "Z rotation first, Y rotation second, X rotation last") + ) + ) + mult_order = EnumProperty( + name = "Scale/Rot/Transl mult order", + description = "Import animations in the specified matrix multiplication order", + default = "SRT", + items = ( + ("TRS", "TRS", "Translation first, Rotation second, Scaling last"), + ("TSR", "TSR", "Translation first, Scaling second, Rotation last"), + ("RTS", "RTS", "Rotation first, Translation second, Scaling last"), + ("RST", "RST", "Rotation first, Scaling second, Translation last"), + ("STR", "STR", "Scaling first, Translation second, Rotation last"), + ("SRT", "SRT", "Scaling first, Rotation second, Translation last") + ) + ) # what the importer actually does def execute(self, context): - return import_bck_func(context, - self.filepath, - self.import_type, - self.angle_limit) + return import_bck_func(context, self) # stuff to append the item to the File -> Import/Export menu def menu_import_bck(self, context): diff --git a/collada_superbmd_export.py b/collada_superbmd_export.py index 1afae87..f66085c 100644 --- a/collada_superbmd_export.py +++ b/collada_superbmd_export.py @@ -91,7 +91,8 @@ def write_bmd_bdl_collada(context, filepath, triangulate): vertex_weight = 0 for group in vertex.groups: vertex_weight += group.weight - if (vertex_weight > 1.0): + # calling round because there seems to be floating point issues here + if (round(vertex_weight, 5) > 1): blender_funcs.disp_msg(("\"%s\": contains non normalized weight in vertices." % (mesh.name)) + " Unable to continue.") return {"FINISHED"} @@ -128,6 +129,14 @@ def write_bmd_bdl_collada(context, filepath, triangulate): armature.children[i].name = child_names[i] armature.children[i].data.name = child_names[i] + # delete all the rest_mat/bind_mat custom property matrices + # they seem to alter exported models in a weird way (I would expect it is a Assimp thing) + for bone in armature.data.bones: + if ("bind_mat" in bone): + bone.pop("bind_mat") + if ("rest_mat" in bone): + bone.pop("rest_mat") + # export the object bpy.ops.wm.collada_export(filepath = filepath, use_blender_profile = False, selected = True, include_children = True, diff --git a/math_funcs.py b/math_funcs.py index 658797a..db8c44b 100644 --- a/math_funcs.py +++ b/math_funcs.py @@ -34,21 +34,34 @@ def calc_scale_matrix(sx, sy, sz): # calc_rotation_matrix function # function to calculate the rotation matrix # for a Extrinsic Euler XYZ system (radians) -def calc_rotation_matrix(rx, ry, rz): - - x_rot = mathutils.Matrix(([1, 0, 0, 0], - [0, math.cos(rx), -math.sin(rx), 0], - [0, math.sin(rx), math.cos(rx), 0], - [0, 0, 0, 1])) - y_rot = mathutils.Matrix(([math.cos(ry), 0, math.sin(ry), 0], - [0, 1, 0, 0], - [-math.sin(ry), 0, math.cos(ry), 0], - [0, 0, 0, 1])) - z_rot = mathutils.Matrix(([math.cos(rz), -math.sin(rz), 0, 0], - [math.sin(rz), math.cos(rz), 0, 0], - [0, 0, 1, 0], - [0, 0, 0, 1])) +def calc_rotation_matrix(rx, ry, rz, order): + # calculate the axis rotation matrices + x_rot = mathutils.Matrix(([1, 0, 0, 0], + [0, math.cos(rx), -math.sin(rx), 0], + [0, math.sin(rx), math.cos(rx), 0], + [0, 0, 0, 1])) + y_rot = mathutils.Matrix(([math.cos(ry), 0, math.sin(ry), 0], + [0, 1, 0, 0], + [-math.sin(ry), 0, math.cos(ry), 0], + [0, 0, 0, 1])) + z_rot = mathutils.Matrix(([math.cos(rz), -math.sin(rz), 0, 0], + [math.sin(rz), math.cos(rz), 0, 0], + [0, 0, 1, 0], + [0, 0, 0, 1])) + # check the rotation order + if (order == "XYZ"): return z_rot * y_rot * x_rot + elif (order == "XZY"): + return y_rot * z_rot * x_rot + elif (order == "YXZ"): + return z_rot * x_rot * y_rot + elif (order == "YZX"): + return x_rot * z_rot * y_rot + elif (order == "ZXY"): + return y_rot * x_rot * z_rot + elif (order == "ZYX"): + return x_rot * y_rot * z_rot + return None # calc_translation_matrix function # function to build the translation matrix @@ -60,12 +73,26 @@ def calc_translation_matrix(tx, ty, tz): [0, 0, 0, 1])) return mat -# calculate transformation matrix for blender -def calc_transf_mat(scale, rotation, translation): - mat = calc_translation_matrix(translation[0], translation[1], translation[2]) - mat *= calc_rotation_matrix(rotation[0], rotation[1], rotation[2]) - mat *= calc_scale_matrix(scale[0], scale[1], scale[2]) - return mat +# calculate a transformation matrix (euler) +def calc_transf_mat(scale, rotation, translation, rot_order, mult_order): + # get the 3 matrices + transl = calc_translation_matrix(translation[0], translation[1], translation[2]) + rot = calc_rotation_matrix(rotation[0], rotation[1], rotation[2], rot_order) + scale = calc_scale_matrix(scale[0], scale[1], scale[2]) + # check the multiplication order + if (mult_order == "TRS"): + return scale * rot * transl + elif (mult_order == "TSR"): + return rot * scale * transl + elif (mult_order == "RTS"): + return scale * transl * rot + elif (mult_order == "RST"): + return transl * scale * rot + elif (mult_order == "STR"): + return rot * transl * scale + elif (mult_order == "SRT"): + return transl * rot * scale + return None # calculate a value of a cubic hermite interpolation # it is assumed t0 and tf are 0 and 1 @@ -115,6 +142,8 @@ class best_chs_fits: # each frame of the animation (start_frame indicates the start frame, integer) # the function will return the above structure # it will be in the general cubic hermite spline form (t0 < tf) + +# make it so that keyframe trigger variables can be modified def find_best_cubic_hermite_spline_fit(start_frame, values, angle_limit): # check |