From 09181540b5aa889df6aa24bea4ce9a4823e0c18d Mon Sep 17 00:00:00 2001 From: Sebastian Parborg Date: Mon, 6 May 2019 09:47:45 +0200 Subject: [PATCH] Implement mirroring in pose mode (absolute and relative) Added working X-mirroring in pose mode with an optional relative mirror mode. Reviewed By: Campbell Barton Differential Revision: http://developer.blender.org/D4765 --- .../startup/bl_ui/space_view3d_toolbar.py | 8 +- .../blenloader/intern/versioning_280.c | 2 +- source/blender/editors/transform/transform.h | 25 ++++ .../editors/transform/transform_conversions.c | 132 ++++++++++++++++++ .../editors/transform/transform_generics.c | 52 +++++++ source/blender/makesdna/DNA_armature_types.h | 2 +- source/blender/makesrna/intern/rna_armature.c | 7 + 7 files changed, 224 insertions(+), 4 deletions(-) diff --git a/release/scripts/startup/bl_ui/space_view3d_toolbar.py b/release/scripts/startup/bl_ui/space_view3d_toolbar.py index 81804f6a399..d672984ca49 100644 --- a/release/scripts/startup/bl_ui/space_view3d_toolbar.py +++ b/release/scripts/startup/bl_ui/space_view3d_toolbar.py @@ -192,9 +192,13 @@ class VIEW3D_PT_tools_posemode_options(View3DPanel, Panel): def draw(self, context): arm = context.active_object.data + layout = self.layout - self.layout.prop(arm, "use_auto_ik") - self.layout.prop(arm, "use_mirror_x") + layout.prop(arm, "use_auto_ik") + layout.prop(arm, "use_mirror_x") + col = layout.column() + col.active = arm.use_mirror_x + col.prop(arm, "use_mirror_relative") # ********** default tools for paint modes **************** diff --git a/source/blender/blenloader/intern/versioning_280.c b/source/blender/blenloader/intern/versioning_280.c index d8d100db022..afbb512fe2f 100644 --- a/source/blender/blenloader/intern/versioning_280.c +++ b/source/blender/blenloader/intern/versioning_280.c @@ -3009,7 +3009,7 @@ void blo_do_versions_280(FileData *fd, Library *UNUSED(lib), Main *bmain) } LISTBASE_FOREACH (bArmature *, arm, &bmain->armatures) { - arm->flag &= ~(ARM_FLAG_UNUSED_1 | ARM_FLAG_UNUSED_5 | ARM_FLAG_UNUSED_7 | + arm->flag &= ~(ARM_FLAG_UNUSED_1 | ARM_FLAG_UNUSED_5 | ARM_MIRROR_RELATIVE | ARM_FLAG_UNUSED_12); } diff --git a/source/blender/editors/transform/transform.h b/source/blender/editors/transform/transform.h index 50fc1a276b9..268af8dff45 100644 --- a/source/blender/editors/transform/transform.h +++ b/source/blender/editors/transform/transform.h @@ -383,6 +383,30 @@ typedef struct BoneInitData { float zwidth; } BoneInitData; +typedef struct PoseInitData_Mirror { + /** Points to the bone which this info is initialized & restored to. + * A NULL value is used to terminate the array. */ + struct bPoseChannel *pchan; + struct { + float loc[3]; + float size[3]; + union { + float eul[3]; + float quat[4]; + float axis_angle[4]; + }; + float curve_in_x; + float curve_out_x; + float roll1; + float roll2; + } orig; + /** + * An extra offset to apply after mirroring. + * Use with #ARM_MIRROR_RELATIVE. + */ + float offset_mtx[4][4]; +} PoseInitData_Mirror; + typedef struct TransData { /** Distance needed to affect element (for Proportionnal Editing). */ float dist; @@ -892,6 +916,7 @@ void flushTransSeq(TransInfo *t); void flushTransTracking(TransInfo *t); void flushTransMasking(TransInfo *t); void flushTransPaintCurve(TransInfo *t); +void restoreMirrorPoseBones(TransDataContainer *tc); void restoreBones(TransDataContainer *tc); /*********************** transform_gizmo.c ********** */ diff --git a/source/blender/editors/transform/transform_conversions.c b/source/blender/editors/transform/transform_conversions.c index 6c1da5ae825..6964783d7c4 100644 --- a/source/blender/editors/transform/transform_conversions.c +++ b/source/blender/editors/transform/transform_conversions.c @@ -1201,6 +1201,74 @@ static short pose_grab_with_ik(Main *bmain, Object *ob) return (tot_ik) ? 1 : 0; } +static void pose_mirror_info_init(PoseInitData_Mirror *pid, + bPoseChannel *pchan, + bPoseChannel *pchan_orig, + bool is_mirror_relative) +{ + pid->pchan = pchan; + copy_v3_v3(pid->orig.loc, pchan->loc); + copy_v3_v3(pid->orig.size, pchan->size); + pid->orig.curve_in_x = pchan->curve_in_x; + pid->orig.curve_out_x = pchan->curve_out_x; + pid->orig.roll1 = pchan->roll1; + pid->orig.roll2 = pchan->roll2; + + if (pchan->rotmode > 0) { + copy_v3_v3(pid->orig.eul, pchan->eul); + } + else if (pchan->rotmode == ROT_MODE_AXISANGLE) { + copy_v3_v3(pid->orig.axis_angle, pchan->rotAxis); + pid->orig.axis_angle[3] = pchan->rotAngle; + } + else { + copy_qt_qt(pid->orig.quat, pchan->quat); + } + + if (is_mirror_relative) { + float pchan_mtx[4][4]; + float pchan_mtx_mirror[4][4]; + + float flip_mtx[4][4]; + unit_m4(flip_mtx); + flip_mtx[0][0] = -1; + + BKE_pchan_to_mat4(pchan_orig, pchan_mtx_mirror); + BKE_pchan_to_mat4(pchan, pchan_mtx); + + mul_m4_m4m4(pchan_mtx_mirror, pchan_mtx_mirror, flip_mtx); + mul_m4_m4m4(pchan_mtx_mirror, flip_mtx, pchan_mtx_mirror); + + invert_m4(pchan_mtx_mirror); + mul_m4_m4m4(pid->offset_mtx, pchan_mtx, pchan_mtx_mirror); + } + else { + unit_m4(pid->offset_mtx); + } +} + +static void pose_mirror_info_restore(const PoseInitData_Mirror *pid) +{ + bPoseChannel *pchan = pid->pchan; + copy_v3_v3(pchan->loc, pid->orig.loc); + copy_v3_v3(pchan->size, pid->orig.size); + pchan->curve_in_x = pid->orig.curve_in_x; + pchan->curve_out_x = pid->orig.curve_out_x; + pchan->roll1 = pid->orig.roll1; + pchan->roll2 = pid->orig.roll2; + + if (pchan->rotmode > 0) { + copy_v3_v3(pchan->eul, pid->orig.eul); + } + else if (pchan->rotmode == ROT_MODE_AXISANGLE) { + copy_v3_v3(pchan->rotAxis, pid->orig.axis_angle); + pchan->rotAngle = pid->orig.axis_angle[3]; + } + else { + copy_qt_qt(pchan->quat, pid->orig.quat); + } +} + /** * When objects array is NULL, use 't->data_container' as is. */ @@ -1225,6 +1293,8 @@ static void createTransPose(TransInfo *t) continue; } + const bool mirror = ((arm->flag & ARM_MIRROR_EDIT) != 0); + /* set flags and count total */ tc->data_len = count_set_pose_transflags(ob, t->mode, t->around, has_translate_rotate); if (tc->data_len == 0) { @@ -1247,6 +1317,25 @@ static void createTransPose(TransInfo *t) has_translate_rotate[0] = true; } } + + if (mirror) { + int total_mirrored = 0; + for (bPoseChannel *pchan = ob->pose->chanbase.first; pchan; pchan = pchan->next) { + if ((pchan->bone->flag & BONE_TRANSFORM) && + BKE_pose_channel_get_mirrored(ob->pose, pchan->name)) { + total_mirrored++; + } + } + + PoseInitData_Mirror *pid = MEM_mallocN((total_mirrored + 1) * sizeof(PoseInitData_Mirror), + "PoseInitData_Mirror"); + + /* Trick to terminate iteration. */ + pid[total_mirrored].pchan = NULL; + + tc->custom.type.data = pid; + tc->custom.type.use_free = true; + } } /* if there are no translatable bones, do rotation */ @@ -1269,6 +1358,19 @@ static void createTransPose(TransInfo *t) short ik_on = 0; int i; + PoseInitData_Mirror *pid = tc->custom.type.data; + int pid_index = 0; + bArmature *arm; + + /* check validity of state */ + arm = BKE_armature_from_object(tc->poseobj); + if ((arm == NULL) || (ob->pose == NULL)) { + continue; + } + + const bool mirror = ((arm->flag & ARM_MIRROR_EDIT) != 0); + const bool is_mirror_relative = ((arm->flag & ARM_MIRROR_RELATIVE) != 0); + tc->poseobj = ob; /* we also allow non-active objects to be transformed, in weightpaint */ /* init trans data */ @@ -1285,6 +1387,15 @@ static void createTransPose(TransInfo *t) for (bPoseChannel *pchan = ob->pose->chanbase.first; pchan; pchan = pchan->next) { if (pchan->bone->flag & BONE_TRANSFORM) { add_pose_transdata(t, pchan, ob, tc, td); + + if (mirror) { + bPoseChannel *pchan_mirror = BKE_pose_channel_get_mirrored(ob->pose, pchan->name); + if (pchan_mirror) { + pose_mirror_info_init(&pid[pid_index], pchan_mirror, pchan, is_mirror_relative); + pid_index++; + } + } + td++; } } @@ -1304,6 +1415,27 @@ static void createTransPose(TransInfo *t) t->flag &= ~T_PROP_EDIT_ALL; } +void restoreMirrorPoseBones(TransDataContainer *tc) +{ + bArmature *arm; + + if (tc->obedit) { + arm = tc->obedit->data; + } + else { + BLI_assert(tc->poseobj != NULL); + arm = tc->poseobj->data; + } + + if (!(arm->flag & ARM_MIRROR_EDIT)) { + return; + } + + for (PoseInitData_Mirror *pid = tc->custom.type.data; pid->pchan; pid++) { + pose_mirror_info_restore(pid); + } +} + void restoreBones(TransDataContainer *tc) { bArmature *arm; diff --git a/source/blender/editors/transform/transform_generics.c b/source/blender/editors/transform/transform_generics.c index a840c04ab5a..05bb9785112 100644 --- a/source/blender/editors/transform/transform_generics.c +++ b/source/blender/editors/transform/transform_generics.c @@ -781,6 +781,45 @@ static void recalcData_spaceclip(TransInfo *t) } } +/** + * if pose bone (partial) selected, copy data. + * context; posemode armature, with mirror editing enabled. + * + * \param pid: Optional, apply relative transform when set. + */ +static void pose_transform_mirror_update(Object *ob, PoseInitData_Mirror *pid) +{ + float flip_mtx[4][4]; + unit_m4(flip_mtx); + flip_mtx[0][0] = -1; + + for (bPoseChannel *pchan_orig = ob->pose->chanbase.first; pchan_orig; + pchan_orig = pchan_orig->next) { + /* no layer check, correct mirror is more important */ + if (pchan_orig->bone->flag & BONE_TRANSFORM) { + bPoseChannel *pchan = BKE_pose_channel_get_mirrored(ob->pose, pchan_orig->name); + + if (pchan) { + /* we assume X-axis flipping for now */ + pchan->curve_in_x = pchan_orig->curve_in_x * -1; + pchan->curve_out_x = pchan_orig->curve_out_x * -1; + pchan->roll1 = pchan_orig->roll1 * -1; // XXX? + pchan->roll2 = pchan_orig->roll2 * -1; // XXX? + + float pchan_mtx_final[4][4]; + BKE_pchan_to_mat4(pchan_orig, pchan_mtx_final); + mul_m4_m4m4(pchan_mtx_final, pchan_mtx_final, flip_mtx); + mul_m4_m4m4(pchan_mtx_final, flip_mtx, pchan_mtx_final); + if (pid) { + mul_m4_m4m4(pchan_mtx_final, pid->offset_mtx, pchan_mtx_final); + pid++; + } + BKE_pchan_apply_mat4(pchan, pchan_mtx_final, false); + } + } + } +} + /* helper for recalcData() - for object transforms, typically in the 3D view */ static void recalcData_objects(TransInfo *t) { @@ -992,6 +1031,19 @@ static void recalcData_objects(TransInfo *t) Object *ob = tc->poseobj; bArmature *arm = ob->data; + if (arm->flag & ARM_MIRROR_EDIT) { + if (t->state != TRANS_CANCEL) { + PoseInitData_Mirror *pid = NULL; + if (arm->flag & ARM_MIRROR_RELATIVE) { + pid = tc->custom.type.data; + } + pose_transform_mirror_update(ob, pid); + } + else { + restoreMirrorPoseBones(tc); + } + } + /* if animtimer is running, and the object already has animation data, * check if the auto-record feature means that we should record 'samples' * (i.e. un-editable animation values) diff --git a/source/blender/makesdna/DNA_armature_types.h b/source/blender/makesdna/DNA_armature_types.h index 483ff3483d3..fd40459503e 100644 --- a/source/blender/makesdna/DNA_armature_types.h +++ b/source/blender/makesdna/DNA_armature_types.h @@ -144,7 +144,7 @@ typedef enum eArmature_Flag { ARM_POSEMODE = (1 << 4), ARM_FLAG_UNUSED_5 = (1 << 5), /* cleared */ ARM_DELAYDEFORM = (1 << 6), - ARM_FLAG_UNUSED_7 = (1 << 7), /* cleared */ + ARM_MIRROR_RELATIVE = (1 << 7), ARM_MIRROR_EDIT = (1 << 8), ARM_AUTO_IK = (1 << 9), /** made option negative, for backwards compat */ diff --git a/source/blender/makesrna/intern/rna_armature.c b/source/blender/makesrna/intern/rna_armature.c index 5461aaa0f1a..fe197faecce 100644 --- a/source/blender/makesrna/intern/rna_armature.c +++ b/source/blender/makesrna/intern/rna_armature.c @@ -1359,6 +1359,13 @@ static void rna_def_armature(BlenderRNA *brna) RNA_def_property_update(prop, 0, "rna_Armature_redraw_data"); RNA_def_property_flag(prop, PROP_LIB_EXCEPTION); + prop = RNA_def_property(srna, "use_mirror_relative", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", ARM_MIRROR_RELATIVE); + RNA_def_property_ui_text( + prop, "Relative Mirror", "Apply relative transformations in X-mirror mode"); + RNA_def_property_update(prop, 0, "rna_Armature_redraw_data"); + + RNA_def_property_flag(prop, PROP_LIB_EXCEPTION); prop = RNA_def_property(srna, "use_auto_ik", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "flag", ARM_AUTO_IK); RNA_def_property_ui_text(