Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorSebastian Parborg <darkdefende@gmail.com>2022-02-04 16:19:44 +0300
committerSebastian Parborg <darkdefende@gmail.com>2022-02-04 16:21:20 +0300
commit623ff64a278924af57d7e1ec7e7bdb8792a560f8 (patch)
tree889a50d215cd1d98b0d7a87fa3d25e32a3aa142e /tests
parent0dd3e77d7177cbf75a1370e594020c02e48cfa10 (diff)
Fix T81541: Symmetrize Transform Constraint, Y rotational axis unexpected results
The case where Y rotation is mapped to Y rotation was not handled. This is now fixed. Also added an automated test to make sure that the symmetrize operator functions as intended. Reviewed By: Sybren Differential Revision: http://developer.blender.org/D9214
Diffstat (limited to 'tests')
-rw-r--r--tests/python/CMakeLists.txt7
-rw-r--r--tests/python/bl_rigging_symmetrize.py244
2 files changed, 251 insertions, 0 deletions
diff --git a/tests/python/CMakeLists.txt b/tests/python/CMakeLists.txt
index 63dcdc0f925..04fdb380da2 100644
--- a/tests/python/CMakeLists.txt
+++ b/tests/python/CMakeLists.txt
@@ -293,6 +293,13 @@ add_blender_test(
--testdir "${TEST_SRC_DIR}/animation"
)
+add_blender_test(
+ bl_rigging_symmetrize
+ --python ${CMAKE_CURRENT_LIST_DIR}/bl_rigging_symmetrize.py
+ --
+ --testdir "${TEST_SRC_DIR}/animation"
+)
+
# ------------------------------------------------------------------------------
# IO TESTS
diff --git a/tests/python/bl_rigging_symmetrize.py b/tests/python/bl_rigging_symmetrize.py
new file mode 100644
index 00000000000..b47ace7f3f1
--- /dev/null
+++ b/tests/python/bl_rigging_symmetrize.py
@@ -0,0 +1,244 @@
+# ##### 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 #####
+
+# <pep8 compliant>
+
+"""
+blender -b -noaudio --factory-startup --python tests/python/bl_rigging_symmetrize.py -- --testdir /path/to/lib/tests/animation
+"""
+
+import pathlib
+import sys
+import unittest
+
+import bpy
+
+
+def check_loc_rot_scale(self, bone, exp_bone):
+ # Check if posistions are the same
+ self.assertEqualVector(
+ bone.head, exp_bone.head, "Head position", bone.name)
+ self.assertEqualVector(
+ bone.tail, exp_bone.tail, "Tail position", bone.name)
+
+ # Scale
+ self.assertEqualVector(
+ bone.scale, exp_bone.scale, "Scale", bone.name)
+
+ # Rotation
+ rot_mode = exp_bone.rotation_mode
+ self.assertEqual(bone.rotation_mode, rot_mode, "Rotations mode does not match on bone %s" % (bone.name))
+
+ if rot_mode == 'QUATERNION':
+ self.assertEqualVector(
+ bone.rotation_quaternion, exp_bone.rotation_quaternion, "Quaternion rotation", bone.name)
+ elif rot_mode == 'AXIS_ANGLE':
+ self.assertEqualVector(
+ bone.axis_angle, exp_bone.axis_angle, "Axis Angle rotation", bone.name)
+ else:
+ # Euler rotation
+ self.assertEqualVector(
+ bone.rotation_euler, exp_bone.rotation_euler, "Euler rotation", bone.name)
+
+
+def check_parent(self, bone, exp_bone):
+ self.assertEqual(type(bone.parent), type(exp_bone.parent),
+ "Missmatching types in pose.bones[%s].parent" % (bone.name))
+ self.assertTrue(bone.parent is None or bone.parent.name == exp_bone.parent.name,
+ "Bone parent does not match on bone %s" % (bone.name))
+
+
+def check_bendy_bones(self, bone, exp_bone):
+ bone_variables = bone.bl_rna.properties.keys()
+
+ bendy_bone_variables = [
+ var for var in bone_variables if var.startswith("bbone_")]
+
+ for var in bendy_bone_variables:
+ value = getattr(bone, var)
+ exp_value = getattr(exp_bone, var)
+
+ self.assertEqual(type(value), type(exp_value),
+ "Missmatching types in pose.bones[%s].%s" % (bone.name, var))
+
+ if isinstance(value, str):
+ self.assertEqual(value, exp_value,
+ "Missmatching value in pose.bones[%s].%s" % (bone.name, var))
+ elif hasattr(value, "name"):
+ self.assertEqual(value.name, exp_value.name,
+ "Missmatching value in pose.bones[%s].%s" % (bone.name, var))
+ else:
+ self.assertAlmostEqual(value, exp_value,
+ "Missmatching value in pose.bones[%s].%s" % (bone.name, var))
+
+
+def check_ik(self, bone, exp_bone):
+ bone_variables = bone.bl_rna.properties.keys()
+ prefixes = ("ik_", "lock_ik", "use_ik")
+ ik_bone_variables = (
+ var for var in bone_variables
+ if var.startswith(prefixes)
+ )
+
+ for var in ik_bone_variables:
+ value = getattr(bone, var)
+ exp_value = getattr(exp_bone, var)
+ self.assertAlmostEqual(value, exp_value,
+ "Missmatching value in pose.bones[%s].%s" % (bone.name, var))
+
+
+def check_constraints(self, input_arm, expected_arm, bone, exp_bone):
+ const_len = len(bone.constraints)
+ expo_const_len = len(exp_bone.constraints)
+
+ self.assertEqual(const_len, expo_const_len,
+ "Constraints missmatch on bone %s" % (bone.name))
+
+ for exp_constraint in exp_bone.constraints:
+ const_name = exp_constraint.name
+ # Make sure that the constraint exists
+ self.assertTrue(const_name in bone.constraints,
+ "Bone %s is expected to contain constraint %s, but it does not." % (
+ bone.name, const_name))
+ constraint = bone.constraints[const_name]
+ const_variables = constraint.bl_rna.properties.keys()
+
+ for var in const_variables:
+
+ if var == "is_override_data":
+ # This variable is not used for local (non linked) data.
+ # For local object it is not initialized, so don't check this value.
+ continue
+
+ value = getattr(constraint, var)
+ exp_value = getattr(exp_constraint, var)
+
+ self.assertEqual(type(value), type(exp_value),
+ "Missmatching constraint value types in pose.bones[%s].constraints[%s].%s" % (
+ bone.name, const_name, var))
+
+ if isinstance(value, str):
+ self.assertEqual(value, exp_value,
+ "Missmatching constraint value in pose.bones[%s].constraints[%s].%s" % (
+ bone.name, const_name, var))
+ elif hasattr(value, "name"):
+ # Some constraints targets the armature itself, so the armature name should missmatch.
+ if value.name == input_arm.name and exp_value.name == expected_arm.name:
+ continue
+
+ self.assertEqual(value.name, exp_value.name,
+ "Missmatching constraint value in pose.bones[%s].constraints[%s].%s" % (
+ bone.name, const_name, var))
+
+ elif isinstance(value, bool):
+ self.assertEqual(value, exp_value,
+ "Missmatching constraint boolean in pose.bones[%s].constraints[%s].%s" % (
+ bone.name, const_name, var))
+ else:
+ self.assertAlmostEqual(value, exp_value,
+ "Missmatching constraint value in pose.bones[%s].constraints[%s].%s" % (
+ bone.name, const_name, var))
+
+
+class AbstractAnimationTest:
+ @classmethod
+ def setUpClass(cls):
+ cls.testdir = args.testdir
+
+ def setUp(self):
+ self.assertTrue(self.testdir.exists(),
+ 'Test dir %s should exist' % self.testdir)
+
+
+class ArmatureSymmetrizeTest(AbstractAnimationTest, unittest.TestCase):
+ def test_symmetrize_operator(self):
+ """Test that the symmetrize operator is working correctly."""
+ bpy.ops.wm.open_mainfile(filepath=str(
+ self.testdir / "symm_test.blend"))
+
+ # T81541 (D9214)
+ arm = bpy.data.objects['transform_const_rig']
+ expected_arm = bpy.data.objects['expected_transform_const_rig']
+ self.assertEqualSymmetrize(arm, expected_arm)
+
+ # T66751 (D6009)
+ arm = bpy.data.objects['dragon_rig']
+ expected_arm = bpy.data.objects['expected_dragon_rig']
+ self.assertEqualSymmetrize(arm, expected_arm)
+
+ def assertEqualSymmetrize(self, input_arm, expected_arm):
+
+ # Symmetrize our input armature
+ bpy.context.view_layer.objects.active = input_arm
+ bpy.ops.object.mode_set(mode='EDIT')
+ bpy.ops.armature.select_all(action='SELECT')
+ bpy.ops.armature.symmetrize()
+ bpy.ops.object.mode_set(mode='OBJECT')
+
+ # Make sure that the bone count is the same
+ bone_len = len(input_arm.pose.bones)
+ expected_bone_len = len(expected_arm.pose.bones)
+ self.assertEqual(bone_len, expected_bone_len,
+ "Expected bone count to match")
+
+ for exp_bone in expected_arm.pose.bones:
+ bone_name = exp_bone.name
+ # Make sure that the bone exists
+ self.assertTrue(bone_name in input_arm.pose.bones,
+ "Armature is expected to contain bone %s, but it does not." % (bone_name))
+ bone = input_arm.pose.bones[bone_name]
+
+ # Loc Rot Scale
+ check_loc_rot_scale(self, bone, exp_bone)
+
+ # Parent settings
+ check_parent(self, bone, exp_bone)
+
+ # Bendy Bones
+ check_bendy_bones(self, bone, exp_bone)
+
+ # IK
+ check_ik(self, bone, exp_bone)
+
+ # Constraints
+ check_constraints(self, input_arm, expected_arm, bone, exp_bone)
+
+ def assertEqualVector(self, vec1, vec2, check_str, bone_name) -> None:
+ for idx, value in enumerate(vec1):
+ self.assertAlmostEqual(
+ value, vec2[idx], 3, "%s does not match with expected value on bone %s" % (check_str, bone_name))
+
+
+def main():
+ global args
+ import argparse
+
+ if '--' in sys.argv:
+ argv = [sys.argv[0]] + sys.argv[sys.argv.index('--') + 1:]
+ else:
+ argv = sys.argv
+
+ parser = argparse.ArgumentParser()
+ parser.add_argument('--testdir', required=True, type=pathlib.Path)
+ args, remaining = parser.parse_known_args(argv)
+
+ unittest.main(argv=remaining)
+
+
+if __name__ == "__main__":
+ main()