diff options
Diffstat (limited to 'source/blender/editors/armature/armature_relations.c')
-rw-r--r-- | source/blender/editors/armature/armature_relations.c | 774 |
1 files changed, 774 insertions, 0 deletions
diff --git a/source/blender/editors/armature/armature_relations.c b/source/blender/editors/armature/armature_relations.c new file mode 100644 index 00000000000..c64bb5c4683 --- /dev/null +++ b/source/blender/editors/armature/armature_relations.c @@ -0,0 +1,774 @@ +/* + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV. + * All rights reserved. + * + * Contributor(s): Blender Foundation, 2002-2009 full recode. + * + * ***** END GPL LICENSE BLOCK ***** + * + * Operators for relations between bones and for transferring bones between armature objects + */ + +/** \file blender/editors/armature/armature_relations.c + * \ingroup edarmature + */ + +#include "DNA_armature_types.h" +#include "DNA_constraint_types.h" +#include "DNA_object_types.h" +#include "DNA_scene_types.h" + +#include "BLI_blenlib.h" +#include "BLI_math.h" + +#include "BLF_translation.h" + +#include "BKE_action.h" +#include "BKE_armature.h" +#include "BKE_constraint.h" +#include "BKE_context.h" +#include "BKE_depsgraph.h" +#include "BKE_global.h" +#include "BKE_main.h" +#include "BKE_report.h" + +#include "RNA_access.h" +#include "RNA_define.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "ED_armature.h" +#include "ED_object.h" +#include "ED_screen.h" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "armature_intern.h" + +/* *************************************** Join *************************************** */ +/* NOTE: no operator define here as this is exported to the Object-level operator */ + +/* Helper function for armature joining - link fixing */ +static void joined_armature_fix_links(Object *tarArm, Object *srcArm, bPoseChannel *pchan, EditBone *curbone) +{ + Object *ob; + bPose *pose; + bPoseChannel *pchant; + bConstraint *con; + + /* let's go through all objects in database */ + for (ob = G.main->object.first; ob; ob = ob->id.next) { + /* do some object-type specific things */ + if (ob->type == OB_ARMATURE) { + pose = ob->pose; + for (pchant = pose->chanbase.first; pchant; pchant = pchant->next) { + for (con = pchant->constraints.first; con; con = con->next) { + bConstraintTypeInfo *cti = BKE_constraint_get_typeinfo(con); + ListBase targets = {NULL, NULL}; + bConstraintTarget *ct; + + /* constraint targets */ + if (cti && cti->get_constraint_targets) { + cti->get_constraint_targets(con, &targets); + + for (ct = targets.first; ct; ct = ct->next) { + if (ct->tar == srcArm) { + if (ct->subtarget[0] == '\0') { + ct->tar = tarArm; + } + else if (strcmp(ct->subtarget, pchan->name) == 0) { + ct->tar = tarArm; + BLI_strncpy(ct->subtarget, curbone->name, sizeof(ct->subtarget)); + } + } + } + + if (cti->flush_constraint_targets) + cti->flush_constraint_targets(con, &targets, 0); + } + + /* action constraint? */ + if (con->type == CONSTRAINT_TYPE_ACTION) { + bActionConstraint *data = con->data; // XXX old animation system + bAction *act; + bActionChannel *achan; + + if (data->act) { + act = data->act; + + for (achan = act->chanbase.first; achan; achan = achan->next) { + if (strcmp(achan->name, pchan->name) == 0) + BLI_strncpy(achan->name, curbone->name, sizeof(achan->name)); + } + } + } + + } + } + } + + /* fix object-level constraints */ + if (ob != srcArm) { + for (con = ob->constraints.first; con; con = con->next) { + bConstraintTypeInfo *cti = BKE_constraint_get_typeinfo(con); + ListBase targets = {NULL, NULL}; + bConstraintTarget *ct; + + /* constraint targets */ + if (cti && cti->get_constraint_targets) { + cti->get_constraint_targets(con, &targets); + + for (ct = targets.first; ct; ct = ct->next) { + if (ct->tar == srcArm) { + if (ct->subtarget[0] == '\0') { + ct->tar = tarArm; + } + else if (strcmp(ct->subtarget, pchan->name) == 0) { + ct->tar = tarArm; + BLI_strncpy(ct->subtarget, curbone->name, sizeof(ct->subtarget)); + } + } + } + + if (cti->flush_constraint_targets) + cti->flush_constraint_targets(con, &targets, 0); + } + } + } + + /* See if an object is parented to this armature */ + if (ob->parent && (ob->parent == srcArm)) { + /* Is object parented to a bone of this src armature? */ + if (ob->partype == PARBONE) { + /* bone name in object */ + if (!strcmp(ob->parsubstr, pchan->name)) + BLI_strncpy(ob->parsubstr, curbone->name, sizeof(ob->parsubstr)); + } + + /* make tar armature be new parent */ + ob->parent = tarArm; + } + } +} + +/* join armature exec is exported for use in object->join objects operator... */ +int join_armature_exec(bContext *C, wmOperator *UNUSED(op)) +{ + Main *bmain = CTX_data_main(C); + Scene *scene = CTX_data_scene(C); + Object *ob = CTX_data_active_object(C); + bArmature *arm = (ob) ? ob->data : NULL; + bPose *pose, *opose; + bPoseChannel *pchan, *pchann; + EditBone *curbone; + float mat[4][4], oimat[4][4]; + + /* Ensure we're not in editmode and that the active object is an armature*/ + if (!ob || ob->type != OB_ARMATURE) + return OPERATOR_CANCELLED; + if (!arm || arm->edbo) + return OPERATOR_CANCELLED; + + /* Get editbones of active armature to add editbones to */ + ED_armature_to_edit(ob); + + /* get pose of active object and move it out of posemode */ + pose = ob->pose; + ob->mode &= ~OB_MODE_POSE; + + CTX_DATA_BEGIN(C, Base *, base, selected_editable_bases) + { + if ((base->object->type == OB_ARMATURE) && (base->object != ob)) { + bArmature *curarm = base->object->data; + + /* Make a list of editbones in current armature */ + ED_armature_to_edit(base->object); + + /* Get Pose of current armature */ + opose = base->object->pose; + base->object->mode &= ~OB_MODE_POSE; + //BASACT->flag &= ~OB_MODE_POSE; + + /* Find the difference matrix */ + invert_m4_m4(oimat, ob->obmat); + mult_m4_m4m4(mat, oimat, base->object->obmat); + + /* Copy bones and posechannels from the object to the edit armature */ + for (pchan = opose->chanbase.first; pchan; pchan = pchann) { + pchann = pchan->next; + curbone = editbone_name_exists(curarm->edbo, pchan->name); + + /* Get new name */ + unique_editbone_name(arm->edbo, curbone->name, NULL); + + /* Transform the bone */ + { + float premat[4][4]; + float postmat[4][4]; + float difmat[4][4]; + float imat[4][4]; + float temp[3][3]; + float delta[3]; + + /* Get the premat */ + sub_v3_v3v3(delta, curbone->tail, curbone->head); + vec_roll_to_mat3(delta, curbone->roll, temp); + + unit_m4(premat); /* Mat4MulMat34 only sets 3x3 part */ + mul_m4_m3m4(premat, temp, mat); + + mul_m4_v3(mat, curbone->head); + mul_m4_v3(mat, curbone->tail); + + /* Get the postmat */ + sub_v3_v3v3(delta, curbone->tail, curbone->head); + vec_roll_to_mat3(delta, curbone->roll, temp); + copy_m4_m3(postmat, temp); + + /* Find the roll */ + invert_m4_m4(imat, premat); + mult_m4_m4m4(difmat, imat, postmat); + + curbone->roll -= (float)atan2(difmat[2][0], difmat[2][2]); + } + + /* Fix Constraints and Other Links to this Bone and Armature */ + joined_armature_fix_links(ob, base->object, pchan, curbone); + + /* Rename pchan */ + BLI_strncpy(pchan->name, curbone->name, sizeof(pchan->name)); + + /* Jump Ship! */ + BLI_remlink(curarm->edbo, curbone); + BLI_addtail(arm->edbo, curbone); + + BLI_remlink(&opose->chanbase, pchan); + BLI_addtail(&pose->chanbase, pchan); + BKE_pose_channels_hash_free(opose); + BKE_pose_channels_hash_free(pose); + } + + ED_base_object_free_and_unlink(bmain, scene, base); + } + } + CTX_DATA_END; + + DAG_relations_tag_update(bmain); /* because we removed object(s) */ + + ED_armature_from_edit(ob); + ED_armature_edit_free(ob); + + WM_event_add_notifier(C, NC_SCENE | ND_OB_ACTIVE, scene); + + return OPERATOR_FINISHED; +} + +/* *********************************** Separate *********************************************** */ + +/* Helper function for armature separating - link fixing */ +static void separated_armature_fix_links(Object *origArm, Object *newArm) +{ + Object *ob; + bPoseChannel *pchan; + bConstraint *con; + ListBase *opchans, *npchans; + + /* get reference to list of bones in original and new armatures */ + opchans = &origArm->pose->chanbase; + npchans = &newArm->pose->chanbase; + + /* let's go through all objects in database */ + for (ob = G.main->object.first; ob; ob = ob->id.next) { + /* do some object-type specific things */ + if (ob->type == OB_ARMATURE) { + for (pchan = ob->pose->chanbase.first; pchan; pchan = pchan->next) { + for (con = pchan->constraints.first; con; con = con->next) { + bConstraintTypeInfo *cti = BKE_constraint_get_typeinfo(con); + ListBase targets = {NULL, NULL}; + bConstraintTarget *ct; + + /* constraint targets */ + if (cti && cti->get_constraint_targets) { + cti->get_constraint_targets(con, &targets); + + for (ct = targets.first; ct; ct = ct->next) { + /* any targets which point to original armature are redirected to the new one only if: + * - the target isn't origArm/newArm itself + * - the target is one that can be found in newArm/origArm + */ + if (ct->subtarget[0] != 0) { + if (ct->tar == origArm) { + if (BLI_findstring(npchans, ct->subtarget, offsetof(bPoseChannel, name))) { + ct->tar = newArm; + } + } + else if (ct->tar == newArm) { + if (BLI_findstring(opchans, ct->subtarget, offsetof(bPoseChannel, name))) { + ct->tar = origArm; + } + } + } + } + + if (cti->flush_constraint_targets) { + cti->flush_constraint_targets(con, &targets, 0); + } + } + } + } + } + + /* fix object-level constraints */ + if (ob != origArm) { + for (con = ob->constraints.first; con; con = con->next) { + bConstraintTypeInfo *cti = BKE_constraint_get_typeinfo(con); + ListBase targets = {NULL, NULL}; + bConstraintTarget *ct; + + /* constraint targets */ + if (cti && cti->get_constraint_targets) { + cti->get_constraint_targets(con, &targets); + + for (ct = targets.first; ct; ct = ct->next) { + /* any targets which point to original armature are redirected to the new one only if: + * - the target isn't origArm/newArm itself + * - the target is one that can be found in newArm/origArm + */ + if (ct->subtarget[0] != '\0') { + if (ct->tar == origArm) { + if (BLI_findstring(npchans, ct->subtarget, offsetof(bPoseChannel, name))) { + ct->tar = newArm; + } + } + else if (ct->tar == newArm) { + if (BLI_findstring(opchans, ct->subtarget, offsetof(bPoseChannel, name))) { + ct->tar = origArm; + } + } + } + } + + if (cti->flush_constraint_targets) { + cti->flush_constraint_targets(con, &targets, 0); + } + } + } + } + + /* See if an object is parented to this armature */ + if (ob->parent && (ob->parent == origArm)) { + /* Is object parented to a bone of this src armature? */ + if ((ob->partype == PARBONE) && (ob->parsubstr[0] != '\0')) { + if (BLI_findstring(npchans, ob->parsubstr, offsetof(bPoseChannel, name))) { + ob->parent = newArm; + } + } + } + } +} + +/* Helper function for armature separating - remove certain bones from the given armature + * sel: remove selected bones from the armature, otherwise the unselected bones are removed + * (ob is not in editmode) + */ +static void separate_armature_bones(Object *ob, short sel) +{ + bArmature *arm = (bArmature *)ob->data; + bPoseChannel *pchan, *pchann; + EditBone *curbone; + + /* make local set of editbones to manipulate here */ + ED_armature_to_edit(ob); + + /* go through pose-channels, checking if a bone should be removed */ + for (pchan = ob->pose->chanbase.first; pchan; pchan = pchann) { + pchann = pchan->next; + curbone = editbone_name_exists(arm->edbo, pchan->name); + + /* check if bone needs to be removed */ + if ( (sel && (curbone->flag & BONE_SELECTED)) || + (!sel && !(curbone->flag & BONE_SELECTED)) ) + { + EditBone *ebo; + bPoseChannel *pchn; + + /* clear the bone->parent var of any bone that had this as its parent */ + for (ebo = arm->edbo->first; ebo; ebo = ebo->next) { + if (ebo->parent == curbone) { + ebo->parent = NULL; + ebo->temp = NULL; /* this is needed to prevent random crashes with in ED_armature_from_edit */ + ebo->flag &= ~BONE_CONNECTED; + } + } + + /* clear the pchan->parent var of any pchan that had this as its parent */ + for (pchn = ob->pose->chanbase.first; pchn; pchn = pchn->next) { + if (pchn->parent == pchan) + pchn->parent = NULL; + } + + /* free any of the extra-data this pchan might have */ + BKE_pose_channel_free(pchan); + BKE_pose_channels_hash_free(ob->pose); + + /* get rid of unneeded bone */ + bone_free(arm, curbone); + BLI_freelinkN(&ob->pose->chanbase, pchan); + } + } + + /* exit editmode (recalculates pchans too) */ + ED_armature_from_edit(ob); + ED_armature_edit_free(ob); +} + +/* separate selected bones into their armature */ +static int separate_armature_exec(bContext *C, wmOperator *UNUSED(op)) +{ + Main *bmain = CTX_data_main(C); + Scene *scene = CTX_data_scene(C); + Object *obedit = CTX_data_edit_object(C); + Object *oldob, *newob; + Base *oldbase, *newbase; + + /* sanity checks */ + if (obedit == NULL) + return OPERATOR_CANCELLED; + + /* set wait cursor in case this takes a while */ + WM_cursor_wait(1); + + /* we are going to do this as follows (unlike every other instance of separate): + * 1. exit editmode +posemode for active armature/base. Take note of what this is. + * 2. duplicate base - BASACT is the new one now + * 3. for each of the two armatures, enter editmode -> remove appropriate bones -> exit editmode + recalc + * 4. fix constraint links + * 5. make original armature active and enter editmode + */ + + /* 1) only edit-base selected */ + /* TODO: use context iterators for this? */ + CTX_DATA_BEGIN(C, Base *, base, visible_bases) + { + if (base->object == obedit) base->flag |= 1; + else base->flag &= ~1; + } + CTX_DATA_END; + + /* 1) store starting settings and exit editmode */ + oldob = obedit; + oldbase = BASACT; + oldob->mode &= ~OB_MODE_POSE; + //oldbase->flag &= ~OB_POSEMODE; + + ED_armature_from_edit(obedit); + ED_armature_edit_free(obedit); + + /* 2) duplicate base */ + newbase = ED_object_add_duplicate(bmain, scene, oldbase, USER_DUP_ARM); /* only duplicate linked armature */ + DAG_relations_tag_update(bmain); + + newob = newbase->object; + newbase->flag &= ~SELECT; + + + /* 3) remove bones that shouldn't still be around on both armatures */ + separate_armature_bones(oldob, 1); + separate_armature_bones(newob, 0); + + + /* 4) fix links before depsgraph flushes */ // err... or after? + separated_armature_fix_links(oldob, newob); + + DAG_id_tag_update(&oldob->id, OB_RECALC_DATA); /* this is the original one */ + DAG_id_tag_update(&newob->id, OB_RECALC_DATA); /* this is the separated one */ + + + /* 5) restore original conditions */ + obedit = oldob; + + ED_armature_to_edit(obedit); + + /* note, notifier might evolve */ + WM_event_add_notifier(C, NC_OBJECT | ND_POSE, obedit); + + /* recalc/redraw + cleanup */ + WM_cursor_wait(0); + + return OPERATOR_FINISHED; +} + +void ARMATURE_OT_separate(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Separate Bones"; + ot->idname = "ARMATURE_OT_separate"; + ot->description = "Isolate selected bones into a separate armature"; + + /* callbacks */ + ot->invoke = WM_operator_confirm; + ot->exec = separate_armature_exec; + ot->poll = ED_operator_editarmature; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + +/* ******************************************** Parenting ************************************************* */ + +/* armature parenting options */ +#define ARM_PAR_CONNECT 1 +#define ARM_PAR_OFFSET 2 + + +/* check for null, before calling! */ +static void bone_connect_to_existing_parent(EditBone *bone) +{ + bone->flag |= BONE_CONNECTED; + copy_v3_v3(bone->head, bone->parent->tail); + bone->rad_head = bone->parent->rad_tail; +} + +static void bone_connect_to_new_parent(ListBase *edbo, EditBone *selbone, EditBone *actbone, short mode) +{ + EditBone *ebone; + float offset[3]; + + if ((selbone->parent) && (selbone->flag & BONE_CONNECTED)) + selbone->parent->flag &= ~(BONE_TIPSEL); + + /* make actbone the parent of selbone */ + selbone->parent = actbone; + + /* in actbone tree we cannot have a loop */ + for (ebone = actbone->parent; ebone; ebone = ebone->parent) { + if (ebone->parent == selbone) { + ebone->parent = NULL; + ebone->flag &= ~BONE_CONNECTED; + } + } + + if (mode == ARM_PAR_CONNECT) { + /* Connected: Child bones will be moved to the parent tip */ + selbone->flag |= BONE_CONNECTED; + sub_v3_v3v3(offset, actbone->tail, selbone->head); + + copy_v3_v3(selbone->head, actbone->tail); + selbone->rad_head = actbone->rad_tail; + + add_v3_v3(selbone->tail, offset); + + /* offset for all its children */ + for (ebone = edbo->first; ebone; ebone = ebone->next) { + EditBone *par; + + for (par = ebone->parent; par; par = par->parent) { + if (par == selbone) { + add_v3_v3(ebone->head, offset); + add_v3_v3(ebone->tail, offset); + break; + } + } + } + } + else { + /* Offset: Child bones will retain their distance from the parent tip */ + selbone->flag &= ~BONE_CONNECTED; + } +} + + +static EnumPropertyItem prop_editarm_make_parent_types[] = { + {ARM_PAR_CONNECT, "CONNECTED", 0, "Connected", ""}, + {ARM_PAR_OFFSET, "OFFSET", 0, "Keep Offset", ""}, + {0, NULL, 0, NULL, NULL} +}; + +static int armature_parent_set_exec(bContext *C, wmOperator *op) +{ + Object *ob = CTX_data_edit_object(C); + bArmature *arm = (bArmature *)ob->data; + EditBone *actbone = CTX_data_active_bone(C); + EditBone *actmirb = NULL; + short val = RNA_enum_get(op->ptr, "type"); + + /* there must be an active bone */ + if (actbone == NULL) { + BKE_report(op->reports, RPT_ERROR, "Operation requires an active bone"); + return OPERATOR_CANCELLED; + } + else if (arm->flag & ARM_MIRROR_EDIT) { + /* For X-Axis Mirror Editing option, we may need a mirror copy of actbone + * - if there's a mirrored copy of selbone, try to find a mirrored copy of actbone + * (i.e. selbone="child.L" and actbone="parent.L", find "child.R" and "parent.R"). + * This is useful for arm-chains, for example parenting lower arm to upper arm + * - if there's no mirrored copy of actbone (i.e. actbone = "parent.C" or "parent") + * then just use actbone. Useful when doing upper arm to spine. + */ + actmirb = ED_armature_bone_get_mirrored(arm->edbo, actbone); + if (actmirb == NULL) + actmirb = actbone; + } + + /* if there is only 1 selected bone, we assume that that is the active bone, + * since a user will need to have clicked on a bone (thus selecting it) to make it active + */ + if (CTX_DATA_COUNT(C, selected_editable_bones) <= 1) { + /* When only the active bone is selected, and it has a parent, + * connect it to the parent, as that is the only possible outcome. + */ + if (actbone->parent) { + bone_connect_to_existing_parent(actbone); + + if ((arm->flag & ARM_MIRROR_EDIT) && (actmirb->parent)) + bone_connect_to_existing_parent(actmirb); + } + } + else { + /* Parent 'selected' bones to the active one + * - the context iterator contains both selected bones and their mirrored copies, + * so we assume that unselected bones are mirrored copies of some selected bone + * - since the active one (and/or its mirror) will also be selected, we also need + * to check that we are not trying to operate on them, since such an operation + * would cause errors + */ + + /* parent selected bones to the active one */ + CTX_DATA_BEGIN(C, EditBone *, ebone, selected_editable_bones) + { + if (ELEM(ebone, actbone, actmirb) == 0) { + if (ebone->flag & BONE_SELECTED) + bone_connect_to_new_parent(arm->edbo, ebone, actbone, val); + else + bone_connect_to_new_parent(arm->edbo, ebone, actmirb, val); + } + } + CTX_DATA_END; + } + + + /* note, notifier might evolve */ + WM_event_add_notifier(C, NC_OBJECT | ND_BONE_SELECT, ob); + + return OPERATOR_FINISHED; +} + +static int armature_parent_set_invoke(bContext *C, wmOperator *UNUSED(op), wmEvent *UNUSED(event)) +{ + EditBone *actbone = CTX_data_active_bone(C); + uiPopupMenu *pup = uiPupMenuBegin(C, CTX_IFACE_(BLF_I18NCONTEXT_OPERATOR_DEFAULT, "Make Parent"), ICON_NONE); + uiLayout *layout = uiPupMenuLayout(pup); + int allchildbones = 0; + + CTX_DATA_BEGIN(C, EditBone *, ebone, selected_editable_bones) + { + if (ebone != actbone) { + if (ebone->parent != actbone) allchildbones = 1; + } + } + CTX_DATA_END; + + uiItemEnumO(layout, "ARMATURE_OT_parent_set", NULL, 0, "type", ARM_PAR_CONNECT); + + /* ob becomes parent, make the associated menus */ + if (allchildbones) + uiItemEnumO(layout, "ARMATURE_OT_parent_set", NULL, 0, "type", ARM_PAR_OFFSET); + + uiPupMenuEnd(C, pup); + + return OPERATOR_CANCELLED; +} + +void ARMATURE_OT_parent_set(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Make Parent"; + ot->idname = "ARMATURE_OT_parent_set"; + ot->description = "Set the active bone as the parent of the selected bones"; + + /* api callbacks */ + ot->invoke = armature_parent_set_invoke; + ot->exec = armature_parent_set_exec; + ot->poll = ED_operator_editarmature; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + RNA_def_enum(ot->srna, "type", prop_editarm_make_parent_types, 0, "ParentType", "Type of parenting"); +} + + + +static EnumPropertyItem prop_editarm_clear_parent_types[] = { + {1, "CLEAR", 0, "Clear Parent", ""}, + {2, "DISCONNECT", 0, "Disconnect Bone", ""}, + {0, NULL, 0, NULL, NULL} +}; + +static void editbone_clear_parent(EditBone *ebone, int mode) +{ + if (ebone->parent) { + /* for nice selection */ + ebone->parent->flag &= ~(BONE_TIPSEL); + } + + if (mode == 1) ebone->parent = NULL; + ebone->flag &= ~BONE_CONNECTED; +} + +static int armature_parent_clear_exec(bContext *C, wmOperator *op) +{ + Object *ob = CTX_data_edit_object(C); + bArmature *arm = (bArmature *)ob->data; + int val = RNA_enum_get(op->ptr, "type"); + + CTX_DATA_BEGIN(C, EditBone *, ebone, selected_editable_bones) + { + editbone_clear_parent(ebone, val); + } + CTX_DATA_END; + + ED_armature_sync_selection(arm->edbo); + + /* note, notifier might evolve */ + WM_event_add_notifier(C, NC_OBJECT | ND_BONE_SELECT, ob); + + return OPERATOR_FINISHED; +} + +void ARMATURE_OT_parent_clear(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Clear Parent"; + ot->idname = "ARMATURE_OT_parent_clear"; + ot->description = "Remove the parent-child relationship between selected bones and their parents"; + + /* api callbacks */ + ot->invoke = WM_menu_invoke; + ot->exec = armature_parent_clear_exec; + ot->poll = ED_operator_editarmature; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + ot->prop = RNA_def_enum(ot->srna, "type", prop_editarm_clear_parent_types, 0, "ClearType", "What way to clear parenting"); +} + |