Implement mirroring in pose mode (absolute and relative)
authorSebastian Parborg <darkdefende@gmail.com>
Mon, 6 May 2019 07:47:45 +0000 (09:47 +0200)
committerSebastian Parborg <darkdefende@gmail.com>
Mon, 6 May 2019 07:47:45 +0000 (09:47 +0200)
Added working X-mirroring in pose mode with an optional relative mirror
mode.

Reviewed By: Campbell Barton

Differential Revision: http://developer.blender.org/D4765

release/scripts/startup/bl_ui/space_view3d_toolbar.py
source/blender/blenloader/intern/versioning_280.c
source/blender/editors/transform/transform.h
source/blender/editors/transform/transform_conversions.c
source/blender/editors/transform/transform_generics.c
source/blender/makesdna/DNA_armature_types.h
source/blender/makesrna/intern/rna_armature.c

index 81804f6..d672984 100644 (file)
@@ -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 ****************
 
index d8d100d..afbb512 100644 (file)
@@ -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);
     }
 
index 50fc1a2..268af8d 100644 (file)
@@ -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 ********** */
index 6c1da5a..6964783 100644 (file)
@@ -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;
index a840c04..05bb978 100644 (file)
@@ -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)
index 483ff34..fd40459 100644 (file)
@@ -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 */
index 5461aaa..fe197fa 100644 (file)
@@ -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(