From 156921114ec50760abfaadec3dbc02140f33ef5b Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Thu, 19 Mar 2015 17:57:17 +1100 Subject: Armature Symmetrize tool D1147 by @julien, with fixes/improvements Duplicate bones where needed, otherwise use existing. Keeps parent relations intact, can operate on parts of an armature. --- release/scripts/startup/bl_ui/space_view3d.py | 2 + source/blender/editors/armature/armature_add.c | 207 ++++++++++++++++++++++ source/blender/editors/armature/armature_intern.h | 1 + source/blender/editors/armature/armature_ops.c | 1 + 4 files changed, 211 insertions(+) diff --git a/release/scripts/startup/bl_ui/space_view3d.py b/release/scripts/startup/bl_ui/space_view3d.py index c65cebfde10..128e24a6d68 100644 --- a/release/scripts/startup/bl_ui/space_view3d.py +++ b/release/scripts/startup/bl_ui/space_view3d.py @@ -2710,6 +2710,7 @@ class VIEW3D_MT_edit_armature(Menu): layout.separator() layout.operator_context = 'EXEC_AREA' + layout.operator("armature.symmetrize") layout.operator("armature.autoside_names", text="AutoName Left/Right").type = 'XAXIS' layout.operator("armature.autoside_names", text="AutoName Front/Back").type = 'YAXIS' layout.operator("armature.autoside_names", text="AutoName Top/Bottom").type = 'ZAXIS' @@ -2748,6 +2749,7 @@ class VIEW3D_MT_armature_specials(Menu): layout.operator("armature.autoside_names", text="AutoName Front/Back").type = 'YAXIS' layout.operator("armature.autoside_names", text="AutoName Top/Bottom").type = 'ZAXIS' layout.operator("armature.flip_names", text="Flip Names") + layout.operator("armature.symmetrize") class VIEW3D_MT_edit_armature_parent(Menu): diff --git a/source/blender/editors/armature/armature_add.c b/source/blender/editors/armature/armature_add.c index a4520dbc0a4..5cc2101650e 100644 --- a/source/blender/editors/armature/armature_add.c +++ b/source/blender/editors/armature/armature_add.c @@ -43,6 +43,7 @@ #include "BKE_constraint.h" #include "BKE_context.h" #include "BKE_idprop.h" +#include "BKE_deform.h" #include "RNA_access.h" #include "RNA_define.h" @@ -516,6 +517,212 @@ void ARMATURE_OT_duplicate(wmOperatorType *ot) ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; } +/** + * near duplicate of #armature_duplicate_selected_exec, + * except for parenting part (keep in sync) + */ +static int armature_symmetrize_exec(bContext *C, wmOperator *op) +{ + bArmature *arm; + EditBone *ebone_iter; + EditBone *ebone_first_dupe = NULL; /* The beginning of the duplicated mirrored bones in the edbo list */ + + Object *obedit = CTX_data_edit_object(C); + const int direction = RNA_enum_get(op->ptr, "direction"); + const int axis = 0; + + arm = obedit->data; + + /* cancel if nothing selected */ + if (CTX_DATA_COUNT(C, selected_bones) == 0) + return OPERATOR_CANCELLED; + + ED_armature_sync_selection(arm->edbo); // XXX why is this needed? + + preEditBoneDuplicate(arm->edbo); + + /* Select mirrored bones */ + for (ebone_iter = arm->edbo->first; ebone_iter; ebone_iter = ebone_iter->next) { + if (EBONE_VISIBLE(arm, ebone_iter) && + (ebone_iter->flag & BONE_SELECTED)) + { + char name_flip[MAX_VGROUP_NAME]; + + BKE_deform_flip_side_name(name_flip, ebone_iter->name, false); + + if (STREQ(name_flip, ebone_iter->name)) { + /* if the name matches, we don't have the potential to be mirrored, just skip */ + ebone_iter->flag &= ~(BONE_SELECTED | BONE_TIPSEL | BONE_ROOTSEL); + } + else { + EditBone *ebone = ED_armature_bone_find_name(arm->edbo, name_flip); + + if (ebone) { + if ((ebone->flag & BONE_SELECTED) == 0) { + /* simple case, we're selected, the other bone isn't! */ + ebone_iter->temp = ebone; + } + else { + /* complicated - choose which direction to copy */ + float axis_delta; + + axis_delta = ebone->head[axis] - ebone_iter->head[axis]; + if (axis_delta == 0.0f) { + axis_delta = ebone->tail[axis] - ebone_iter->tail[axis]; + } + + if (axis_delta == 0.0f) { + /* both mirrored bones exist and point to eachother and overlap exactly. + * + * in this case theres no well defined solution, so de-select both and skip. + */ + ebone->flag &= ~(BONE_SELECTED | BONE_TIPSEL | BONE_ROOTSEL); + ebone_iter->flag &= ~(BONE_SELECTED | BONE_TIPSEL | BONE_ROOTSEL); + } + else { + EditBone *ebone_src, *ebone_dst; + if (((axis_delta < 0.0f) ? -1 : 1) == direction) { + ebone_src = ebone; + ebone_dst = ebone_iter; + } + else { + ebone_src = ebone_iter; + ebone_dst = ebone; + } + + ebone_src->temp = ebone_dst; + ebone_dst->flag &= ~(BONE_SELECTED | BONE_TIPSEL | BONE_ROOTSEL); + } + } + } + } + } + } + + /* Find the selected bones and duplicate them as needed, with mirrored name */ + for (ebone_iter = arm->edbo->first; ebone_iter && ebone_iter != ebone_first_dupe; ebone_iter = ebone_iter->next) { + if (EBONE_VISIBLE(arm, ebone_iter) && + (ebone_iter->flag & BONE_SELECTED) && + /* will be set if the mirror bone already exists (no need to make a new one) */ + (ebone_iter->temp == NULL)) + { + char name_flip[MAX_VGROUP_NAME]; + + BKE_deform_flip_side_name(name_flip, ebone_iter->name, false); + + /* bones must have a side-suffix */ + if (!STREQ(name_flip, ebone_iter->name)) { + EditBone *ebone; + + ebone = duplicateEditBone(ebone_iter, name_flip, arm->edbo, obedit); + + if (!ebone_first_dupe) { + ebone_first_dupe = ebone; + } + } + } + } + + /* Run though the list and fix the pointers */ + for (ebone_iter = arm->edbo->first; ebone_iter && ebone_iter != ebone_first_dupe; ebone_iter = ebone_iter->next) { + if (ebone_iter->temp) { + /* copy all flags except for ... */ + const int flag_copy = ((int)~0) & ~(BONE_SELECTED | BONE_ROOTSEL | BONE_TIPSEL); + + EditBone *ebone = ebone_iter->temp; + + /* copy flags incase bone is pre-existing data */ + ebone->flag = (ebone->flag & ~flag_copy) | (ebone_iter->flag & flag_copy); + + if (ebone_iter->parent == NULL) { + /* If this bone has no parent, + * Set the duplicate->parent to NULL + */ + ebone->parent = NULL; + } + else { + /* the parent may have been duplicated, if not lookup the mirror parent */ + EditBone *ebone_parent = + (ebone_iter->parent->temp ? + ebone_iter->parent->temp : ED_armature_bone_get_mirrored(arm->edbo, ebone_iter->parent)); + + if (ebone_parent == NULL) { + /* If the mirror lookup failed, (but the current bone has a parent) + * then we can assume the parent has no L/R but is a center bone. + * So just use the same parent for both. + */ + ebone_parent = ebone_iter->parent; + } + + ebone->parent = ebone_parent; + } + + /* Lets try to fix any constraint subtargets that might + * have been duplicated + */ + updateDuplicateSubtarget(ebone, arm->edbo, obedit); + } + } + + transform_armature_mirror_update(obedit); + + /* Selected bones now have their 'temp' pointer set, + * so we don't need this anymore */ + + /* Deselect the old bones and select the new ones */ + for (ebone_iter = arm->edbo->first; ebone_iter && ebone_iter != ebone_first_dupe; ebone_iter = ebone_iter->next) { + if (EBONE_VISIBLE(arm, ebone_iter)) { + ebone_iter->flag &= ~(BONE_SELECTED | BONE_TIPSEL | BONE_ROOTSEL); + } + } + + /* New bones will be selected, but some of the bones may already exist */ + for (ebone_iter = arm->edbo->first; ebone_iter && ebone_iter != ebone_first_dupe; ebone_iter = ebone_iter->next) { + EditBone *ebone = ebone_iter->temp; + if (ebone && EBONE_SELECTABLE(arm, ebone)) { + ED_armature_ebone_select_set(ebone, true); + } + } + + /* correct the active bone */ + if (arm->act_edbone && arm->act_edbone->temp) { + arm->act_edbone = arm->act_edbone->temp; + } + + ED_armature_validate_active(arm); + + WM_event_add_notifier(C, NC_OBJECT | ND_BONE_SELECT, obedit); + + return OPERATOR_FINISHED; +} + +/* following conventions from #MESH_OT_symmetrize */ +void ARMATURE_OT_symmetrize(wmOperatorType *ot) +{ + /* subset of 'symmetrize_direction_items' */ + static EnumPropertyItem arm_symmetrize_direction_items[] = { + {-1, "NEGATIVE_X", 0, "-X to +X", ""}, + {+1, "POSITIVE_X", 0, "+X to -X", ""}, + {0, NULL, 0, NULL, NULL} + }; + + /* identifiers */ + ot->name = "Symmetrize"; + ot->idname = "ARMATURE_OT_symmetrize"; + ot->description = "Enforce symmetry, make copies of the selection or use existing"; + + /* api callbacks */ + ot->exec = armature_symmetrize_exec; + ot->poll = ED_operator_editarmature; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + ot->prop = RNA_def_enum( + ot->srna, "direction", arm_symmetrize_direction_items, -1, + "Direction", "Which sides to copy from and to (when both are selected)"); +} + /* ------------------------------------------ */ /* previously extrude_armature */ diff --git a/source/blender/editors/armature/armature_intern.h b/source/blender/editors/armature/armature_intern.h index 2c64c9aa345..0607bc49515 100644 --- a/source/blender/editors/armature/armature_intern.h +++ b/source/blender/editors/armature/armature_intern.h @@ -72,6 +72,7 @@ void ARMATURE_OT_shortest_path_pick(struct wmOperatorType *ot); void ARMATURE_OT_delete(struct wmOperatorType *ot); void ARMATURE_OT_duplicate(struct wmOperatorType *ot); +void ARMATURE_OT_symmetrize(struct wmOperatorType *ot); void ARMATURE_OT_extrude(struct wmOperatorType *ot); void ARMATURE_OT_hide(struct wmOperatorType *ot); void ARMATURE_OT_reveal(struct wmOperatorType *ot); diff --git a/source/blender/editors/armature/armature_ops.c b/source/blender/editors/armature/armature_ops.c index c80953d6737..552faa4ecfb 100644 --- a/source/blender/editors/armature/armature_ops.c +++ b/source/blender/editors/armature/armature_ops.c @@ -66,6 +66,7 @@ void ED_operatortypes_armature(void) WM_operatortype_append(ARMATURE_OT_delete); WM_operatortype_append(ARMATURE_OT_duplicate); + WM_operatortype_append(ARMATURE_OT_symmetrize); WM_operatortype_append(ARMATURE_OT_extrude); WM_operatortype_append(ARMATURE_OT_hide); WM_operatortype_append(ARMATURE_OT_reveal); -- cgit v1.2.3