summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorOwl <isaclien9752@gmail.com>2025-08-27 23:54:31 -0400
committerOwl <isaclien9752@gmail.com>2025-08-27 23:54:31 -0400
commitf4ff299e4e073b71495a3e2a385fa96d65fb6f80 (patch)
tree02ccd603b6f3907d108abd3d3d76ecdb1235b14a
parentea2eeb7d18869bb19773782289dca325c14776d0 (diff)
downloadblenxy-f4ff299e4e073b71495a3e2a385fa96d65fb6f80.tar.gz
blenxy-f4ff299e4e073b71495a3e2a385fa96d65fb6f80.zip
rotations people, rotations...
-rw-r--r--__init__.py28
-rw-r--r--bck_export.py125
-rw-r--r--bck_import.py66
-rw-r--r--collada_superbmd_export.py11
-rw-r--r--math_funcs.py69
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