diff options
-rw-r--r-- | release/scripts/startup/bl_ui/properties_constraint.py | 1 | ||||
-rw-r--r-- | source/blender/blenkernel/intern/constraint.c | 36 | ||||
-rw-r--r-- | source/blender/blenlib/BLI_math_matrix.h | 1 | ||||
-rw-r--r-- | source/blender/blenlib/intern/math_matrix.c | 22 | ||||
-rw-r--r-- | source/blender/editors/transform/transform_convert.c | 5 | ||||
-rw-r--r-- | source/blender/makesdna/DNA_constraint_types.h | 17 | ||||
-rw-r--r-- | source/blender/makesrna/intern/rna_constraint.c | 48 | ||||
-rw-r--r-- | tests/python/bl_constraints.py | 64 |
8 files changed, 183 insertions, 11 deletions
diff --git a/release/scripts/startup/bl_ui/properties_constraint.py b/release/scripts/startup/bl_ui/properties_constraint.py index a88def34767..2a0cf56534c 100644 --- a/release/scripts/startup/bl_ui/properties_constraint.py +++ b/release/scripts/startup/bl_ui/properties_constraint.py @@ -505,6 +505,7 @@ class ConstraintButtonsPanel: self.target_template(layout, con) + layout.prop(con, "remove_target_shear") layout.prop(con, "mix_mode", text="Mix") self.space_template(layout, con) diff --git a/source/blender/blenkernel/intern/constraint.c b/source/blender/blenkernel/intern/constraint.c index 193b6d82d03..41da728fa5b 100644 --- a/source/blender/blenkernel/intern/constraint.c +++ b/source/blender/blenkernel/intern/constraint.c @@ -2235,17 +2235,47 @@ static void translike_evaluate(bConstraint *con, bConstraintOb *cob, ListBase *t bConstraintTarget *ct = targets->first; if (VALID_CONS_TARGET(ct)) { + float target_mat[4][4]; + + copy_m4_m4(target_mat, ct->matrix); + + /* Remove the shear of the target matrix if enabled. + * Use Y as the axis since it's the natural default for bones. */ + if (data->flag & TRANSLIKE_REMOVE_TARGET_SHEAR) { + orthogonalize_m4_stable(target_mat, 1, false); + } + + /* Finally, combine the matrices. */ switch (data->mix_mode) { case TRANSLIKE_MIX_REPLACE: - copy_m4_m4(cob->matrix, ct->matrix); + copy_m4_m4(cob->matrix, target_mat); + break; + + /* Simple matrix multiplication. */ + case TRANSLIKE_MIX_BEFORE_FULL: + mul_m4_m4m4(cob->matrix, target_mat, cob->matrix); + break; + + case TRANSLIKE_MIX_AFTER_FULL: + mul_m4_m4m4(cob->matrix, cob->matrix, target_mat); break; + /* Aligned Inherit Scale emulation. */ case TRANSLIKE_MIX_BEFORE: - mul_m4_m4m4_aligned_scale(cob->matrix, ct->matrix, cob->matrix); + mul_m4_m4m4_aligned_scale(cob->matrix, target_mat, cob->matrix); break; case TRANSLIKE_MIX_AFTER: - mul_m4_m4m4_aligned_scale(cob->matrix, cob->matrix, ct->matrix); + mul_m4_m4m4_aligned_scale(cob->matrix, cob->matrix, target_mat); + break; + + /* Fully separate handling of channels. */ + case TRANSLIKE_MIX_BEFORE_SPLIT: + mul_m4_m4m4_split_channels(cob->matrix, target_mat, cob->matrix); + break; + + case TRANSLIKE_MIX_AFTER_SPLIT: + mul_m4_m4m4_split_channels(cob->matrix, cob->matrix, target_mat); break; default: diff --git a/source/blender/blenlib/BLI_math_matrix.h b/source/blender/blenlib/BLI_math_matrix.h index 54df88ca541..0c3a184d302 100644 --- a/source/blender/blenlib/BLI_math_matrix.h +++ b/source/blender/blenlib/BLI_math_matrix.h @@ -211,6 +211,7 @@ void mul_transposed_mat3_m4_v3(const float M[4][4], float r[3]); void mul_m3_v3_double(const float M[3][3], double r[3]); void mul_m4_m4m4_aligned_scale(float R[4][4], const float A[4][4], const float B[4][4]); +void mul_m4_m4m4_split_channels(float R[4][4], const float A[4][4], const float B[4][4]); void mul_m3_fl(float R[3][3], float f); void mul_m4_fl(float R[4][4], float f); diff --git a/source/blender/blenlib/intern/math_matrix.c b/source/blender/blenlib/intern/math_matrix.c index 5eb0125062d..88bef854213 100644 --- a/source/blender/blenlib/intern/math_matrix.c +++ b/source/blender/blenlib/intern/math_matrix.c @@ -1290,6 +1290,9 @@ bool invert_m4_m4(float inverse[4][4], const float mat[4][4]) * Combines transformations, handling scale separately in a manner equivalent * to the Aligned Inherit Scale mode, in order to avoid creating shear. * If A scale is uniform, the result is equivalent to ordinary multiplication. + * + * Note: this effectively takes output location from simple multiplication, + * and uses mul_m4_m4m4_split_channels for rotation and scale. */ void mul_m4_m4m4_aligned_scale(float R[4][4], const float A[4][4], const float B[4][4]) { @@ -1307,6 +1310,25 @@ void mul_m4_m4m4_aligned_scale(float R[4][4], const float A[4][4], const float B loc_rot_size_to_mat4(R, loc_r, rot_r, size_r); } +/** + * Separately combines location, rotation and scale of the input matrices. + */ +void mul_m4_m4m4_split_channels(float R[4][4], const float A[4][4], const float B[4][4]) +{ + float loc_a[3], rot_a[3][3], size_a[3]; + float loc_b[3], rot_b[3][3], size_b[3]; + float loc_r[3], rot_r[3][3], size_r[3]; + + mat4_to_loc_rot_size(loc_a, rot_a, size_a, A); + mat4_to_loc_rot_size(loc_b, rot_b, size_b, B); + + add_v3_v3v3(loc_r, loc_a, loc_b); + mul_m3_m3m3_uniq(rot_r, rot_a, rot_b); + mul_v3_v3v3(size_r, size_a, size_b); + + loc_rot_size_to_mat4(R, loc_r, rot_r, size_r); +} + /****************************** Linear Algebra *******************************/ void transpose_m3(float R[3][3]) diff --git a/source/blender/editors/transform/transform_convert.c b/source/blender/editors/transform/transform_convert.c index 99368a40225..00fd008151d 100644 --- a/source/blender/editors/transform/transform_convert.c +++ b/source/blender/editors/transform/transform_convert.c @@ -849,10 +849,13 @@ bool constraints_list_needinv(TransInfo *t, ListBase *list) /* Copy Transforms constraint only does this in the Before mode. */ bTransLikeConstraint *data = (bTransLikeConstraint *)con->data; - if (ELEM(data->mix_mode, TRANSLIKE_MIX_BEFORE) && + if (ELEM(data->mix_mode, TRANSLIKE_MIX_BEFORE, TRANSLIKE_MIX_BEFORE_FULL) && ELEM(t->mode, TFM_ROTATION, TFM_TRANSLATION)) { return true; } + if (ELEM(data->mix_mode, TRANSLIKE_MIX_BEFORE_SPLIT) && ELEM(t->mode, TFM_ROTATION)) { + return true; + } } else if (con->type == CONSTRAINT_TYPE_ACTION) { /* The Action constraint only does this in the Before mode. */ diff --git a/source/blender/makesdna/DNA_constraint_types.h b/source/blender/makesdna/DNA_constraint_types.h index 1e4fd2a70f2..25f72da2f15 100644 --- a/source/blender/makesdna/DNA_constraint_types.h +++ b/source/blender/makesdna/DNA_constraint_types.h @@ -316,8 +316,9 @@ typedef struct bSameVolumeConstraint { /* Copy Transform Constraint */ typedef struct bTransLikeConstraint { struct Object *tar; + int flag; char mix_mode; - char _pad[7]; + char _pad[3]; /** MAX_ID_NAME-2. */ char subtarget[64]; } bTransLikeConstraint; @@ -810,6 +811,12 @@ typedef enum eCopyScale_Flags { SIZELIKE_UNIFORM = (1 << 5), } eCopyScale_Flags; +/* bTransLikeConstraint.flag */ +typedef enum eCopyTransforms_Flags { + /* Remove shear from the target matrix. */ + TRANSLIKE_REMOVE_TARGET_SHEAR = (1 << 0), +} eCopyTransforms_Flags; + /* bTransLikeConstraint.mix_mode */ typedef enum eCopyTransforms_MixMode { /* Replace rotation channel values. */ @@ -818,6 +825,14 @@ typedef enum eCopyTransforms_MixMode { TRANSLIKE_MIX_BEFORE = 1, /* Multiply the copied transformation on the right, with anti-shear scale handling. */ TRANSLIKE_MIX_AFTER = 2, + /* Multiply the copied transformation on the left, handling loc/rot/scale separately. */ + TRANSLIKE_MIX_BEFORE_SPLIT = 3, + /* Multiply the copied transformation on the right, handling loc/rot/scale separately. */ + TRANSLIKE_MIX_AFTER_SPLIT = 4, + /* Multiply the copied transformation on the left, using simple matrix multiplication. */ + TRANSLIKE_MIX_BEFORE_FULL = 5, + /* Multiply the copied transformation on the right, using simple matrix multiplication. */ + TRANSLIKE_MIX_AFTER_FULL = 6, } eCopyTransforms_MixMode; /* bTransformConstraint.to/from */ diff --git a/source/blender/makesrna/intern/rna_constraint.c b/source/blender/makesrna/intern/rna_constraint.c index d79f2c4d13f..d34885e1a68 100644 --- a/source/blender/makesrna/intern/rna_constraint.c +++ b/source/blender/makesrna/intern/rna_constraint.c @@ -1624,18 +1624,48 @@ static void rna_def_constraint_transform_like(BlenderRNA *brna) 0, "Replace", "Replace the original transformation with copied"}, + {0, "", 0, NULL, NULL}, + {TRANSLIKE_MIX_BEFORE_FULL, + "BEFORE_FULL", + 0, + "Before Original (Full)", + "Apply copied transformation before original, using simple matrix multiplication as if " + "the constraint target is a parent in Full Inherit Scale mode. " + "Will create shear when combining rotation and non-uniform scale"}, {TRANSLIKE_MIX_BEFORE, "BEFORE", 0, - "Before Original", - "Apply copied transformation before original, as if the constraint target is a parent. " - "Scale is handled specially to avoid creating shear"}, + "Before Original (Aligned)", + "Apply copied transformation before original, as if the constraint target is a parent in " + "Aligned Inherit Scale mode. This effectively uses Full for location and Split Channels " + "for rotation and scale"}, + {TRANSLIKE_MIX_BEFORE_SPLIT, + "BEFORE_SPLIT", + 0, + "Before Original (Split Channels)", + "Apply copied transformation before original, handling location, rotation and scale " + "separately, similar to a sequence of three Copy constraints"}, + {0, "", 0, NULL, NULL}, + {TRANSLIKE_MIX_AFTER_FULL, + "AFTER_FULL", + 0, + "After Original (Full)", + "Apply copied transformation after original, using simple matrix multiplication as if " + "the constraint target is a child in Full Inherit Scale mode. " + "Will create shear when combining rotation and non-uniform scale"}, {TRANSLIKE_MIX_AFTER, "AFTER", 0, - "After Original", - "Apply copied transformation after original, as if the constraint target is a child. " - "Scale is handled specially to avoid creating shear"}, + "After Original (Aligned)", + "Apply copied transformation after original, as if the constraint target is a child in " + "Aligned Inherit Scale mode. This effectively uses Full for location and Split Channels " + "for rotation and scale"}, + {TRANSLIKE_MIX_AFTER_SPLIT, + "AFTER_SPLIT", + 0, + "After Original (Split Channels)", + "Apply copied transformation after original, handling location, rotation and scale " + "separately, similar to a sequence of three Copy constraints"}, {0, NULL, 0, NULL, NULL}, }; @@ -1653,6 +1683,12 @@ static void rna_def_constraint_transform_like(BlenderRNA *brna) RNA_define_lib_overridable(true); + prop = RNA_def_property(srna, "remove_target_shear", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", TRANSLIKE_REMOVE_TARGET_SHEAR); + RNA_def_property_ui_text( + prop, "Remove Target Shear", "Remove shear from the target transformation before combining"); + RNA_def_property_update(prop, NC_OBJECT | ND_CONSTRAINT, "rna_Constraint_update"); + prop = RNA_def_property(srna, "mix_mode", PROP_ENUM, PROP_NONE); RNA_def_property_enum_sdna(prop, NULL, "mix_mode"); RNA_def_property_enum_items(prop, mix_mode_items); diff --git a/tests/python/bl_constraints.py b/tests/python/bl_constraints.py index 279c896c6af..a2690fa4e11 100644 --- a/tests/python/bl_constraints.py +++ b/tests/python/bl_constraints.py @@ -375,6 +375,70 @@ class CustomSpaceTest(AbstractConstraintTests): ))) +class CopyTransformsTest(AbstractConstraintTests): + layer_collection = 'Copy Transforms' + + def test_mix_mode_object(self): + """Copy Transforms: all mix modes for objects""" + constraint = bpy.data.objects["Copy Transforms.object.owner"].constraints["Copy Transforms"] + + constraint.mix_mode = 'REPLACE' + self.matrix_test('Copy Transforms.object.owner', Matrix(( + (-0.7818737626075745, 0.14389121532440186, 0.4845699667930603, -0.017531070858240128), + (-0.2741589844226837, -0.591389000415802, -1.2397242784500122, -0.08039521425962448), + (0.04909384995698929, -1.0109175443649292, 0.7942137122154236, 0.1584688276052475), + (0.0, 0.0, 0.0, 1.0) + ))) + + constraint.mix_mode = 'BEFORE_FULL' + self.matrix_test('Copy Transforms.object.owner', Matrix(( + (-1.0791258811950684, -0.021011866629123688, 0.3120136260986328, 0.9082338809967041), + (0.2128538191318512, -0.3411901891231537, -1.7376484870910645, -0.39762523770332336), + (-0.03584420680999756, -1.0162957906723022, 0.8004404306411743, -0.9015425443649292), + (0.0, 0.0, 0.0, 1.0) + ))) + + constraint.mix_mode = 'BEFORE' + self.matrix_test('Copy Transforms.object.owner', Matrix(( + (-0.9952367544174194, -0.03077685832977295, 0.05301344022154808, 0.9082338809967041), + (-0.013416174799203873, -0.39984768629074097, -1.8665285110473633, -0.39762523770332336), + (0.03660336509346962, -0.9833710193634033, 0.75728839635849, -0.9015425443649292), + (0.0, 0.0, 0.0, 1.0) + ))) + + constraint.mix_mode = 'BEFORE_SPLIT' + self.matrix_test('Copy Transforms.object.owner', Matrix(( + (-0.9952367544174194, -0.03077685832977295, 0.05301344022154808, -1.0175310373306274), + (-0.013416174799203873, -0.39984768629074097, -1.8665285110473633, 0.9196047782897949), + (0.03660336509346962, -0.9833710193634033, 0.75728839635849, 0.1584688276052475), + (0.0, 0.0, 0.0, 1.0) + ))) + + constraint.mix_mode = 'AFTER_FULL' + self.matrix_test('Copy Transforms.object.owner', Matrix(( + (-0.8939255475997925, -0.2866469621658325, 0.7563635110855103, -0.964445173740387), + (-0.09460853785276413, -0.73727947473526, -1.0267245769500732, 0.9622588753700256), + (0.37042146921157837, -1.1893107891082764, 1.0113294124603271, 0.21314144134521484), + (0.0, 0.0, 0.0, 1.0) + ))) + + constraint.mix_mode = 'AFTER' + self.matrix_test('Copy Transforms.object.owner', Matrix(( + (-0.9033845067024231, -0.2048732340335846, 0.7542480826377869, -0.964445173740387), + (-0.1757974475622177, -0.6721230745315552, -1.5190268754959106, 0.9622588753700256), + (0.38079890608787537, -0.7963172793388367, 1.0880682468414307, 0.21314144134521484), + (0.0, 0.0, 0.0, 1.0) + ))) + + constraint.mix_mode = 'AFTER_SPLIT' + self.matrix_test('Copy Transforms.object.owner', Matrix(( + (-0.9033845067024231, -0.2048732340335846, 0.7542480826377869, -1.0175310373306274), + (-0.1757974475622177, -0.6721230745315552, -1.5190268754959106, 0.9196047782897949), + (0.38079890608787537, -0.7963172793388367, 1.0880682468414307, 0.1584688276052475), + (0.0, 0.0, 0.0, 1.0) + ))) + + def main(): global args import argparse |