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
This commit is contained in:
Dalai Felinto 2022-03-30 11:07:29 +02:00
parent 35f34a3cf8
commit 8621fdb10d
Notes: blender-bot 2023-02-14 08:58:01 +01:00
Referenced by commit 3e8bd1f6e4, Fix T100040: Crash when transform applied on multi-user image
Referenced by commit 9b25fafbec, Cleanup: Left over from review of apply transform
Referenced by issue #100040, Crash with rescaling reference image and apply scale
Referenced by issue #98192, Regression: Crash when apply transforms after deleting an object
2 changed files with 178 additions and 13 deletions

View File

@ -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

View File

@ -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 (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_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);
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);
}
/** \} */