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:
parent
35f34a3cf8
commit
8621fdb10d
Notes:
blender-bot
2023-02-14 08:58:01 +01:00
Referenced by commit3e8bd1f6e4
, Fix T100040: Crash when transform applied on multi-user image Referenced by commit9b25fafbec
, 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
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
|
Loading…
Reference in New Issue