/* * 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. * Armature EditMode tools - transforms, chain based editing, and other settings */ /** \file * \ingroup edarmature */ #include "DNA_armature_types.h" #include "DNA_constraint_types.h" #include "DNA_object_types.h" #include "DNA_scene_types.h" #include "MEM_guardedalloc.h" #include "BLT_translation.h" #include "BLI_blenlib.h" #include "BLI_ghash.h" #include "BLI_math.h" #include "BKE_action.h" #include "BKE_armature.h" #include "BKE_constraint.h" #include "BKE_context.h" #include "BKE_global.h" #include "BKE_layer.h" #include "BKE_main.h" #include "BKE_object.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_outliner.h" #include "ED_screen.h" #include "ED_view3d.h" #include "DEG_depsgraph.h" #include "armature_intern.h" /* -------------------------------------------------------------------- */ /** \name Object Tools Public API * \{ */ /* NOTE: these functions are exported to the Object module to be called from the tools there */ /** * See #BKE_armature_transform for object-mode transform. */ void ED_armature_edit_transform(bArmature *arm, const float mat[4][4], const bool do_props) { EditBone *ebone; float scale = mat4_to_scale(mat); /* store the scale of the matrix here to use on envelopes */ float mat3[3][3]; copy_m3_m4(mat3, mat); normalize_m3(mat3); /* Do the rotations */ for (ebone = arm->edbo->first; ebone; ebone = ebone->next) { float tmat[3][3]; /* find the current bone's roll matrix */ ED_armature_ebone_to_mat3(ebone, tmat); /* transform the roll matrix */ mul_m3_m3m3(tmat, mat3, tmat); /* transform the bone */ mul_m4_v3(mat, ebone->head); mul_m4_v3(mat, ebone->tail); /* apply the transformed roll back */ mat3_to_vec_roll(tmat, NULL, &ebone->roll); if (do_props) { ebone->rad_head *= scale; ebone->rad_tail *= scale; ebone->dist *= scale; /* we could be smarter and scale by the matrix along the x & z axis */ ebone->xwidth *= scale; ebone->zwidth *= scale; } } } void ED_armature_transform(bArmature *arm, const float mat[4][4], const bool do_props) { if (arm->edbo) { ED_armature_edit_transform(arm, mat, do_props); } else { BKE_armature_transform(arm, mat, do_props); } } /* exported for use in editors/object/ */ /* 0 == do center, 1 == center new, 2 == center cursor */ void ED_armature_origin_set( Main *bmain, Object *ob, const float cursor[3], int centermode, int around) { const bool is_editmode = BKE_object_is_in_editmode(ob); EditBone *ebone; bArmature *arm = ob->data; float cent[3]; /* Put the armature into editmode */ if (is_editmode == false) { ED_armature_to_edit(arm); } /* Find the centerpoint */ if (centermode == 2) { copy_v3_v3(cent, cursor); invert_m4_m4(ob->imat, ob->obmat); mul_m4_v3(ob->imat, cent); } else { if (around == V3D_AROUND_CENTER_BOUNDS) { float min[3], max[3]; INIT_MINMAX(min, max); for (ebone = arm->edbo->first; ebone; ebone = ebone->next) { minmax_v3v3_v3(min, max, ebone->head); minmax_v3v3_v3(min, max, ebone->tail); } mid_v3_v3v3(cent, min, max); } else { /* #V3D_AROUND_CENTER_MEDIAN. */ int total = 0; zero_v3(cent); for (ebone = arm->edbo->first; ebone; ebone = ebone->next) { total += 2; add_v3_v3(cent, ebone->head); add_v3_v3(cent, ebone->tail); } if (total) { mul_v3_fl(cent, 1.0f / (float)total); } } } /* Do the adjustments */ for (ebone = arm->edbo->first; ebone; ebone = ebone->next) { sub_v3_v3(ebone->head, cent); sub_v3_v3(ebone->tail, cent); } /* Turn the list into an armature */ if (is_editmode == false) { ED_armature_from_edit(bmain, arm); ED_armature_edit_free(arm); } /* Adjust object location for new centerpoint */ if (centermode && (is_editmode == false)) { mul_mat3_m4_v3(ob->obmat, cent); /* omit translation part */ add_v3_v3(ob->loc, cent); } } /** \} */ /* -------------------------------------------------------------------- */ /** \name Bone Roll Calculate Operator * \{ */ /* adjust bone roll to align Z axis with vector * vec is in local space and is normalized */ float ED_armature_ebone_roll_to_vector(const EditBone *bone, const float align_axis[3], const bool axis_only) { float mat[3][3], nor[3]; float vec[3], align_axis_proj[3], roll = 0.0f; BLI_ASSERT_UNIT_V3(align_axis); sub_v3_v3v3(nor, bone->tail, bone->head); /* If tail == head or the bone is aligned with the axis... */ if (normalize_v3(nor) <= FLT_EPSILON || (fabsf(dot_v3v3(align_axis, nor)) >= (1.0f - FLT_EPSILON))) { return roll; } vec_roll_to_mat3_normalized(nor, 0.0f, mat); /* project the new_up_axis along the normal */ project_v3_v3v3_normalized(vec, align_axis, nor); sub_v3_v3v3(align_axis_proj, align_axis, vec); if (axis_only) { if (angle_v3v3(align_axis_proj, mat[2]) > (float)(M_PI_2)) { negate_v3(align_axis_proj); } } roll = angle_v3v3(align_axis_proj, mat[2]); cross_v3_v3v3(vec, mat[2], align_axis_proj); if (dot_v3v3(vec, nor) < 0.0f) { return -roll; } return roll; } /* note, ranges arithmetic is used below */ typedef enum eCalcRollTypes { /* pos */ CALC_ROLL_POS_X = 0, CALC_ROLL_POS_Y, CALC_ROLL_POS_Z, CALC_ROLL_TAN_POS_X, CALC_ROLL_TAN_POS_Z, /* neg */ CALC_ROLL_NEG_X, CALC_ROLL_NEG_Y, CALC_ROLL_NEG_Z, CALC_ROLL_TAN_NEG_X, CALC_ROLL_TAN_NEG_Z, /* no sign */ CALC_ROLL_ACTIVE, CALC_ROLL_VIEW, CALC_ROLL_CURSOR, } eCalcRollTypes; static const EnumPropertyItem prop_calc_roll_types[] = { {0, "", 0, N_("Positive"), ""}, {CALC_ROLL_TAN_POS_X, "POS_X", 0, "Local +X Tangent", ""}, {CALC_ROLL_TAN_POS_Z, "POS_Z", 0, "Local +Z Tangent", ""}, {CALC_ROLL_POS_X, "GLOBAL_POS_X", 0, "Global +X Axis", ""}, {CALC_ROLL_POS_Y, "GLOBAL_POS_Y", 0, "Global +Y Axis", ""}, {CALC_ROLL_POS_Z, "GLOBAL_POS_Z", 0, "Global +Z Axis", ""}, {0, "", 0, N_("Negative"), ""}, {CALC_ROLL_TAN_NEG_X, "NEG_X", 0, "Local -X Tangent", ""}, {CALC_ROLL_TAN_NEG_Z, "NEG_Z", 0, "Local -Z Tangent", ""}, {CALC_ROLL_NEG_X, "GLOBAL_NEG_X", 0, "Global -X Axis", ""}, {CALC_ROLL_NEG_Y, "GLOBAL_NEG_Y", 0, "Global -Y Axis", ""}, {CALC_ROLL_NEG_Z, "GLOBAL_NEG_Z", 0, "Global -Z Axis", ""}, {0, "", 0, N_("Other"), ""}, {CALC_ROLL_ACTIVE, "ACTIVE", 0, "Active Bone", ""}, {CALC_ROLL_VIEW, "VIEW", 0, "View Axis", ""}, {CALC_ROLL_CURSOR, "CURSOR", 0, "Cursor", ""}, {0, NULL, 0, NULL, NULL}, }; static int armature_calc_roll_exec(bContext *C, wmOperator *op) { ViewLayer *view_layer = CTX_data_view_layer(C); Object *ob_active = CTX_data_edit_object(C); int ret = OPERATOR_FINISHED; eCalcRollTypes type = RNA_enum_get(op->ptr, "type"); const bool axis_only = RNA_boolean_get(op->ptr, "axis_only"); /* axis_flip when matching the active bone never makes sense */ bool axis_flip = ((type >= CALC_ROLL_ACTIVE) ? RNA_boolean_get(op->ptr, "axis_flip") : (type >= CALC_ROLL_TAN_NEG_X) ? true : false); uint objects_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *ob = objects[ob_index]; bArmature *arm = ob->data; bool changed = false; float imat[3][3]; EditBone *ebone; if ((type >= CALC_ROLL_NEG_X) && (type <= CALC_ROLL_TAN_NEG_Z)) { type -= (CALC_ROLL_ACTIVE - CALC_ROLL_NEG_X); axis_flip = true; } copy_m3_m4(imat, ob->obmat); invert_m3(imat); if (type == CALC_ROLL_CURSOR) { /* Cursor */ Scene *scene = CTX_data_scene(C); float cursor_local[3]; const View3DCursor *cursor = &scene->cursor; invert_m4_m4(ob->imat, ob->obmat); copy_v3_v3(cursor_local, cursor->location); mul_m4_v3(ob->imat, cursor_local); /* cursor */ for (ebone = arm->edbo->first; ebone; ebone = ebone->next) { if (EBONE_VISIBLE(arm, ebone) && EBONE_EDITABLE(ebone)) { float cursor_rel[3]; sub_v3_v3v3(cursor_rel, cursor_local, ebone->head); if (axis_flip) { negate_v3(cursor_rel); } if (normalize_v3(cursor_rel) != 0.0f) { ebone->roll = ED_armature_ebone_roll_to_vector(ebone, cursor_rel, axis_only); changed = true; } } } } else if (ELEM(type, CALC_ROLL_TAN_POS_X, CALC_ROLL_TAN_POS_Z)) { for (ebone = arm->edbo->first; ebone; ebone = ebone->next) { if (ebone->parent) { bool is_edit = (EBONE_VISIBLE(arm, ebone) && EBONE_EDITABLE(ebone)); bool is_edit_parent = (EBONE_VISIBLE(arm, ebone->parent) && EBONE_EDITABLE(ebone->parent)); if (is_edit || is_edit_parent) { EditBone *ebone_other = ebone->parent; float dir_a[3]; float dir_b[3]; float vec[3]; bool is_vec_zero; sub_v3_v3v3(dir_a, ebone->tail, ebone->head); normalize_v3(dir_a); /* find the first bone in the chain with a different direction */ do { sub_v3_v3v3(dir_b, ebone_other->head, ebone_other->tail); normalize_v3(dir_b); if (type == CALC_ROLL_TAN_POS_Z) { cross_v3_v3v3(vec, dir_a, dir_b); } else { add_v3_v3v3(vec, dir_a, dir_b); } } while ((is_vec_zero = (normalize_v3(vec) < 0.00001f)) && (ebone_other = ebone_other->parent)); if (!is_vec_zero) { if (axis_flip) { negate_v3(vec); } if (is_edit) { ebone->roll = ED_armature_ebone_roll_to_vector(ebone, vec, axis_only); changed = true; } /* parentless bones use cross product with child */ if (is_edit_parent) { if (ebone->parent->parent == NULL) { ebone->parent->roll = ED_armature_ebone_roll_to_vector( ebone->parent, vec, axis_only); changed = true; } } } } } } } else { float vec[3] = {0.0f, 0.0f, 0.0f}; if (type == CALC_ROLL_VIEW) { /* View */ RegionView3D *rv3d = CTX_wm_region_view3d(C); if (rv3d == NULL) { BKE_report(op->reports, RPT_ERROR, "No region view3d available"); ret = OPERATOR_CANCELLED; goto cleanup; } copy_v3_v3(vec, rv3d->viewinv[2]); mul_m3_v3(imat, vec); } else if (type == CALC_ROLL_ACTIVE) { float mat[3][3]; bArmature *arm_active = ob_active->data; ebone = (EditBone *)arm_active->act_edbone; if (ebone == NULL) { BKE_report(op->reports, RPT_ERROR, "No active bone set"); ret = OPERATOR_CANCELLED; goto cleanup; } ED_armature_ebone_to_mat3(ebone, mat); copy_v3_v3(vec, mat[2]); } else { /* Axis */ BLI_assert(type <= 5); if (type < 3) { vec[type] = 1.0f; } else { vec[type - 2] = -1.0f; } mul_m3_v3(imat, vec); normalize_v3(vec); } if (axis_flip) { negate_v3(vec); } for (ebone = arm->edbo->first; ebone; ebone = ebone->next) { if (EBONE_VISIBLE(arm, ebone) && EBONE_EDITABLE(ebone)) { /* roll func is a callback which assumes that all is well */ ebone->roll = ED_armature_ebone_roll_to_vector(ebone, vec, axis_only); changed = true; } } } if (arm->flag & ARM_MIRROR_EDIT) { for (ebone = arm->edbo->first; ebone; ebone = ebone->next) { if ((EBONE_VISIBLE(arm, ebone) && EBONE_EDITABLE(ebone)) == 0) { EditBone *ebone_mirr = ED_armature_ebone_get_mirrored(arm->edbo, ebone); if (ebone_mirr && (EBONE_VISIBLE(arm, ebone_mirr) && EBONE_EDITABLE(ebone_mirr))) { ebone->roll = -ebone_mirr->roll; } } } } if (changed) { /* note, notifier might evolve */ WM_event_add_notifier(C, NC_OBJECT | ND_BONE_SELECT, ob); DEG_id_tag_update(&arm->id, ID_RECALC_SELECT); } } cleanup: MEM_freeN(objects); return ret; } void ARMATURE_OT_calculate_roll(wmOperatorType *ot) { /* identifiers */ ot->name = "Recalculate Roll"; ot->idname = "ARMATURE_OT_calculate_roll"; ot->description = "Automatically fix alignment of select bones' axes"; /* api callbacks */ ot->invoke = WM_menu_invoke; ot->exec = armature_calc_roll_exec; ot->poll = ED_operator_editarmature; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; /* properties */ ot->prop = RNA_def_enum(ot->srna, "type", prop_calc_roll_types, CALC_ROLL_TAN_POS_X, "Type", ""); RNA_def_boolean(ot->srna, "axis_flip", 0, "Flip Axis", "Negate the alignment axis"); RNA_def_boolean(ot->srna, "axis_only", 0, "Shortest Rotation", "Ignore the axis direction, use the shortest rotation to align"); } static int armature_roll_clear_exec(bContext *C, wmOperator *op) { ViewLayer *view_layer = CTX_data_view_layer(C); const float roll = RNA_float_get(op->ptr, "roll"); uint objects_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *ob = objects[ob_index]; bArmature *arm = ob->data; bool changed = false; LISTBASE_FOREACH (EditBone *, ebone, arm->edbo) { if (EBONE_VISIBLE(arm, ebone) && EBONE_EDITABLE(ebone)) { /* Roll func is a callback which assumes that all is well. */ ebone->roll = roll; changed = true; } } if (arm->flag & ARM_MIRROR_EDIT) { LISTBASE_FOREACH (EditBone *, ebone, arm->edbo) { if ((EBONE_VISIBLE(arm, ebone) && EBONE_EDITABLE(ebone)) == 0) { EditBone *ebone_mirr = ED_armature_ebone_get_mirrored(arm->edbo, ebone); if (ebone_mirr && (EBONE_VISIBLE(arm, ebone_mirr) && EBONE_EDITABLE(ebone_mirr))) { ebone->roll = -ebone_mirr->roll; changed = true; } } } } if (changed) { /* Note, notifier might evolve. */ WM_event_add_notifier(C, NC_OBJECT | ND_BONE_SELECT, ob); DEG_id_tag_update(&arm->id, ID_RECALC_SELECT); } } MEM_freeN(objects); return OPERATOR_FINISHED; } void ARMATURE_OT_roll_clear(wmOperatorType *ot) { /* identifiers */ ot->name = "Clear Roll"; ot->idname = "ARMATURE_OT_roll_clear"; ot->description = "Clear roll for selected bones"; /* api callbacks */ ot->exec = armature_roll_clear_exec; ot->poll = ED_operator_editarmature; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; RNA_def_float_rotation(ot->srna, "roll", 0, NULL, DEG2RADF(-360.0f), DEG2RADF(360.0f), "Roll", "", DEG2RADF(-360.0f), DEG2RADF(360.0f)); } /** \} */ /* -------------------------------------------------------------------- */ /** \name Chain-Based Tool Utilities * \{ */ /* temporary data-structure for merge/fill bones */ typedef struct EditBonePoint { struct EditBonePoint *next, *prev; EditBone *head_owner; /* EditBone which uses this point as a 'head' point */ EditBone *tail_owner; /* EditBone which uses this point as a 'tail' point */ float vec[3]; /* the actual location of the point in local/EditMode space */ } EditBonePoint; /* find chain-tips (i.e. bones without children) */ static void chains_find_tips(ListBase *edbo, ListBase *list) { EditBone *curBone, *ebo; LinkData *ld; /* note: this is potentially very slow ... there's got to be a better way */ for (curBone = edbo->first; curBone; curBone = curBone->next) { short stop = 0; /* is this bone contained within any existing chain? (skip if so) */ for (ld = list->first; ld; ld = ld->next) { for (ebo = ld->data; ebo; ebo = ebo->parent) { if (ebo == curBone) { stop = 1; break; } } if (stop) { break; } } /* skip current bone if it is part of an existing chain */ if (stop) { continue; } /* is any existing chain part of the chain formed by this bone? */ stop = 0; for (ebo = curBone->parent; ebo; ebo = ebo->parent) { for (ld = list->first; ld; ld = ld->next) { if (ld->data == ebo) { ld->data = curBone; stop = 1; break; } } if (stop) { break; } } /* current bone has already been added to a chain? */ if (stop) { continue; } /* add current bone to a new chain */ ld = MEM_callocN(sizeof(LinkData), "BoneChain"); ld->data = curBone; BLI_addtail(list, ld); } } /** \} */ /* -------------------------------------------------------------------- */ /** \name Fill Operator * \{ */ static void fill_add_joint(EditBone *ebo, short eb_tail, ListBase *points) { EditBonePoint *ebp; float vec[3]; short found = 0; if (eb_tail) { copy_v3_v3(vec, ebo->tail); } else { copy_v3_v3(vec, ebo->head); } for (ebp = points->first; ebp; ebp = ebp->next) { if (equals_v3v3(ebp->vec, vec)) { if (eb_tail) { if ((ebp->head_owner) && (ebp->head_owner->parent == ebo)) { /* so this bone's tail owner is this bone */ ebp->tail_owner = ebo; found = 1; break; } } else { if ((ebp->tail_owner) && (ebo->parent == ebp->tail_owner)) { /* so this bone's head owner is this bone */ ebp->head_owner = ebo; found = 1; break; } } } } /* allocate a new point if no existing point was related */ if (found == 0) { ebp = MEM_callocN(sizeof(EditBonePoint), "EditBonePoint"); if (eb_tail) { copy_v3_v3(ebp->vec, ebo->tail); ebp->tail_owner = ebo; } else { copy_v3_v3(ebp->vec, ebo->head); ebp->head_owner = ebo; } BLI_addtail(points, ebp); } } /* bone adding between selected joints */ static int armature_fill_bones_exec(bContext *C, wmOperator *op) { Scene *scene = CTX_data_scene(C); View3D *v3d = CTX_wm_view3d(C); ListBase points = {NULL, NULL}; EditBone *newbone = NULL; int count; bool mixed_object_error = false; /* loop over all bones, and only consider if visible */ bArmature *arm = NULL; CTX_DATA_BEGIN_WITH_ID (C, EditBone *, ebone, visible_bones, bArmature *, arm_iter) { bool check = false; if (!(ebone->flag & BONE_CONNECTED) && (ebone->flag & BONE_ROOTSEL)) { fill_add_joint(ebone, 0, &points); check = true; } if (ebone->flag & BONE_TIPSEL) { fill_add_joint(ebone, 1, &points); check = true; } if (check) { if (arm && (arm != arm_iter)) { mixed_object_error = true; } arm = arm_iter; } } CTX_DATA_END; /* the number of joints determines how we fill: * 1) between joint and cursor (joint=head, cursor=tail) * 2) between the two joints (order is dependent on active-bone/hierarchy) * 3+) error (a smarter method involving finding chains needs to be worked out */ count = BLI_listbase_count(&points); if (count == 0) { BKE_report(op->reports, RPT_ERROR, "No joints selected"); return OPERATOR_CANCELLED; } if (mixed_object_error) { BKE_report(op->reports, RPT_ERROR, "Bones for different objects selected"); BLI_freelistN(&points); return OPERATOR_CANCELLED; } Object *obedit = NULL; { ViewLayer *view_layer = CTX_data_view_layer(C); FOREACH_OBJECT_IN_EDIT_MODE_BEGIN (view_layer, v3d, ob_iter) { if (ob_iter->data == arm) { obedit = ob_iter; } } FOREACH_OBJECT_IN_MODE_END; } BLI_assert(obedit != NULL); if (count == 1) { EditBonePoint *ebp; float curs[3]; /* Get Points - selected joint */ ebp = points.first; /* Get points - cursor (tail) */ invert_m4_m4(obedit->imat, obedit->obmat); mul_v3_m4v3(curs, obedit->imat, scene->cursor.location); /* Create a bone */ newbone = add_points_bone(obedit, ebp->vec, curs); } else if (count == 2) { EditBonePoint *ebp_a, *ebp_b; float head[3], tail[3]; short headtail = 0; /* check that the points don't belong to the same bone */ ebp_a = (EditBonePoint *)points.first; ebp_b = ebp_a->next; if (((ebp_a->head_owner == ebp_b->tail_owner) && (ebp_a->head_owner != NULL)) || ((ebp_a->tail_owner == ebp_b->head_owner) && (ebp_a->tail_owner != NULL))) { BKE_report(op->reports, RPT_ERROR, "Same bone selected..."); BLI_freelistN(&points); return OPERATOR_CANCELLED; } /* find which one should be the 'head' */ if ((ebp_a->head_owner && ebp_b->head_owner) || (ebp_a->tail_owner && ebp_b->tail_owner)) { /* use active, nice predictable */ if (arm->act_edbone && ELEM(arm->act_edbone, ebp_a->head_owner, ebp_a->tail_owner)) { headtail = 1; } else if (arm->act_edbone && ELEM(arm->act_edbone, ebp_b->head_owner, ebp_b->tail_owner)) { headtail = 2; } else { /* rule: whichever one is closer to 3d-cursor */ float curs[3]; float dist_sq_a, dist_sq_b; /* get cursor location */ invert_m4_m4(obedit->imat, obedit->obmat); mul_v3_m4v3(curs, obedit->imat, scene->cursor.location); /* get distances */ dist_sq_a = len_squared_v3v3(ebp_a->vec, curs); dist_sq_b = len_squared_v3v3(ebp_b->vec, curs); /* compare distances - closer one therefore acts as direction for bone to go */ headtail = (dist_sq_a < dist_sq_b) ? 2 : 1; } } else if (ebp_a->head_owner) { headtail = 1; } else if (ebp_b->head_owner) { headtail = 2; } /* assign head/tail combinations */ if (headtail == 2) { copy_v3_v3(head, ebp_a->vec); copy_v3_v3(tail, ebp_b->vec); } else if (headtail == 1) { copy_v3_v3(head, ebp_b->vec); copy_v3_v3(tail, ebp_a->vec); } /* add new bone and parent it to the appropriate end */ if (headtail) { newbone = add_points_bone(obedit, head, tail); /* do parenting (will need to set connected flag too) */ if (headtail == 2) { /* ebp tail or head - tail gets priority */ if (ebp_a->tail_owner) { newbone->parent = ebp_a->tail_owner; } else { newbone->parent = ebp_a->head_owner; } } else { /* ebp_b tail or head - tail gets priority */ if (ebp_b->tail_owner) { newbone->parent = ebp_b->tail_owner; } else { newbone->parent = ebp_b->head_owner; } } /* don't set for bone connecting two head points of bones */ if (ebp_a->tail_owner || ebp_b->tail_owner) { newbone->flag |= BONE_CONNECTED; } } } else { BKE_reportf(op->reports, RPT_ERROR, "Too many points selected: %d", count); BLI_freelistN(&points); return OPERATOR_CANCELLED; } if (newbone) { ED_armature_edit_deselect_all(obedit); arm->act_edbone = newbone; newbone->flag |= BONE_TIPSEL; } /* updates */ ED_armature_edit_refresh_layer_used(arm); WM_event_add_notifier(C, NC_OBJECT | ND_POSE, obedit); DEG_id_tag_update(&arm->id, ID_RECALC_COPY_ON_WRITE); /* free points */ BLI_freelistN(&points); return OPERATOR_FINISHED; } void ARMATURE_OT_fill(wmOperatorType *ot) { /* identifiers */ ot->name = "Fill Between Joints"; ot->idname = "ARMATURE_OT_fill"; ot->description = "Add bone between selected joint(s) and/or 3D-Cursor"; /* callbacks */ ot->exec = armature_fill_bones_exec; ot->poll = ED_operator_editarmature; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; } /** \} */ /* -------------------------------------------------------------------- */ /** \name Switch Direction Operator * * Currently, this does not use context loops, as context loops do not make it * easy to retrieve any hierarchical/chain relationships which are necessary for * this to be done easily. * \{ */ /* helper to clear BONE_TRANSFORM flags */ static void armature_clear_swap_done_flags(bArmature *arm) { EditBone *ebone; for (ebone = arm->edbo->first; ebone; ebone = ebone->next) { ebone->flag &= ~BONE_TRANSFORM; } } static int armature_switch_direction_exec(bContext *C, wmOperator *UNUSED(op)) { ViewLayer *view_layer = CTX_data_view_layer(C); uint objects_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *ob = objects[ob_index]; bArmature *arm = ob->data; ListBase chains = {NULL, NULL}; LinkData *chain; /* get chains of bones (ends on chains) */ chains_find_tips(arm->edbo, &chains); if (BLI_listbase_is_empty(&chains)) { continue; } /* ensure that mirror bones will also be operated on */ armature_tag_select_mirrored(arm); /* Clear BONE_TRANSFORM flags * - Used to prevent duplicate/canceling operations from occurring T34123. * - #BONE_DONE cannot be used here as that's already used for mirroring. */ armature_clear_swap_done_flags(arm); /* loop over chains, only considering selected and visible bones */ for (chain = chains.first; chain; chain = chain->next) { EditBone *ebo, *child = NULL, *parent = NULL; /* loop over bones in chain */ for (ebo = chain->data; ebo; ebo = parent) { /* parent is this bone's original parent * - we store this, as the next bone that is checked is this one * but the value of ebo->parent may change here... */ parent = ebo->parent; /* skip bone if already handled, see T34123. */ if ((ebo->flag & BONE_TRANSFORM) == 0) { /* only if selected and editable */ if (EBONE_VISIBLE(arm, ebo) && EBONE_EDITABLE(ebo)) { /* swap head and tail coordinates */ swap_v3_v3(ebo->head, ebo->tail); /* do parent swapping: * - use 'child' as new parent * - connected flag is only set if points are coincidental */ ebo->parent = child; if ((child) && equals_v3v3(ebo->head, child->tail)) { ebo->flag |= BONE_CONNECTED; } else { ebo->flag &= ~BONE_CONNECTED; } /* get next bones * - child will become the new parent of next bone */ child = ebo; } else { /* not swapping this bone, however, if its 'parent' got swapped, unparent us from it * as it will be facing in opposite direction */ if ((parent) && (EBONE_VISIBLE(arm, parent) && EBONE_EDITABLE(parent))) { ebo->parent = NULL; ebo->flag &= ~BONE_CONNECTED; } /* get next bones * - child will become new parent of next bone (not swapping occurred, * so set to NULL to prevent infinite-loop) */ child = NULL; } /* tag as done (to prevent double-swaps) */ ebo->flag |= BONE_TRANSFORM; } } } /* free chains */ BLI_freelistN(&chains); /* clear temp flags */ armature_clear_swap_done_flags(arm); armature_tag_unselect(arm); /* note, notifier might evolve */ WM_event_add_notifier(C, NC_OBJECT | ND_BONE_SELECT, ob); DEG_id_tag_update(&arm->id, ID_RECALC_SELECT); } MEM_freeN(objects); return OPERATOR_FINISHED; } void ARMATURE_OT_switch_direction(wmOperatorType *ot) { /* identifiers */ ot->name = "Switch Direction"; ot->idname = "ARMATURE_OT_switch_direction"; ot->description = "Change the direction that a chain of bones points in (head and tail swap)"; /* api callbacks */ ot->exec = armature_switch_direction_exec; ot->poll = ED_operator_editarmature; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; } /** \} */ /* -------------------------------------------------------------------- */ /** \name Align Operator * \{ */ /* helper to fix a ebone position if its parent has moved due to alignment*/ static void fix_connected_bone(EditBone *ebone) { float diff[3]; if (!(ebone->parent) || !(ebone->flag & BONE_CONNECTED) || equals_v3v3(ebone->parent->tail, ebone->head)) { return; } /* if the parent has moved we translate child's head and tail accordingly */ sub_v3_v3v3(diff, ebone->parent->tail, ebone->head); add_v3_v3(ebone->head, diff); add_v3_v3(ebone->tail, diff); } /* helper to recursively find chains of connected bones starting at ebone and fix their position */ static void fix_editbone_connected_children(ListBase *edbo, EditBone *ebone) { EditBone *selbone; for (selbone = edbo->first; selbone; selbone = selbone->next) { if ((selbone->parent) && (selbone->parent == ebone) && (selbone->flag & BONE_CONNECTED)) { fix_connected_bone(selbone); fix_editbone_connected_children(edbo, selbone); } } } static void bone_align_to_bone(ListBase *edbo, EditBone *selbone, EditBone *actbone) { float selboneaxis[3], actboneaxis[3], length; sub_v3_v3v3(actboneaxis, actbone->tail, actbone->head); normalize_v3(actboneaxis); sub_v3_v3v3(selboneaxis, selbone->tail, selbone->head); length = len_v3(selboneaxis); mul_v3_fl(actboneaxis, length); add_v3_v3v3(selbone->tail, selbone->head, actboneaxis); selbone->roll = actbone->roll; /* if the bone being aligned has connected descendants they must be moved * according to their parent new position, otherwise they would be left * in an inconsistent state: connected but away from the parent*/ fix_editbone_connected_children(edbo, selbone); } static int armature_align_bones_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; int num_selected_bones; /* there must be an active bone */ if (actbone == NULL) { BKE_report(op->reports, RPT_ERROR, "Operation requires an active bone"); return OPERATOR_CANCELLED; } 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_ebone_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 */ num_selected_bones = CTX_DATA_COUNT(C, selected_editable_bones); if (num_selected_bones <= 1) { /* When only the active bone is selected, and it has a parent, * align it to the parent, as that is the only possible outcome. */ if (actbone->parent) { bone_align_to_bone(arm->edbo, actbone, actbone->parent); if ((arm->flag & ARM_MIRROR_EDIT) && (actmirb->parent)) { bone_align_to_bone(arm->edbo, actmirb, actmirb->parent); } BKE_reportf(op->reports, RPT_INFO, "Aligned bone '%s' to parent", actbone->name); } } else { /* Align '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 */ /* align 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_align_to_bone(arm->edbo, ebone, actbone); } else { bone_align_to_bone(arm->edbo, ebone, actmirb); } } } CTX_DATA_END; BKE_reportf( op->reports, RPT_INFO, "%d bones aligned to bone '%s'", num_selected_bones, actbone->name); } /* note, notifier might evolve */ WM_event_add_notifier(C, NC_OBJECT | ND_BONE_SELECT, ob); DEG_id_tag_update(&arm->id, ID_RECALC_SELECT); return OPERATOR_FINISHED; } void ARMATURE_OT_align(wmOperatorType *ot) { /* identifiers */ ot->name = "Align Bones"; ot->idname = "ARMATURE_OT_align"; ot->description = "Align selected bones to the active bone (or to their parent)"; /* api callbacks */ ot->exec = armature_align_bones_exec; ot->poll = ED_operator_editarmature; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; } /** \} */ /* -------------------------------------------------------------------- */ /** \name Split Operator * \{ */ static int armature_split_exec(bContext *C, wmOperator *UNUSED(op)) { ViewLayer *view_layer = CTX_data_view_layer(C); uint objects_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *ob = objects[ob_index]; bArmature *arm = ob->data; LISTBASE_FOREACH (EditBone *, bone, arm->edbo) { if (bone->parent && (bone->flag & BONE_SELECTED) != (bone->parent->flag & BONE_SELECTED)) { bone->parent = NULL; bone->flag &= ~BONE_CONNECTED; } } LISTBASE_FOREACH (EditBone *, bone, arm->edbo) { ED_armature_ebone_select_set(bone, (bone->flag & BONE_SELECTED) != 0); } WM_event_add_notifier(C, NC_OBJECT | ND_BONE_SELECT, ob); DEG_id_tag_update(&arm->id, ID_RECALC_SELECT); } MEM_freeN(objects); return OPERATOR_FINISHED; } void ARMATURE_OT_split(wmOperatorType *ot) { /* identifiers */ ot->name = "Split"; ot->idname = "ARMATURE_OT_split"; ot->description = "Split off selected bones from connected unselected bones"; /* api callbacks */ ot->exec = armature_split_exec; ot->poll = ED_operator_editarmature; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; } /** \} */ /* -------------------------------------------------------------------- */ /** \name Delete Operator * \{ */ static bool armature_delete_ebone_cb(const char *bone_name, void *arm_p) { bArmature *arm = arm_p; EditBone *ebone; ebone = ED_armature_ebone_find_name(arm->edbo, bone_name); return (ebone && (ebone->flag & BONE_SELECTED) && (arm->layer & ebone->layer)); } /* previously delete_armature */ /* only editmode! */ static int armature_delete_selected_exec(bContext *C, wmOperator *UNUSED(op)) { EditBone *curBone, *ebone_next; bool changed_multi = false; /* cancel if nothing selected */ if (CTX_DATA_COUNT(C, selected_bones) == 0) { return OPERATOR_CANCELLED; } ViewLayer *view_layer = CTX_data_view_layer(C); uint objects_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; bArmature *arm = obedit->data; bool changed = false; armature_select_mirrored(arm); BKE_pose_channels_remove(obedit, armature_delete_ebone_cb, arm); for (curBone = arm->edbo->first; curBone; curBone = ebone_next) { ebone_next = curBone->next; if (arm->layer & curBone->layer) { if (curBone->flag & BONE_SELECTED) { if (curBone == arm->act_edbone) { arm->act_edbone = NULL; } ED_armature_ebone_remove(arm, curBone); changed = true; } } } if (changed) { changed_multi = true; ED_armature_edit_sync_selection(arm->edbo); ED_armature_edit_refresh_layer_used(arm); BKE_pose_tag_recalc(CTX_data_main(C), obedit->pose); WM_event_add_notifier(C, NC_OBJECT | ND_BONE_SELECT, obedit); DEG_id_tag_update(&arm->id, ID_RECALC_SELECT); ED_outliner_select_sync_from_edit_bone_tag(C); } } MEM_freeN(objects); if (!changed_multi) { return OPERATOR_CANCELLED; } return OPERATOR_FINISHED; } void ARMATURE_OT_delete(wmOperatorType *ot) { /* identifiers */ ot->name = "Delete Selected Bone(s)"; ot->idname = "ARMATURE_OT_delete"; ot->description = "Remove selected bones from the armature"; /* api callbacks */ ot->invoke = WM_operator_confirm; ot->exec = armature_delete_selected_exec; ot->poll = ED_operator_editarmature; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; } static bool armature_dissolve_ebone_cb(const char *bone_name, void *arm_p) { bArmature *arm = arm_p; EditBone *ebone; ebone = ED_armature_ebone_find_name(arm->edbo, bone_name); return (ebone && (ebone->flag & BONE_DONE)); } static int armature_dissolve_selected_exec(bContext *C, wmOperator *UNUSED(op)) { ViewLayer *view_layer = CTX_data_view_layer(C); EditBone *ebone, *ebone_next; bool changed_multi = false; uint objects_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; bArmature *arm = obedit->data; bool changed = false; /* store for mirror */ GHash *ebone_flag_orig = NULL; int ebone_num = 0; for (ebone = arm->edbo->first; ebone; ebone = ebone->next) { ebone->temp.p = NULL; ebone->flag &= ~BONE_DONE; ebone_num++; } if (arm->flag & ARM_MIRROR_EDIT) { GHashIterator gh_iter; ebone_flag_orig = BLI_ghash_ptr_new_ex(__func__, ebone_num); for (ebone = arm->edbo->first; ebone; ebone = ebone->next) { union { int flag; void *p; } val = {0}; val.flag = ebone->flag; BLI_ghash_insert(ebone_flag_orig, ebone, val.p); } armature_select_mirrored_ex(arm, BONE_SELECTED | BONE_ROOTSEL | BONE_TIPSEL); GHASH_ITER (gh_iter, ebone_flag_orig) { union { int flag; void *p; } *val_p = (void *)BLI_ghashIterator_getValue_p(&gh_iter); ebone = BLI_ghashIterator_getKey(&gh_iter); val_p->flag = ebone->flag & ~val_p->flag; } } for (ebone = arm->edbo->first; ebone; ebone = ebone->next) { if (ebone->parent && ebone->flag & BONE_CONNECTED) { if (ebone->parent->temp.ebone == ebone->parent) { /* ignore */ } else if (ebone->parent->temp.ebone) { /* set ignored */ ebone->parent->temp.ebone = ebone->parent; } else { /* set child */ ebone->parent->temp.ebone = ebone; } } } /* cleanup multiple used bones */ for (ebone = arm->edbo->first; ebone; ebone = ebone->next) { if (ebone->temp.ebone == ebone) { ebone->temp.ebone = NULL; } } for (ebone = arm->edbo->first; ebone; ebone = ebone->next) { /* break connections for unseen bones */ if (((arm->layer & ebone->layer) && ((ED_armature_ebone_selectflag_get(ebone) & (BONE_TIPSEL | BONE_SELECTED)))) == 0) { ebone->temp.ebone = NULL; } if (((arm->layer & ebone->layer) && ((ED_armature_ebone_selectflag_get(ebone) & (BONE_ROOTSEL | BONE_SELECTED)))) == 0) { if (ebone->parent && (ebone->flag & BONE_CONNECTED)) { ebone->parent->temp.ebone = NULL; } } } for (ebone = arm->edbo->first; ebone; ebone = ebone->next) { if (ebone->parent && (ebone->parent->temp.ebone == ebone)) { ebone->flag |= BONE_DONE; } } BKE_pose_channels_remove(obedit, armature_dissolve_ebone_cb, arm); for (ebone = arm->edbo->first; ebone; ebone = ebone_next) { ebone_next = ebone->next; if (ebone->flag & BONE_DONE) { copy_v3_v3(ebone->parent->tail, ebone->tail); ebone->parent->rad_tail = ebone->rad_tail; SET_FLAG_FROM_TEST(ebone->parent->flag, ebone->flag & BONE_TIPSEL, BONE_TIPSEL); ED_armature_ebone_remove_ex(arm, ebone, false); changed = true; } } if (changed) { for (ebone = arm->edbo->first; ebone; ebone = ebone->next) { if (ebone->parent && ebone->parent->temp.ebone && (ebone->flag & BONE_CONNECTED)) { ebone->rad_head = ebone->parent->rad_tail; } } if (arm->flag & ARM_MIRROR_EDIT) { for (ebone = arm->edbo->first; ebone; ebone = ebone->next) { union { int flag; void *p; } *val_p = (void *)BLI_ghash_lookup_p(ebone_flag_orig, ebone); if (val_p && val_p->flag) { ebone->flag &= ~val_p->flag; } } } } if (arm->flag & ARM_MIRROR_EDIT) { BLI_ghash_free(ebone_flag_orig, NULL, NULL); } if (changed) { changed_multi = true; ED_armature_edit_sync_selection(arm->edbo); ED_armature_edit_refresh_layer_used(arm); WM_event_add_notifier(C, NC_OBJECT | ND_BONE_SELECT, obedit); DEG_id_tag_update(&arm->id, ID_RECALC_SELECT); ED_outliner_select_sync_from_edit_bone_tag(C); } } MEM_freeN(objects); if (!changed_multi) { return OPERATOR_CANCELLED; } return OPERATOR_FINISHED; } void ARMATURE_OT_dissolve(wmOperatorType *ot) { /* identifiers */ ot->name = "Dissolve Selected Bone(s)"; ot->idname = "ARMATURE_OT_dissolve"; ot->description = "Dissolve selected bones from the armature"; /* api callbacks */ ot->exec = armature_dissolve_selected_exec; ot->poll = ED_operator_editarmature; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; } /** \} */ /* -------------------------------------------------------------------- */ /** \name Hide Operator * \{ */ static int armature_hide_exec(bContext *C, wmOperator *op) { ViewLayer *view_layer = CTX_data_view_layer(C); const int invert = RNA_boolean_get(op->ptr, "unselected") ? BONE_SELECTED : 0; /* cancel if nothing selected */ if (CTX_DATA_COUNT(C, selected_bones) == 0) { return OPERATOR_CANCELLED; } uint objects_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; bArmature *arm = obedit->data; bool changed = false; LISTBASE_FOREACH (EditBone *, ebone, arm->edbo) { if (EBONE_VISIBLE(arm, ebone)) { if ((ebone->flag & BONE_SELECTED) != invert) { ebone->flag &= ~(BONE_TIPSEL | BONE_SELECTED | BONE_ROOTSEL); ebone->flag |= BONE_HIDDEN_A; changed = true; } } } if (!changed) { continue; } ED_armature_edit_validate_active(arm); ED_armature_edit_sync_selection(arm->edbo); WM_event_add_notifier(C, NC_OBJECT | ND_BONE_SELECT, obedit); DEG_id_tag_update(&arm->id, ID_RECALC_SELECT); } MEM_freeN(objects); return OPERATOR_FINISHED; } void ARMATURE_OT_hide(wmOperatorType *ot) { /* identifiers */ ot->name = "Hide Selected"; ot->idname = "ARMATURE_OT_hide"; ot->description = "Tag selected bones to not be visible in Edit Mode"; /* api callbacks */ ot->exec = armature_hide_exec; ot->poll = ED_operator_editarmature; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; /* props */ RNA_def_boolean(ot->srna, "unselected", 0, "Unselected", "Hide unselected rather than selected"); } /** \} */ /* -------------------------------------------------------------------- */ /** \name Reveal Operator * \{ */ static int armature_reveal_exec(bContext *C, wmOperator *op) { ViewLayer *view_layer = CTX_data_view_layer(C); const bool select = RNA_boolean_get(op->ptr, "select"); uint objects_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( view_layer, CTX_wm_view3d(C), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; bArmature *arm = obedit->data; bool changed = false; LISTBASE_FOREACH (EditBone *, ebone, arm->edbo) { if (arm->layer & ebone->layer) { if (ebone->flag & BONE_HIDDEN_A) { if (!(ebone->flag & BONE_UNSELECTABLE)) { SET_FLAG_FROM_TEST(ebone->flag, select, (BONE_TIPSEL | BONE_SELECTED | BONE_ROOTSEL)); } ebone->flag &= ~BONE_HIDDEN_A; changed = true; } } } if (changed) { ED_armature_edit_validate_active(arm); ED_armature_edit_sync_selection(arm->edbo); WM_event_add_notifier(C, NC_OBJECT | ND_BONE_SELECT, obedit); DEG_id_tag_update(&arm->id, ID_RECALC_SELECT); } } MEM_freeN(objects); return OPERATOR_FINISHED; } void ARMATURE_OT_reveal(wmOperatorType *ot) { /* identifiers */ ot->name = "Reveal Hidden"; ot->idname = "ARMATURE_OT_reveal"; ot->description = "Reveal all bones hidden in Edit Mode"; /* api callbacks */ ot->exec = armature_reveal_exec; ot->poll = ED_operator_editarmature; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; RNA_def_boolean(ot->srna, "select", true, "Select", ""); } /** \} */