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.
This commit is contained in:
parent
c6ee43d384
commit
156921114e
@ -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):
|
||||
|
@ -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 */
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user