diff options
author | Dalai Felinto <dalai@blender.org> | 2022-03-30 12:07:29 +0300 |
---|---|---|
committer | Dalai Felinto <dalai@blender.org> | 2022-03-30 12:07:57 +0300 |
commit | 8621fdb10dc402eeff5aa996eeb992a513afd4c0 (patch) | |
tree | 9f159edc1f57c61524bcc327a30d24b18a342c73 | |
parent | 35f34a3cf840852b70c1be5910be5517265d96cc (diff) |
Apply Object Transform: Multi-user data support
The current behaviour is to prevent multi-user data from having its
transformation applied.
However in some particular cases it is possible to apply them:
* If all the users of the multi-user data are part of the selection.
* If not all the users are in the selection but the selection is made
single-user.
The active object is used as reference to set the transformation of the
other selected objects.
Note: For simplicity sake, this new behaviour is only available if all
the selection is using the same data.
Differential Revision: https://developer.blender.org/D14377
-rw-r--r-- | release/scripts/startup/bl_ui/space_view3d.py | 2 | ||||
-rw-r--r-- | source/blender/editors/object/object_transform.cc | 189 |
2 files changed, 178 insertions, 13 deletions
diff --git a/release/scripts/startup/bl_ui/space_view3d.py b/release/scripts/startup/bl_ui/space_view3d.py index e78ea9d7fc1..0919faa8460 100644 --- a/release/scripts/startup/bl_ui/space_view3d.py +++ b/release/scripts/startup/bl_ui/space_view3d.py @@ -2640,6 +2640,8 @@ class VIEW3D_MT_object_apply(Menu): def draw(self, _context): layout = self.layout + # Need invoke for the popup confirming the multi-user data operation + layout.operator_context = 'INVOKE_DEFAULT' props = layout.operator("object.transform_apply", text="Location", text_ctxt=i18n_contexts.default) props.location, props.rotation, props.scale = True, False, False diff --git a/source/blender/editors/object/object_transform.cc b/source/blender/editors/object/object_transform.cc index da75703a0d9..e279ebbb02e 100644 --- a/source/blender/editors/object/object_transform.cc +++ b/source/blender/editors/object/object_transform.cc @@ -587,18 +587,99 @@ static Array<Object *> sorted_selected_editable_objects(bContext *C) return sorted_objects; } +/** + * Check if we need and can handle the special multiuser case. + */ +static bool apply_objects_internal_can_multiuser(bContext *C) +{ + Object *obact = CTX_data_active_object(C); + + if (ELEM(NULL, obact, obact->data)) { + return false; + } + + if (ID_REAL_USERS(obact->data) == 1) { + return false; + } + + bool all_objects_same_data = true; + bool obact_selected = false; + + CTX_DATA_BEGIN (C, Object *, ob, selected_editable_objects) { + if (ob->data != obact->data) { + all_objects_same_data = false; + break; + } + + if (ob == obact) { + obact_selected = true; + } + } + CTX_DATA_END; + + return all_objects_same_data && obact_selected; +} + +/** + * Check if the current selection need to be made into single user + * + * It assumes that all selected objects share the same object data. + */ +static bool apply_objects_internal_need_single_user(bContext *C) +{ + Object *ob = CTX_data_active_object(C); + BLI_assert(apply_objects_internal_can_multiuser(C)); + + /* Counting the number of objects is valid since it's known the + * selection is only made up of users of the active objects data. */ + return (ID_REAL_USERS(ob->data) > CTX_DATA_COUNT(C, selected_editable_objects)); +} + static int apply_objects_internal(bContext *C, ReportList *reports, bool apply_loc, bool apply_rot, bool apply_scale, - bool do_props) + bool do_props, + bool do_single_user) { Main *bmain = CTX_data_main(C); Scene *scene = CTX_data_scene(C); Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); float rsmat[3][3], obmat[3][3], iobmat[3][3], mat[4][4], scale; bool changed = true; + bool do_multi_user = apply_objects_internal_can_multiuser(C); + float obact_invmat[4][4], obact_parent[4][4], obact_parentinv[4][4]; + + /* Only used when do_multi_user is set .*/ + Object *obact = NULL; + bool make_single_user = false; + + if (do_multi_user) { + obact = CTX_data_active_object(C); + invert_m4_m4(obact_invmat, obact->obmat); + + Object workob; + BKE_object_workob_calc_parent(depsgraph, scene, obact, &workob); + copy_m4_m4(obact_parent, workob.obmat); + copy_m4_m4(obact_parentinv, obact->parentinv); + + if (apply_objects_internal_need_single_user(C)) { + if (do_single_user) { + make_single_user = true; + } + else { + ID *obact_data = static_cast<ID *>(obact->data); + BKE_reportf(reports, + RPT_ERROR, + "Cannot apply to a multi user: Object \"%s\", %s \"%s\", aborting", + obact->id.name + 2, + BKE_idtype_idcode_to_name(GS(obact_data->name)), + obact_data->name + 2); + return OPERATOR_CANCELLED; + } + } + } /* first check if we can execute */ CTX_DATA_BEGIN (C, Object *, ob, selected_editable_objects) { @@ -612,7 +693,7 @@ static int apply_objects_internal(bContext *C, OB_FONT, OB_GPENCIL)) { ID *obdata = static_cast<ID *>(ob->data); - if (ID_REAL_USERS(obdata) > 1) { + if (!do_multi_user && ID_REAL_USERS(obdata) > 1) { BKE_reportf(reports, RPT_ERROR, R"(Cannot apply to a multi user: Object "%s", %s "%s", aborting)", @@ -728,6 +809,15 @@ static int apply_objects_internal(bContext *C, changed = false; /* now execute */ + + if (make_single_user) { + /* Make single user. */ + ED_object_single_obdata_user(bmain, scene, obact); + BKE_main_id_newptr_and_tag_clear(bmain); + WM_event_add_notifier(C, NC_WINDOW, NULL); + DEG_relations_tag_update(bmain); + } + Array<Object *> objects = sorted_selected_editable_objects(C); if (objects.is_empty()) { return OPERATOR_CANCELLED; @@ -774,7 +864,14 @@ static int apply_objects_internal(bContext *C, } /* apply to object data */ - if (ob->type == OB_MESH) { + if (do_multi_user && ob != obact) { + /* Don't apply, just set the new object data, the correct + * transformations will happen later. */ + id_us_min((ID *)ob->data); + ob->data = obact->data; + id_us_plus((ID *)ob->data); + } + else if (ob->type == OB_MESH) { Mesh *me = static_cast<Mesh *>(ob->data); if (apply_scale) { @@ -882,16 +979,53 @@ static int apply_objects_internal(bContext *C, continue; } - if (apply_loc) { - zero_v3(ob->loc); - } - if (apply_scale) { - ob->scale[0] = ob->scale[1] = ob->scale[2] = 1.0f; + if (do_multi_user && ob != obact) { + float _obmat[4][4], _iobmat[4][4]; + float _mat[4][4]; + + copy_m4_m4(_obmat, ob->obmat); + invert_m4_m4(_iobmat, _obmat); + + copy_m4_m4(_mat, _obmat); + mul_m4_m4_post(_mat, obact_invmat); + mul_m4_m4_post(_mat, obact_parent); + mul_m4_m4_post(_mat, obact_parentinv); + + if (apply_loc && apply_scale && apply_rot) { + BKE_object_apply_mat4(ob, _mat, false, true); + } + else { + Object ob_temp = blender::dna::shallow_copy(*ob); + BKE_object_apply_mat4(&ob_temp, _mat, false, true); + + if (apply_loc) { + copy_v3_v3(ob->loc, ob_temp.loc); + } + + if (apply_scale) { + copy_v3_v3(ob->scale, ob_temp.scale); + } + + if (apply_rot) { + copy_v4_v4(ob->quat, ob_temp.quat); + copy_v3_v3(ob->rot, ob_temp.rot); + copy_v3_v3(ob->rotAxis, ob_temp.rotAxis); + ob->rotAngle = ob_temp.rotAngle; + } + } } - if (apply_rot) { - zero_v3(ob->rot); - unit_qt(ob->quat); - unit_axis_angle(ob->rotAxis, &ob->rotAngle); + else { + if (apply_loc) { + zero_v3(ob->loc); + } + if (apply_scale) { + ob->scale[0] = ob->scale[1] = ob->scale[2] = 1.0f; + } + if (apply_rot) { + zero_v3(ob->rot); + unit_qt(ob->quat); + unit_axis_angle(ob->rotAxis, &ob->rotAngle); + } } Object *ob_eval = DEG_get_evaluated_object(depsgraph, ob); @@ -969,14 +1103,35 @@ static int object_transform_apply_exec(bContext *C, wmOperator *op) const bool rot = RNA_boolean_get(op->ptr, "rotation"); const bool sca = RNA_boolean_get(op->ptr, "scale"); const bool do_props = RNA_boolean_get(op->ptr, "properties"); + const bool do_single_user = RNA_boolean_get(op->ptr, "isolate_users"); if (loc || rot || sca) { - return apply_objects_internal(C, op->reports, loc, rot, sca, do_props); + return apply_objects_internal(C, op->reports, loc, rot, sca, do_props, do_single_user); } /* allow for redo */ return OPERATOR_FINISHED; } +static int object_transform_apply_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event)) +{ + Object *ob = ED_object_active_context(C); + + bool can_handle_multiuser = apply_objects_internal_can_multiuser(C); + bool need_single_user = can_handle_multiuser && apply_objects_internal_need_single_user(C); + + if ((ob->data != NULL) && need_single_user) { + PropertyRNA *prop = RNA_struct_find_property(op->ptr, "isolate_users"); + if (!RNA_property_is_set(op->ptr, prop)) { + RNA_property_boolean_set(op->ptr, prop, true); + } + if (RNA_property_boolean_get(op->ptr, prop)) { + return WM_operator_confirm_message( + C, op, "Create new object-data users and apply transformation"); + } + } + return object_transform_apply_exec(C, op); +} + void OBJECT_OT_transform_apply(wmOperatorType *ot) { /* identifiers */ @@ -986,6 +1141,7 @@ void OBJECT_OT_transform_apply(wmOperatorType *ot) /* api callbacks */ ot->exec = object_transform_apply_exec; + ot->invoke = object_transform_apply_invoke; ot->poll = ED_operator_objectmode; /* flags */ @@ -999,6 +1155,13 @@ void OBJECT_OT_transform_apply(wmOperatorType *ot) true, "Apply Properties", "Modify properties such as curve vertex radius, font size and bone envelope"); + PropertyRNA *prop = RNA_def_boolean(ot->srna, + "isolate_users", + false, + "Isolate Multi User Data", + "Create new object-data users if needed"); + RNA_def_property_flag(prop, PROP_HIDDEN); + RNA_def_property_flag(prop, PROP_SKIP_SAVE); } /** \} */ |