[Bf-blender-cvs] [623ff64a278] master: Fix T81541: Symmetrize Transform Constraint, Y rotational axis unexpected results

Sebastian Parborg noreply at git.blender.org
Fri Feb 4 14:21:28 CET 2022


Commit: 623ff64a278924af57d7e1ec7e7bdb8792a560f8
Author: Sebastian Parborg
Date:   Fri Feb 4 14:19:44 2022 +0100
Branches: master
https://developer.blender.org/rB623ff64a278924af57d7e1ec7e7bdb8792a560f8

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

===================================================================

M	source/blender/editors/armature/armature_add.c
M	tests/python/CMakeLists.txt
A	tests/python/bl_rigging_symmetrize.py

===================================================================

diff --git a/source/blender/editors/armature/armature_add.c b/source/blender/editors/armature/armature_add.c
index 02ecfdb4ea6..4a327904ddd 100644
--- a/source/blender/editors/armature/armature_add.c
+++ b/source/blender/editors/armature/armature_add.c
@@ -581,8 +581,6 @@ static void updateDuplicateLocRotConstraintSettings(Object *ob,
 {
   /* This code assumes that bRotLimitConstraint and bLocLimitConstraint have the same fields in
    * the same memory locations. */
-  BLI_assert(sizeof(bLocLimitConstraint) == sizeof(bRotLimitConstraint));
-
   bRotLimitConstraint *limit = (bRotLimitConstraint *)curcon->data;
   float local_mat[4][4], imat[4][4];
 
@@ -798,6 +796,13 @@ static void updateDuplicateTransformConstraintSettings(Object *ob,
           trans->to_min_rot[i] = temp_vec[i];
         }
       }
+
+      if (trans->from == TRANS_ROTATION && trans->map[1] == Y) {
+        /* Y Rot to Y Rot: Flip and invert */
+        trans->to_max_rot[1] = -trans->to_min_rot[1];
+        trans->to_min_rot[1] = -temp_vec[1];
+      }
+
       break;
   }
   /* convert back to the settings space */
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 cont

@@ Diff output truncated at 10240 characters. @@



More information about the Bf-blender-cvs mailing list