Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHenrik Dick <hen-di@web.de>2021-08-12 15:22:19 +0300
committerSybren A. Stüvel <sybren@blender.org>2021-08-12 15:24:27 +0300
commitd6891d9bee2bd2073cb45e1ac9a04b2f03f05a9a (patch)
tree0c91afc7afff7859688c3875a329063422e749c9
parent215734bc522a8db532a626f460b85b66b0aa0a4c (diff)
Add Extras Dropdown Menu to Constraints
Add Apply Constraint, Duplicate Constraint, and Copy To Selected operators, and include them in a menu similar to the menu for modifiers. The shortcuts in the extras menu are also matched to modifiers. All the here added operators are intended to work exactly like the analogous ones for modifiers. That means the apply operator should apply a constraint as if it was first in the list, just like modifiers do. I have added the same warning message as for modifiers when that happens. The decision to use this approach of appling the constraint as if it was first, was made for consistency with modifiers. People are already used to how it works there. Is also provides more intricate control over the applied transforms, then just applying all constraints up to that one. Apply all constraints is already kinda implemented in Bake Animation. Reviewed By: HooglyBoogly, sybren, #user_interface Differential Revision: https://developer.blender.org/D10914
-rw-r--r--release/scripts/presets/keyconfig/keymap_data/blender_default.py2
-rw-r--r--release/scripts/presets/keyconfig/keymap_data/industry_compatible_data.py1
-rw-r--r--source/blender/blenkernel/BKE_constraint.h22
-rw-r--r--source/blender/blenkernel/intern/constraint.c105
-rw-r--r--source/blender/draw/engines/overlay/overlay_extra.c3
-rw-r--r--source/blender/editors/interface/interface_templates.c94
-rw-r--r--source/blender/editors/object/object_constraint.c324
-rw-r--r--source/blender/editors/object/object_intern.h3
-rw-r--r--source/blender/editors/object/object_ops.c3
-rw-r--r--source/blender/makesrna/intern/rna_constraint.c6
10 files changed, 543 insertions, 20 deletions
diff --git a/release/scripts/presets/keyconfig/keymap_data/blender_default.py b/release/scripts/presets/keyconfig/keymap_data/blender_default.py
index 7abbbaddb5c..d9538930f33 100644
--- a/release/scripts/presets/keyconfig/keymap_data/blender_default.py
+++ b/release/scripts/presets/keyconfig/keymap_data/blender_default.py
@@ -775,6 +775,8 @@ def km_property_editor(_params):
# Constraint panels
("constraint.delete", {"type": 'X', "value": 'PRESS'}, {"properties": [("report", True)]}),
("constraint.delete", {"type": 'DEL', "value": 'PRESS'}, {"properties": [("report", True)]}),
+ ("constraint.copy", {"type": 'D', "value": 'PRESS', "shift": True}, None),
+ ("constraint.apply", {"type": 'A', "value": 'PRESS', "ctrl": True}, {"properties": [("report", True)]}),
])
return keymap
diff --git a/release/scripts/presets/keyconfig/keymap_data/industry_compatible_data.py b/release/scripts/presets/keyconfig/keymap_data/industry_compatible_data.py
index cb9970f600b..dba94d71a43 100644
--- a/release/scripts/presets/keyconfig/keymap_data/industry_compatible_data.py
+++ b/release/scripts/presets/keyconfig/keymap_data/industry_compatible_data.py
@@ -465,6 +465,7 @@ def km_property_editor(params):
# Constraint panels
("constraint.delete", {"type": 'BACK_SPACE', "value": 'PRESS'}, {"properties": [("report", True)]}),
("constraint.delete", {"type": 'DEL', "value": 'PRESS'}, {"properties": [("report", True)]}),
+ ("constraint.copy", {"type": 'D', "value": 'PRESS', "ctrl": True}, None),
])
return keymap
diff --git a/source/blender/blenkernel/BKE_constraint.h b/source/blender/blenkernel/BKE_constraint.h
index 575df93a9fc..784b395dfa5 100644
--- a/source/blender/blenkernel/BKE_constraint.h
+++ b/source/blender/blenkernel/BKE_constraint.h
@@ -192,6 +192,28 @@ bool BKE_constraint_remove_ex(ListBase *list,
bool clear_dep);
bool BKE_constraint_remove(ListBase *list, struct bConstraint *con);
+bool BKE_constraint_apply_for_object(struct Depsgraph *depsgraph,
+ struct Scene *scene,
+ struct Object *ob,
+ struct bConstraint *con);
+bool BKE_constraint_apply_and_remove_for_object(struct Depsgraph *depsgraph,
+ struct Scene *scene,
+ ListBase /*bConstraint*/ *constraints,
+ struct Object *ob,
+ struct bConstraint *con);
+
+bool BKE_constraint_apply_for_pose(struct Depsgraph *depsgraph,
+ struct Scene *scene,
+ struct Object *ob,
+ struct bPoseChannel *pchan,
+ struct bConstraint *con);
+bool BKE_constraint_apply_and_remove_for_pose(struct Depsgraph *depsgraph,
+ struct Scene *scene,
+ ListBase /*bConstraint*/ *constraints,
+ struct Object *ob,
+ struct bConstraint *con,
+ struct bPoseChannel *pchan);
+
void BKE_constraint_panel_expand(struct bConstraint *con);
/* Constraints + Proxies function prototypes */
diff --git a/source/blender/blenkernel/intern/constraint.c b/source/blender/blenkernel/intern/constraint.c
index 0da29ded13d..4b26022039e 100644
--- a/source/blender/blenkernel/intern/constraint.c
+++ b/source/blender/blenkernel/intern/constraint.c
@@ -5677,6 +5677,111 @@ bool BKE_constraint_remove_ex(ListBase *list, Object *ob, bConstraint *con, bool
return false;
}
+/* Apply the specified constraint in the given constraint stack */
+bool BKE_constraint_apply_for_object(Depsgraph *depsgraph,
+ Scene *scene,
+ Object *ob,
+ bConstraint *con)
+{
+ if (!con) {
+ return false;
+ }
+
+ const float ctime = BKE_scene_frame_get(scene);
+
+ bConstraint *new_con = BKE_constraint_duplicate_ex(con, 0, !ID_IS_LINKED(ob));
+ ListBase single_con = {new_con, new_con};
+
+ bConstraintOb *cob = BKE_constraints_make_evalob(
+ depsgraph, scene, ob, NULL, CONSTRAINT_OBTYPE_OBJECT);
+ /* Undo the effect of the current constraint stack evaluation. */
+ mul_m4_m4m4(cob->matrix, ob->constinv, cob->matrix);
+
+ /* Evaluate single constraint. */
+ BKE_constraints_solve(depsgraph, &single_con, cob, ctime);
+ /* Copy transforms back. This will leave the object in a bad state
+ * as ob->constinv will be wrong until next evaluation. */
+ BKE_constraints_clear_evalob(cob);
+
+ /* Free the copied constraint. */
+ BKE_constraint_free_data(new_con);
+ BLI_freelinkN(&single_con, new_con);
+
+ /* Apply transform from matrix. */
+ BKE_object_apply_mat4(ob, ob->obmat, true, true);
+
+ return true;
+}
+
+bool BKE_constraint_apply_and_remove_for_object(Depsgraph *depsgraph,
+ Scene *scene,
+ ListBase /*bConstraint*/ *constraints,
+ Object *ob,
+ bConstraint *con)
+{
+ if (!BKE_constraint_apply_for_object(depsgraph, scene, ob, con)) {
+ return false;
+ }
+
+ return BKE_constraint_remove_ex(constraints, ob, con, true);
+}
+
+bool BKE_constraint_apply_for_pose(
+ Depsgraph *depsgraph, Scene *scene, Object *ob, bPoseChannel *pchan, bConstraint *con)
+{
+ if (!con) {
+ return false;
+ }
+
+ const float ctime = BKE_scene_frame_get(scene);
+
+ bConstraint *new_con = BKE_constraint_duplicate_ex(con, 0, !ID_IS_LINKED(ob));
+ ListBase single_con;
+ single_con.first = new_con;
+ single_con.last = new_con;
+
+ float vec[3];
+ copy_v3_v3(vec, pchan->pose_mat[3]);
+
+ bConstraintOb *cob = BKE_constraints_make_evalob(
+ depsgraph, scene, ob, pchan, CONSTRAINT_OBTYPE_BONE);
+ /* Undo the effects of currently applied constraints. */
+ mul_m4_m4m4(cob->matrix, pchan->constinv, cob->matrix);
+ /* Evaluate single constraint. */
+ BKE_constraints_solve(depsgraph, &single_con, cob, ctime);
+ BKE_constraints_clear_evalob(cob);
+
+ /* Free the copied constraint. */
+ BKE_constraint_free_data(new_con);
+ BLI_freelinkN(&single_con, new_con);
+
+ /* Prevent constraints breaking a chain. */
+ if (pchan->bone->flag & BONE_CONNECTED) {
+ copy_v3_v3(pchan->pose_mat[3], vec);
+ }
+
+ /* Apply transform from matrix. */
+ float mat[4][4];
+ BKE_armature_mat_pose_to_bone(pchan, pchan->pose_mat, mat);
+ BKE_pchan_apply_mat4(pchan, mat, true);
+
+ return true;
+}
+
+bool BKE_constraint_apply_and_remove_for_pose(Depsgraph *depsgraph,
+ Scene *scene,
+ ListBase /*bConstraint*/ *constraints,
+ Object *ob,
+ bConstraint *con,
+ bPoseChannel *pchan)
+{
+ if (!BKE_constraint_apply_for_pose(depsgraph, scene, ob, pchan, con)) {
+ return false;
+ }
+
+ return BKE_constraint_remove_ex(constraints, ob, con, true);
+}
+
void BKE_constraint_panel_expand(bConstraint *con)
{
con->ui_expand_flag |= UI_PANEL_DATA_EXPAND_ROOT;
diff --git a/source/blender/draw/engines/overlay/overlay_extra.c b/source/blender/draw/engines/overlay/overlay_extra.c
index f5be9c846d1..2a9080eb217 100644
--- a/source/blender/draw/engines/overlay/overlay_extra.c
+++ b/source/blender/draw/engines/overlay/overlay_extra.c
@@ -1357,7 +1357,8 @@ static void OVERLAY_relationship_lines(OVERLAY_ExtraCallBuffers *cb,
}
}
}
- BKE_constraints_clear_evalob(cob);
+ /* NOTE: Don't use BKE_constraints_clear_evalob here as that will reset ob->constinv. */
+ MEM_freeN(cob);
}
}
diff --git a/source/blender/editors/interface/interface_templates.c b/source/blender/editors/interface/interface_templates.c
index 766840909cc..47ff0c9fd3c 100644
--- a/source/blender/editors/interface/interface_templates.c
+++ b/source/blender/editors/interface/interface_templates.c
@@ -2621,6 +2621,72 @@ static void constraint_active_func(bContext *UNUSED(C), void *ob_v, void *con_v)
ED_object_constraint_active_set(ob_v, con_v);
}
+static void constraint_ops_extra_draw(bContext *C, uiLayout *layout, void *con_v)
+{
+ PointerRNA op_ptr;
+ uiLayout *row;
+ bConstraint *con = (bConstraint *)con_v;
+
+ PointerRNA ptr;
+ Object *ob = ED_object_active_context(C);
+
+ RNA_pointer_create(&ob->id, &RNA_Constraint, con, &ptr);
+ uiLayoutSetContextPointer(layout, "constraint", &ptr);
+ uiLayoutSetOperatorContext(layout, WM_OP_INVOKE_DEFAULT);
+
+ uiLayoutSetUnitsX(layout, 4.0f);
+
+ /* Apply. */
+ uiItemO(layout,
+ CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Apply"),
+ ICON_CHECKMARK,
+ "CONSTRAINT_OT_apply");
+
+ /* Duplicate. */
+ uiItemO(layout,
+ CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Duplicate"),
+ ICON_DUPLICATE,
+ "CONSTRAINT_OT_copy");
+
+ uiItemO(layout,
+ CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Copy to Selected"),
+ 0,
+ "CONSTRAINT_OT_copy_to_selected");
+
+ uiItemS(layout);
+
+ /* Move to first. */
+ row = uiLayoutColumn(layout, false);
+ uiItemFullO(row,
+ "CONSTRAINT_OT_move_to_index",
+ IFACE_("Move to First"),
+ ICON_TRIA_UP,
+ NULL,
+ WM_OP_INVOKE_DEFAULT,
+ 0,
+ &op_ptr);
+ RNA_int_set(&op_ptr, "index", 0);
+ if (!con->prev) {
+ uiLayoutSetEnabled(row, false);
+ }
+
+ /* Move to last. */
+ row = uiLayoutColumn(layout, false);
+ uiItemFullO(row,
+ "CONSTRAINT_OT_move_to_index",
+ IFACE_("Move to Last"),
+ ICON_TRIA_DOWN,
+ NULL,
+ WM_OP_INVOKE_DEFAULT,
+ 0,
+ &op_ptr);
+ ListBase *constraint_list = ED_object_constraint_list_from_constraint(ob, con, NULL);
+ RNA_int_set(&op_ptr, "index", BLI_listbase_count(constraint_list) - 1);
+ if (!con->next) {
+ uiLayoutSetEnabled(row, false);
+ }
+}
+
static void draw_constraint_header(uiLayout *layout, Object *ob, bConstraint *con)
{
bPoseChannel *pchan = BKE_pose_channel_active(ob);
@@ -2652,11 +2718,13 @@ static void draw_constraint_header(uiLayout *layout, Object *ob, bConstraint *co
UI_block_emboss_set(block, UI_EMBOSS);
+ uiLayout *row = uiLayoutRow(layout, true);
+
if (proxy_protected == 0) {
- uiItemR(layout, &ptr, "name", 0, "", ICON_NONE);
+ uiItemR(row, &ptr, "name", 0, "", ICON_NONE);
}
else {
- uiItemL(layout, con->name, ICON_NONE);
+ uiItemL(row, con->name, ICON_NONE);
}
/* proxy-protected constraints cannot be edited, so hide up/down + close buttons */
@@ -2697,22 +2765,22 @@ static void draw_constraint_header(uiLayout *layout, Object *ob, bConstraint *co
UI_block_emboss_set(block, UI_EMBOSS);
}
else {
- /* enabled */
- UI_block_emboss_set(block, UI_EMBOSS_NONE_OR_STATUS);
- uiItemR(layout, &ptr, "mute", 0, "", 0);
- UI_block_emboss_set(block, UI_EMBOSS);
+ /* Enabled eye icon. */
+ uiItemR(row, &ptr, "enabled", 0, "", ICON_NONE);
- uiLayoutSetOperatorContext(layout, WM_OP_INVOKE_DEFAULT);
+ /* Extra operators menu. */
+ uiItemMenuF(row, "", ICON_DOWNARROW_HLT, constraint_ops_extra_draw, con);
/* Close 'button' - emboss calls here disable drawing of 'button' behind X */
- UI_block_emboss_set(block, UI_EMBOSS_NONE);
- uiItemO(layout, "", ICON_X, "CONSTRAINT_OT_delete");
- UI_block_emboss_set(block, UI_EMBOSS);
-
- /* Some extra padding at the end, so the 'x' icon isn't too close to drag button. */
- uiItemS(layout);
+ sub = uiLayoutRow(row, false);
+ uiLayoutSetEmboss(sub, UI_EMBOSS_NONE);
+ uiLayoutSetOperatorContext(sub, WM_OP_INVOKE_DEFAULT);
+ uiItemO(sub, "", ICON_X, "CONSTRAINT_OT_delete");
}
+ /* Some extra padding at the end, so the 'x' icon isn't too close to drag button. */
+ uiItemS(layout);
+
/* Set but-locks for protected settings (magic numbers are used here!) */
if (proxy_protected) {
UI_block_lock_set(block, true, TIP_("Cannot edit Proxy-Protected Constraint"));
diff --git a/source/blender/editors/object/object_constraint.c b/source/blender/editors/object/object_constraint.c
index 3d0213f1830..e0419e0a4cc 100644
--- a/source/blender/editors/object/object_constraint.c
+++ b/source/blender/editors/object/object_constraint.c
@@ -1483,13 +1483,11 @@ static int constraint_delete_exec(bContext *C, wmOperator *op)
/* free the constraint */
if (BKE_constraint_remove_ex(lb, ob, con, true)) {
- /* there's no active constraint now, so make sure this is the case */
- BKE_constraints_active_set(&ob->constraints, NULL);
/* needed to set the flags on posebones correctly */
ED_object_constraint_update(bmain, ob);
/* relations */
- DEG_relations_tag_update(CTX_data_main(C));
+ DEG_relations_tag_update(bmain);
/* notifiers */
WM_event_add_notifier(C, NC_OBJECT | ND_CONSTRAINT | NA_REMOVED, ob);
@@ -1507,10 +1505,10 @@ static int constraint_delete_exec(bContext *C, wmOperator *op)
static int constraint_delete_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
int retval;
- if (edit_constraint_invoke_properties(C, op, event, &retval)) {
- return constraint_delete_exec(C, op);
+ if (!edit_constraint_invoke_properties(C, op, event, &retval)) {
+ return OPERATOR_CANCELLED;
}
- return OPERATOR_CANCELLED;
+ return constraint_delete_exec(C, op);
}
void CONSTRAINT_OT_delete(wmOperatorType *ot)
@@ -1534,6 +1532,320 @@ void CONSTRAINT_OT_delete(wmOperatorType *ot)
/** \} */
/* ------------------------------------------------------------------- */
+/** \name Apply Constraint Operator
+ * \{ */
+
+static int constraint_apply_exec(bContext *C, wmOperator *op)
+{
+ Scene *scene = CTX_data_scene(C);
+ Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
+ Main *bmain = CTX_data_main(C);
+ Object *ob = ED_object_active_context(C);
+ bConstraint *con = edit_constraint_property_get(C, op, ob, 0);
+ bPoseChannel *pchan;
+ ListBase *constraints = ED_object_constraint_list_from_constraint(ob, con, &pchan);
+
+ /* Store name temporarily for report. */
+ char name[MAX_NAME];
+ strcpy(name, con->name);
+ const bool is_first_constraint = con != constraints->first;
+
+ /* Copy the constraint. */
+ bool success;
+ if (pchan) {
+ success = BKE_constraint_apply_and_remove_for_pose(
+ depsgraph, scene, constraints, ob, con, pchan);
+ }
+ else {
+ success = BKE_constraint_apply_and_remove_for_object(depsgraph, scene, constraints, ob, con);
+ }
+
+ if (!success) {
+ /* Couldn't remove due to some invalid data. */
+ return OPERATOR_CANCELLED;
+ }
+
+ /* Update for any children that may get moved. */
+ DEG_id_tag_update(&ob->id, ID_RECALC_TRANSFORM);
+
+ /* Needed to set the flags on posebones correctly. */
+ ED_object_constraint_update(bmain, ob);
+
+ DEG_relations_tag_update(bmain);
+ WM_event_add_notifier(C, NC_OBJECT | ND_CONSTRAINT | NA_REMOVED, ob);
+ if (pchan) {
+ WM_event_add_notifier(C, NC_OBJECT | ND_POSE, ob);
+ }
+ else {
+ WM_event_add_notifier(C, NC_OBJECT | ND_TRANSFORM, ob);
+ }
+
+ if (RNA_boolean_get(op->ptr, "report")) {
+ if (is_first_constraint) {
+ BKE_report(op->reports,
+ RPT_INFO,
+ "Applied constraint was not first, result may not be as expected");
+ }
+ else {
+ /* Only add this report if the operator didn't cause another one. The purpose here is
+ * to alert that something happened, and the previous report will do that anyway. */
+ BKE_reportf(op->reports, RPT_INFO, "Applied constraint: %s", name);
+ }
+ }
+
+ return OPERATOR_FINISHED;
+}
+
+static int constraint_apply_invoke(bContext *C, wmOperator *op, const wmEvent *event)
+{
+ int retval;
+ if (!edit_constraint_invoke_properties(C, op, event, &retval)) {
+ return OPERATOR_CANCELLED;
+ }
+ return constraint_apply_exec(C, op);
+}
+
+void CONSTRAINT_OT_apply(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Apply Constraint";
+ ot->idname = "CONSTRAINT_OT_apply";
+ ot->description = "Apply constraint and remove from the stack";
+
+ /* callbacks */
+ ot->invoke = constraint_apply_invoke;
+ ot->exec = constraint_apply_exec;
+ ot->poll = edit_constraint_poll;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+ edit_constraint_properties(ot);
+ edit_constraint_report_property(ot);
+}
+
+/** \} */
+
+/* ------------------------------------------------------------------- */
+/** \name Copy Constraint Operator
+ * \{ */
+
+static int constraint_copy_exec(bContext *C, wmOperator *op)
+{
+ Main *bmain = CTX_data_main(C);
+ Object *ob = ED_object_active_context(C);
+ bConstraint *con = edit_constraint_property_get(C, op, ob, 0);
+ bPoseChannel *pchan;
+ ListBase *constraints = ED_object_constraint_list_from_constraint(ob, con, &pchan);
+
+ /* Store name temporarily for report. */
+ char name[MAX_NAME];
+ strcpy(name, con->name);
+
+ /* Copy the constraint. */
+ bConstraint *copy_con;
+ if (pchan) {
+ copy_con = BKE_constraint_copy_for_pose(ob, pchan, con);
+ }
+ else {
+ copy_con = BKE_constraint_copy_for_object(ob, con);
+ }
+
+ if (!copy_con) {
+ /* Couldn't remove due to some invalid data. */
+ return OPERATOR_CANCELLED;
+ }
+ /* Move constraint to correct position. */
+ const int new_index = BLI_findindex(constraints, con) + 1;
+ const int current_index = BLI_findindex(constraints, copy_con);
+ BLI_assert(new_index >= 0);
+ BLI_assert(current_index >= 0);
+ BLI_listbase_link_move(constraints, copy_con, new_index - current_index);
+
+ /* Needed to set the flags on posebones correctly. */
+ ED_object_constraint_update(bmain, ob);
+
+ DEG_relations_tag_update(bmain);
+ WM_event_add_notifier(C, NC_OBJECT | ND_CONSTRAINT | NA_ADDED, ob);
+
+ if (RNA_boolean_get(op->ptr, "report")) {
+ BKE_reportf(op->reports, RPT_INFO, "Copied constraint: %s", name);
+ }
+
+ return OPERATOR_FINISHED;
+}
+
+static int constraint_copy_invoke(bContext *C, wmOperator *op, const wmEvent *event)
+{
+ int retval;
+ if (!edit_constraint_invoke_properties(C, op, event, &retval)) {
+ return OPERATOR_CANCELLED;
+ }
+ return constraint_copy_exec(C, op);
+}
+
+void CONSTRAINT_OT_copy(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Duplicate Constraint";
+ ot->idname = "CONSTRAINT_OT_copy";
+ ot->description = "Duplicate constraint at the same position in the stack";
+
+ /* callbacks */
+ ot->invoke = constraint_copy_invoke;
+ ot->exec = constraint_copy_exec;
+ ot->poll = edit_constraint_poll;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+ edit_constraint_properties(ot);
+ edit_constraint_report_property(ot);
+}
+
+/** \} */
+
+/* ------------------------------------------------------------------- */
+/** \name Copy Constraint To Selected Operator
+ * \{ */
+
+static int constraint_copy_to_selected_exec(bContext *C, wmOperator *op)
+{
+ Main *bmain = CTX_data_main(C);
+ Object *obact = ED_object_active_context(C);
+ bConstraint *con = edit_constraint_property_get(C, op, obact, 0);
+ bPoseChannel *pchan;
+ ED_object_constraint_list_from_constraint(obact, con, &pchan);
+
+ if (pchan) {
+ /* Don't do anything if bone doesn't exist or doesn't have any constraints. */
+ if (pchan->constraints.first == NULL) {
+ BKE_report(op->reports, RPT_ERROR, "No constraints for copying");
+ return OPERATOR_CANCELLED;
+ }
+
+ Object *prev_ob = NULL;
+
+ /* Copy all constraints from active posebone to all selected posebones. */
+ CTX_DATA_BEGIN_WITH_ID (C, bPoseChannel *, chan, selected_pose_bones, Object *, ob) {
+ /* If we're not handling the object we're copying from, copy all constraints over. */
+ if (pchan == chan) {
+ continue;
+ }
+
+ BKE_constraint_copy_for_pose(ob, chan, con);
+ /* Update flags (need to add here, not just copy). */
+ chan->constflag |= pchan->constflag;
+
+ if (prev_ob == ob) {
+ continue;
+ }
+
+ BKE_pose_tag_recalc(bmain, ob->pose);
+ DEG_id_tag_update((ID *)ob, ID_RECALC_GEOMETRY);
+ prev_ob = ob;
+ }
+ CTX_DATA_END;
+ }
+ else {
+ /* Copy all constraints from active object to all selected objects. */
+ CTX_DATA_BEGIN (C, Object *, ob, selected_editable_objects) {
+ /* If we're not handling the object we're copying from, copy all constraints over. */
+ if (obact == ob) {
+ continue;
+ }
+
+ BKE_constraint_copy_for_object(ob, con);
+ DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY | ID_RECALC_TRANSFORM);
+ }
+ CTX_DATA_END;
+ }
+
+ /* Force depsgraph to get recalculated since new relationships added. */
+ DEG_relations_tag_update(bmain);
+
+ WM_event_add_notifier(C, NC_OBJECT | ND_CONSTRAINT, NULL);
+
+ return OPERATOR_FINISHED;
+}
+
+static int constraint_copy_to_selected_invoke(bContext *C, wmOperator *op, const wmEvent *event)
+{
+ int retval;
+ if (!edit_constraint_invoke_properties(C, op, event, &retval)) {
+ return retval;
+ }
+ return constraint_copy_to_selected_exec(C, op);
+}
+
+static bool constraint_copy_to_selected_poll(bContext *C)
+{
+ PointerRNA ptr = CTX_data_pointer_get_type(C, "constraint", &RNA_Constraint);
+ Object *obact = (ptr.owner_id) ? (Object *)ptr.owner_id : ED_object_active_context(C);
+ bConstraint *con = ptr.data;
+ bPoseChannel *pchan;
+ ED_object_constraint_list_from_constraint(obact, con, &pchan);
+
+ if (pchan) {
+ bool found = false;
+ CTX_DATA_BEGIN_WITH_ID (C, bPoseChannel *, chan, selected_pose_bones, Object *, UNUSED(ob)) {
+ if (pchan != chan) {
+ /** NOTE: Can not return here, because CTX_DATA_BEGIN_WITH_ID allocated
+ * a list that needs to be freed by CTX_DATA_END. */
+ found = true;
+ break;
+ }
+ }
+ CTX_DATA_END;
+ if (found) {
+ return true;
+ }
+
+ CTX_wm_operator_poll_msg_set(C, "No other bones are selected");
+ return false;
+ }
+
+ if (!obact) {
+ CTX_wm_operator_poll_msg_set(C, "No selected object to copy from");
+ return false;
+ }
+
+ bool found = false;
+ CTX_DATA_BEGIN (C, Object *, ob, selected_objects) {
+ if (ob != obact) {
+ /** NOTE: Can not return here, because CTX_DATA_BEGIN allocated
+ * a list that needs to be freed by CTX_DATA_END. */
+ found = true;
+ break;
+ }
+ }
+ CTX_DATA_END;
+ if (found) {
+ return true;
+ }
+
+ CTX_wm_operator_poll_msg_set(C, "No other objects are selected");
+ return false;
+}
+
+void CONSTRAINT_OT_copy_to_selected(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Copy Constraint To Selected";
+ ot->idname = "CONSTRAINT_OT_copy_to_selected";
+ ot->description = "Copy constraint to other selected objects/bones";
+
+ /* api callbacks */
+ ot->exec = constraint_copy_to_selected_exec;
+ ot->invoke = constraint_copy_to_selected_invoke;
+ ot->poll = constraint_copy_to_selected_poll;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+ edit_constraint_properties(ot);
+}
+
+/** \} */
+
+/* ------------------------------------------------------------------- */
/** \name Move Down Constraint Operator
* \{ */
diff --git a/source/blender/editors/object/object_intern.h b/source/blender/editors/object/object_intern.h
index 6299fdcc7f7..10e016738d0 100644
--- a/source/blender/editors/object/object_intern.h
+++ b/source/blender/editors/object/object_intern.h
@@ -226,6 +226,9 @@ void POSE_OT_ik_add(struct wmOperatorType *ot);
void POSE_OT_ik_clear(struct wmOperatorType *ot);
void CONSTRAINT_OT_delete(struct wmOperatorType *ot);
+void CONSTRAINT_OT_apply(struct wmOperatorType *ot);
+void CONSTRAINT_OT_copy(struct wmOperatorType *ot);
+void CONSTRAINT_OT_copy_to_selected(struct wmOperatorType *ot);
void CONSTRAINT_OT_move_up(struct wmOperatorType *ot);
void CONSTRAINT_OT_move_to_index(struct wmOperatorType *ot);
diff --git a/source/blender/editors/object/object_ops.c b/source/blender/editors/object/object_ops.c
index a438c760d3b..c1928cf7f8a 100644
--- a/source/blender/editors/object/object_ops.c
+++ b/source/blender/editors/object/object_ops.c
@@ -183,6 +183,9 @@ void ED_operatortypes_object(void)
WM_operatortype_append(POSE_OT_ik_add);
WM_operatortype_append(POSE_OT_ik_clear);
WM_operatortype_append(CONSTRAINT_OT_delete);
+ WM_operatortype_append(CONSTRAINT_OT_apply);
+ WM_operatortype_append(CONSTRAINT_OT_copy);
+ WM_operatortype_append(CONSTRAINT_OT_copy_to_selected);
WM_operatortype_append(CONSTRAINT_OT_move_up);
WM_operatortype_append(CONSTRAINT_OT_move_down);
WM_operatortype_append(CONSTRAINT_OT_move_to_index);
diff --git a/source/blender/makesrna/intern/rna_constraint.c b/source/blender/makesrna/intern/rna_constraint.c
index e36d052d27c..5968c8bac8f 100644
--- a/source/blender/makesrna/intern/rna_constraint.c
+++ b/source/blender/makesrna/intern/rna_constraint.c
@@ -3509,6 +3509,12 @@ void RNA_def_constraint(BlenderRNA *brna)
RNA_def_property_update(prop, NC_OBJECT | ND_CONSTRAINT, "rna_Constraint_update");
RNA_def_property_ui_icon(prop, ICON_HIDE_OFF, -1);
+ prop = RNA_def_property(srna, "enabled", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_negative_sdna(prop, NULL, "flag", CONSTRAINT_OFF);
+ RNA_def_property_ui_text(prop, "Enabled", "Use the results of this constraint");
+ RNA_def_property_update(prop, NC_OBJECT | ND_CONSTRAINT, "rna_Constraint_update");
+ RNA_def_property_ui_icon(prop, ICON_HIDE_ON, 1);
+
prop = RNA_def_property(srna, "show_expanded", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_flag(prop, PROP_NO_DEG_UPDATE);
RNA_def_property_boolean_sdna(prop, NULL, "ui_expand_flag", 0);