Modifiers: Affect all selected objects when holding alt #120695

Merged
Hans Goudey merged 8 commits from HooglyBoogly/blender:modifier-multi-object-alt into main 2024-04-18 17:41:04 +02:00
6 changed files with 323 additions and 183 deletions

View File

@ -4,7 +4,7 @@
import bpy
from bpy.types import Operator
from bpy.props import IntProperty
from bpy.props import IntProperty, BoolProperty
from bpy.app.translations import pgettext_data as data_
@ -136,6 +136,101 @@ def get_enabled_socket_with_name(sockets, name):
return socket
return None
def create_wrapper_group(modifier, old_group):
wrapper_name = old_group.name + ".wrapper"
group = bpy.data.node_groups.new(wrapper_name, 'GeometryNodeTree')
group.interface.new_socket(data_("Geometry"), in_out='OUTPUT', socket_type='NodeSocketGeometry')
group.is_modifier = True
first_geometry_input = next((item for item in old_group.interface.items_tree if item.item_type == 'SOCKET' and
item.in_out == 'INPUT' and
item.bl_socket_idname == 'NodeSocketGeometry'), None)
if first_geometry_input:
group.interface.new_socket(data_("Geometry"), in_out='INPUT', socket_type='NodeSocketGeometry')
group_input_node = group.nodes.new('NodeGroupInput')
group_input_node.location.x = -200 - group_input_node.width
group_input_node.select = False
group_output_node = group.nodes.new('NodeGroupOutput')
group_output_node.is_active_output = True
group_output_node.location.x = 200
group_output_node.select = False
group_node = group.nodes.new("GeometryNodeGroup")
group_node.node_tree = old_group
group_node.update()
# Copy default values for inputs and create named attribute input nodes.
input_nodes = []
for input_socket in old_group.interface.items_tree:
if input_socket.item_type != 'SOCKET' or (input_socket.in_out not in {'INPUT', 'BOTH'}):
continue
identifier = input_socket.identifier
group_node_input = get_socket_with_identifier(group_node.inputs, identifier)
if modifier_input_use_attribute(modifier, identifier):
input_node = group.nodes.new("GeometryNodeInputNamedAttribute")
input_nodes.append(input_node)
input_node.data_type = socket_idname_to_attribute_type(input_socket.bl_socket_idname)
attribute_name = modifier_attribute_name_get(modifier, identifier)
input_node.inputs["Name"].default_value = attribute_name
output_socket = get_enabled_socket_with_name(input_node.outputs, "Attribute")
group.links.new(output_socket, group_node_input)
elif hasattr(input_socket, "default_value"):
group_node_input.default_value = modifier[identifier]
if first_geometry_input:
group.links.new(group_input_node.outputs[0],
get_socket_with_identifier(group_node.inputs, first_geometry_input.identifier))
# Adjust locations of named attribute input nodes and group input node to make some space.
if input_nodes:
for i, node in enumerate(input_nodes):
node.location.x = -175
node.location.y = i * -50
group_input_node.location.x = -350
# Connect outputs to store named attribute nodes to replace modifier attribute outputs.
store_nodes = []
first_geometry_output = None
for output_socket in old_group.interface.items_tree:
if output_socket.item_type != 'SOCKET' or (output_socket.in_out not in {'OUTPUT', 'BOTH'}):
continue
identifier = output_socket.identifier
group_node_output = get_socket_with_identifier(group_node.outputs, identifier)
attribute_name = modifier_attribute_name_get(modifier, identifier)
if attribute_name:
store_node = group.nodes.new("GeometryNodeStoreNamedAttribute")
store_nodes.append(store_node)
store_node.data_type = socket_idname_to_attribute_type(output_socket.bl_socket_idname)
store_node.domain = output_socket.attribute_domain
store_node.inputs["Name"].default_value = attribute_name
input_socket = get_enabled_socket_with_name(store_node.inputs, "Value")
group.links.new(group_node_output, input_socket)
elif output_socket.bl_socket_idname == 'NodeSocketGeometry':
if not first_geometry_output:
first_geometry_output = group_node_output
# Adjust locations of store named attribute nodes and move group output.
# Note that the node group has its sockets names translated, while the built-in nodes don't.
if store_nodes:
for i, node in enumerate(store_nodes):
node.location.x = (i + 1) * 175
node.location.y = 0
group_output_node.location.x = (len(store_nodes) + 1) * 175
group.links.new(first_geometry_output, store_nodes[0].inputs["Geometry"])
for i in range(len(store_nodes) - 1):
group.links.new(store_nodes[i].outputs["Geometry"], store_nodes[i + 1].inputs["Geometry"])
group.links.new(store_nodes[-1].outputs["Geometry"], group_output_node.inputs[data_("Geometry")])
else:
if not first_geometry_output:
self.report({'WARNING'}, "Node group must have a geometry output")
return {'CANCELLED'}
group.links.new(first_geometry_output, group_output_node.inputs[data_("Geometry")])
return group
class MoveModifierToNodes(Operator):
"""Move inputs and outputs from in the modifier to a new node group"""
@ -144,111 +239,40 @@ class MoveModifierToNodes(Operator):
bl_label = "Move to Nodes"
bl_options = {'REGISTER', 'UNDO'}
use_selected_objects: BoolProperty(
name="Selected Objects",
description="Affect all selected objects instead of just the active object",
)
@classmethod
def poll(cls, context):
return edit_geometry_nodes_modifier_poll(context)
def invoke(self, context, event):
if event.alt:
self.use_selected_objects = True
return self.execute(context)
def execute(self, context):
modifier = get_context_modifier(context)
if not modifier:
return {'CANCELLED'}
old_group = modifier.node_group
if not old_group:
active_modifier = get_context_modifier(context)
if not active_modifier:
return {'CANCELLED'}
modifier_name = active_modifier.name
wrapper_name = old_group.name + ".wrapper"
group = bpy.data.node_groups.new(wrapper_name, 'GeometryNodeTree')
group.interface.new_socket(data_("Geometry"), in_out='OUTPUT', socket_type='NodeSocketGeometry')
group.is_modifier = True
first_geometry_input = next((item for item in old_group.interface.items_tree if item.item_type == 'SOCKET' and
item.in_out == 'INPUT' and
item.bl_socket_idname == 'NodeSocketGeometry'), None)
if first_geometry_input:
group.interface.new_socket(data_("Geometry"), in_out='INPUT', socket_type='NodeSocketGeometry')
group_input_node = group.nodes.new('NodeGroupInput')
group_input_node.location.x = -200 - group_input_node.width
group_input_node.select = False
group_output_node = group.nodes.new('NodeGroupOutput')
group_output_node.is_active_output = True
group_output_node.location.x = 200
group_output_node.select = False
group_node = group.nodes.new("GeometryNodeGroup")
group_node.node_tree = old_group
group_node.update()
# Copy default values for inputs and create named attribute input nodes.
input_nodes = []
for input_socket in old_group.interface.items_tree:
if input_socket.item_type != 'SOCKET' or (input_socket.in_out not in {'INPUT', 'BOTH'}):
continue
identifier = input_socket.identifier
group_node_input = get_socket_with_identifier(group_node.inputs, identifier)
if modifier_input_use_attribute(modifier, identifier):
input_node = group.nodes.new("GeometryNodeInputNamedAttribute")
input_nodes.append(input_node)
input_node.data_type = socket_idname_to_attribute_type(input_socket.bl_socket_idname)
attribute_name = modifier_attribute_name_get(modifier, identifier)
input_node.inputs["Name"].default_value = attribute_name
output_socket = get_enabled_socket_with_name(input_node.outputs, "Attribute")
group.links.new(output_socket, group_node_input)
elif hasattr(input_socket, "default_value"):
group_node_input.default_value = modifier[identifier]
if first_geometry_input:
group.links.new(group_input_node.outputs[0],
get_socket_with_identifier(group_node.inputs, first_geometry_input.identifier))
# Adjust locations of named attribute input nodes and group input node to make some space.
if input_nodes:
for i, node in enumerate(input_nodes):
node.location.x = -175
node.location.y = i * -50
group_input_node.location.x = -350
# Connect outputs to store named attribute nodes to replace modifier attribute outputs.
store_nodes = []
first_geometry_output = None
for output_socket in old_group.interface.items_tree:
if output_socket.item_type != 'SOCKET' or (output_socket.in_out not in {'OUTPUT', 'BOTH'}):
continue
identifier = output_socket.identifier
group_node_output = get_socket_with_identifier(group_node.outputs, identifier)
attribute_name = modifier_attribute_name_get(modifier, identifier)
if attribute_name:
store_node = group.nodes.new("GeometryNodeStoreNamedAttribute")
store_nodes.append(store_node)
store_node.data_type = socket_idname_to_attribute_type(output_socket.bl_socket_idname)
store_node.domain = output_socket.attribute_domain
store_node.inputs["Name"].default_value = attribute_name
input_socket = get_enabled_socket_with_name(store_node.inputs, "Value")
group.links.new(group_node_output, input_socket)
elif output_socket.bl_socket_idname == 'NodeSocketGeometry':
if not first_geometry_output:
first_geometry_output = group_node_output
# Adjust locations of store named attribute nodes and move group output.
# Note that the node group has its sockets names translated, while the built-in nodes don't.
if store_nodes:
for i, node in enumerate(store_nodes):
node.location.x = (i + 1) * 175
node.location.y = 0
group_output_node.location.x = (len(store_nodes) + 1) * 175
group.links.new(first_geometry_output, store_nodes[0].inputs["Geometry"])
for i in range(len(store_nodes) - 1):
group.links.new(store_nodes[i].outputs["Geometry"], store_nodes[i + 1].inputs["Geometry"])
group.links.new(store_nodes[-1].outputs["Geometry"], group_output_node.inputs[data_("Geometry")])
objects = []
if self.use_selected_objects:
objects = context.selected_editable_objects
else:
if not first_geometry_output:
self.report({'WARNING'}, "Node group must have a geometry output")
return {'CANCELLED'}
group.links.new(first_geometry_output, group_output_node.inputs[data_("Geometry")])
objects = [context.object]
modifier.node_group = group
for ob in objects:
modifier = ob.modifiers[modifier_name]
if not modifier:
continue
old_group = modifier.node_group
if not old_group:
continue
modifier.node_group = create_wrapper_group(modifier, old_group)
return {'FINISHED'}

View File

@ -66,7 +66,7 @@ class OBJECT_MT_modifier_add(ModifierAddMenu, Menu):
icon='VIEWZOOM').menu_idname = "OBJECT_MT_modifier_add"
layout.separator()
layout.operator_context = 'EXEC_REGION_WIN'
layout.operator_context = 'INVOKE_REGION_WIN'
if geometry_nodes_supported:
self.operator_modifier_add(layout, 'NODES')

View File

@ -26,6 +26,7 @@
#include "BLT_translation.hh"
#include "RNA_access.hh"
#include "RNA_define.hh"
#include "ED_asset.hh"
#include "ED_asset_menu_utils.hh"
@ -295,11 +296,9 @@ static int modifier_add_asset_exec(bContext *C, wmOperator *op)
{
Main *bmain = CTX_data_main(C);
Scene *scene = CTX_data_scene(C);
Object *object = context_active_object(C);
NodesModifierData *nmd = reinterpret_cast<NodesModifierData *>(
modifier_add(op->reports, bmain, scene, object, nullptr, eModifierType_Nodes));
if (!nmd) {
Vector<PointerRNA> objects = modifier_get_edit_objects(*C, *op);
if (objects.is_empty()) {
return OPERATOR_CANCELLED;
}
@ -308,21 +307,43 @@ static int modifier_add_asset_exec(bContext *C, wmOperator *op)
return OPERATOR_CANCELLED;
}
nmd->node_group = node_group;
id_us_plus(&node_group->id);
MOD_nodes_update_interface(object, nmd);
bool changed = false;
for (const PointerRNA &ptr : objects) {
Object *object = static_cast<Object *>(ptr.data);
NodesModifierData *nmd = reinterpret_cast<NodesModifierData *>(
modifier_add(op->reports, bmain, scene, object, nullptr, eModifierType_Nodes));
if (!nmd) {
continue;
}
changed = true;
nmd->node_group = node_group;
id_us_plus(&node_group->id);
MOD_nodes_update_interface(object, nmd);
/* By default, don't show the data-block selector since it's not usually necessary for assets. */
nmd->flag |= NODES_MODIFIER_HIDE_DATABLOCK_SELECTOR;
/* Don't show the data-block selector since it's not usually necessary for assets. */
nmd->flag |= NODES_MODIFIER_HIDE_DATABLOCK_SELECTOR;
STRNCPY(nmd->modifier.name, DATA_(node_group->id.name + 2));
BKE_modifier_unique_name(&object->modifiers, &nmd->modifier);
STRNCPY(nmd->modifier.name, DATA_(node_group->id.name + 2));
BKE_modifier_unique_name(&object->modifiers, &nmd->modifier);
WM_event_add_notifier(C, NC_OBJECT | ND_MODIFIER, object);
WM_event_add_notifier(C, NC_OBJECT | ND_MODIFIER, object);
}
if (!changed) {
return OPERATOR_CANCELLED;
}
return OPERATOR_FINISHED;
}
static int modifier_add_asset_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
if (event->modifier & KM_ALT) {
RNA_boolean_set(op->ptr, "use_selected_objects", true);
}
return modifier_add_asset_exec(C, op);
}
static std::string modifier_add_asset_get_description(bContext *C,
wmOperatorType * /*ot*/,
PointerRNA *ptr)
@ -344,6 +365,7 @@ static void OBJECT_OT_modifier_add_node_group(wmOperatorType *ot)
ot->description = "Add a procedural operation/effect to the active object";
ot->idname = "OBJECT_OT_modifier_add_node_group";
ot->invoke = modifier_add_asset_invoke;
ot->exec = modifier_add_asset_exec;
ot->poll = ED_operator_object_active_editable;
ot->get_description = modifier_add_asset_get_description;
@ -352,6 +374,7 @@ static void OBJECT_OT_modifier_add_node_group(wmOperatorType *ot)
asset::operator_asset_reference_props_register(*ot->srna);
WM_operator_properties_id_lookup(ot, false);
modifier_register_use_selected_objects_prop(ot);
}
static MenuType modifier_add_unassigned_assets_menu_type()

View File

@ -8,6 +8,10 @@
#pragma once
#include "BLI_vector.hh"
#include "RNA_types.hh"
struct bContext;
struct ModifierData;
struct Object;
@ -379,4 +383,7 @@ void object_modifier_add_asset_register();
void collection_exporter_register();
Vector<PointerRNA> modifier_get_edit_objects(const bContext &C, const wmOperator &op);
void modifier_register_use_selected_objects_prop(wmOperatorType *ot);
} // namespace blender::ed::object

View File

@ -1233,6 +1233,29 @@ bool modifier_copy(
/** \} */
Vector<PointerRNA> modifier_get_edit_objects(const bContext &C, const wmOperator &op)
{
Vector<PointerRNA> objects;
if (RNA_boolean_get(op.ptr, "use_selected_objects")) {
CTX_data_selected_editable_objects(&C, &objects);
}
else {
if (Object *object = context_active_object(&C)) {
objects.append(RNA_id_pointer_create(&object->id));
}
}
return objects;
}
void modifier_register_use_selected_objects_prop(wmOperatorType *ot)
{
RNA_def_boolean(ot->srna,
"use_selected_objects",
false,
"Selected Objects",
"Affect all selected objects instead of just the active object");
}
/* ------------------------------------------------------------------- */
/** \name Add Modifier Operator
* \{ */
@ -1241,18 +1264,35 @@ static int modifier_add_exec(bContext *C, wmOperator *op)
{
Main *bmain = CTX_data_main(C);
Scene *scene = CTX_data_scene(C);
Object *ob = context_active_object(C);
int type = RNA_enum_get(op->ptr, "type");
if (!modifier_add(op->reports, bmain, scene, ob, nullptr, type)) {
bool changed = false;
for (const PointerRNA &ptr : modifier_get_edit_objects(*C, *op)) {
Object *ob = static_cast<Object *>(ptr.data);
if (!modifier_add(op->reports, bmain, scene, ob, nullptr, type)) {
continue;
}
changed = true;
WM_event_add_notifier(C, NC_OBJECT | ND_MODIFIER, ob);
}
if (!changed) {
return OPERATOR_CANCELLED;
}
WM_event_add_notifier(C, NC_OBJECT | ND_MODIFIER, ob);
return OPERATOR_FINISHED;
}
static int modifier_add_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
if (event->modifier & KM_ALT) {
RNA_boolean_set(op->ptr, "use_selected_objects", true);
}
if (!RNA_struct_property_is_set(op->ptr, "type")) {
return WM_menu_invoke(C, op, event);
}
return modifier_add_exec(C, op);
}
static const EnumPropertyItem *modifier_add_itemf(bContext *C,
PointerRNA * /*ptr*/,
PropertyRNA * /*prop*/,
@ -1311,7 +1351,7 @@ void OBJECT_OT_modifier_add(wmOperatorType *ot)
ot->idname = "OBJECT_OT_modifier_add";
/* api callbacks */
ot->invoke = WM_menu_invoke;
ot->invoke = modifier_add_invoke;
ot->exec = modifier_add_exec;
ot->poll = ED_operator_object_active_editable;
@ -1323,6 +1363,7 @@ void OBJECT_OT_modifier_add(wmOperatorType *ot)
ot->srna, "type", rna_enum_object_modifier_type_items, eModifierType_Subsurf, "Type", "");
RNA_def_enum_funcs(prop, modifier_add_itemf);
ot->prop = prop;
modifier_register_use_selected_objects_prop(ot);
}
/** \} */
@ -1432,6 +1473,12 @@ static bool edit_modifier_invoke_properties_with_hover(bContext *C,
const wmEvent *event,
int *r_retval)
{
if (RNA_struct_find_property(op->ptr, "use_selected_objects")) {
if (event->modifier & KM_ALT) {
RNA_boolean_set(op->ptr, "use_selected_objects", true);
}
}
if (RNA_struct_property_is_set(op->ptr, "modifier")) {
return true;
}
@ -1488,34 +1535,42 @@ static int modifier_remove_exec(bContext *C, wmOperator *op)
Main *bmain = CTX_data_main(C);
Scene *scene = CTX_data_scene(C);
ViewLayer *view_layer = CTX_data_view_layer(C);
Object *ob = context_active_object(C);
ModifierData *md = edit_modifier_property_get(op, ob, 0);
int mode_orig = ob->mode;
if (md == nullptr) {
return OPERATOR_CANCELLED;
}
/* Store name temporarily for report. */
char name[MAX_NAME];
STRNCPY(name, md->name);
RNA_string_get(op->ptr, "modifier", name);
if (!modifier_remove(op->reports, bmain, scene, ob, md)) {
return OPERATOR_CANCELLED;
}
bool changed = false;
for (const PointerRNA &ptr : modifier_get_edit_objects(*C, *op)) {
Object *ob = static_cast<Object *>(ptr.data);
ModifierData *md = BKE_modifiers_findby_name(ob, name);
if (md == nullptr) {
continue;
}
WM_event_add_notifier(C, NC_OBJECT | ND_MODIFIER, ob);
int mode_orig = ob->mode;
if (!modifier_remove(op->reports, bmain, scene, ob, md)) {
continue;
}
/* if cloth/softbody was removed, particle mode could be cleared */
if (mode_orig & OB_MODE_PARTICLE_EDIT) {
if ((ob->mode & OB_MODE_PARTICLE_EDIT) == 0) {
BKE_view_layer_synced_ensure(scene, view_layer);
if (ob == BKE_view_layer_active_object_get(view_layer)) {
WM_event_add_notifier(C, NC_SCENE | ND_MODE | NS_MODE_OBJECT, nullptr);
changed = true;
WM_event_add_notifier(C, NC_OBJECT | ND_MODIFIER, ob);
/* if cloth/softbody was removed, particle mode could be cleared */
if (mode_orig & OB_MODE_PARTICLE_EDIT) {
if ((ob->mode & OB_MODE_PARTICLE_EDIT) == 0) {
BKE_view_layer_synced_ensure(scene, view_layer);
if (ob == BKE_view_layer_active_object_get(view_layer)) {
WM_event_add_notifier(C, NC_SCENE | ND_MODE | NS_MODE_OBJECT, nullptr);
}
}
}
}
if (!changed) {
return OPERATOR_CANCELLED;
}
if (RNA_boolean_get(op->ptr, "report")) {
BKE_reportf(op->reports, RPT_INFO, "Removed modifier: %s", name);
}
@ -1546,6 +1601,7 @@ void OBJECT_OT_modifier_remove(wmOperatorType *ot)
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_INTERNAL;
edit_modifier_properties(ot);
edit_modifier_report_property(ot);
modifier_register_use_selected_objects_prop(ot);
}
/** \} */
@ -1646,11 +1702,26 @@ void OBJECT_OT_modifier_move_down(wmOperatorType *ot)
static int modifier_move_to_index_exec(bContext *C, wmOperator *op)
{
Object *ob = context_active_object(C);
ModifierData *md = edit_modifier_property_get(op, ob, 0);
int index = RNA_int_get(op->ptr, "index");
char name[MAX_NAME];
RNA_string_get(op->ptr, "modifier", name);
if (!(md && modifier_move_to_index(op->reports, RPT_WARNING, ob, md, index, true))) {
const int index = RNA_int_get(op->ptr, "index");
bool changed = false;
for (const PointerRNA &ptr : modifier_get_edit_objects(*C, *op)) {
Object *ob = static_cast<Object *>(ptr.data);
ModifierData *md = BKE_modifiers_findby_name(ob, name);
if (!md) {
continue;
}
if (!modifier_move_to_index(op->reports, RPT_WARNING, ob, md, index, true)) {
continue;
}
changed = true;
}
if (!changed) {
return OPERATOR_CANCELLED;
}
@ -1682,6 +1753,7 @@ void OBJECT_OT_modifier_move_to_index(wmOperatorType *ot)
edit_modifier_properties(ot);
RNA_def_int(
ot->srna, "index", 0, 0, INT_MAX, "Index", "The index to move the modifier to", 0, INT_MAX);
modifier_register_use_selected_objects_prop(ot);
}
/** \} */
@ -1722,9 +1794,13 @@ static int modifier_apply_exec_ex(bContext *C, wmOperator *op, int apply_as, boo
Main *bmain = CTX_data_main(C);
Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
Scene *scene = CTX_data_scene(C);
Object *ob = context_active_object(C);
ModifierData *md = edit_modifier_property_get(op, ob, 0);
Vector<PointerRNA> objects = modifier_get_edit_objects(*C, *op);
char name[MAX_NAME];
RNA_string_get(op->ptr, "modifier", name);
const bool do_report = RNA_boolean_get(op->ptr, "report");
const int reports_len = do_report ? BLI_listbase_count(&op->reports->list) : 0;
const bool do_single_user = (apply_as == MODIFIER_APPLY_DATA) ?
RNA_boolean_get(op->ptr, "single_user") :
@ -1733,40 +1809,43 @@ static int modifier_apply_exec_ex(bContext *C, wmOperator *op, int apply_as, boo
RNA_boolean_get(op->ptr, "merge_customdata") :
false;
if (md == nullptr) {
return OPERATOR_CANCELLED;
}
bool changed = false;
for (const PointerRNA &ptr : objects) {
Object *ob = static_cast<Object *>(ptr.data);
ModifierData *md = BKE_modifiers_findby_name(ob, name);
if (md == nullptr) {
continue;
}
const ModifierTypeInfo *mti = BKE_modifier_get_info((ModifierType)md->type);
const ModifierTypeInfo *mti = BKE_modifier_get_info((ModifierType)md->type);
if (do_single_user && ID_REAL_USERS(ob->data) > 1) {
single_obdata_user_make(bmain, scene, ob);
BKE_main_id_newptr_and_tag_clear(bmain);
WM_event_add_notifier(C, NC_WINDOW, nullptr);
if (do_single_user && ID_REAL_USERS(ob->data) > 1) {
single_obdata_user_make(bmain, scene, ob);
BKE_main_id_newptr_and_tag_clear(bmain);
WM_event_add_notifier(C, NC_WINDOW, nullptr);
DEG_relations_tag_update(bmain);
}
if (!modifier_apply(bmain, op->reports, depsgraph, scene, ob, md, apply_as, keep_modifier)) {
continue;
}
changed = true;
if (ob->type == OB_MESH && do_merge_customdata &&
ELEM(mti->type, ModifierTypeType::Constructive, ModifierTypeType::Nonconstructive))
{
BKE_mesh_merge_customdata_for_apply_modifier((Mesh *)ob->data);
}
DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY);
DEG_relations_tag_update(bmain);
WM_event_add_notifier(C, NC_OBJECT | ND_MODIFIER, ob);
}
int reports_len;
char name[MAX_NAME];
if (do_report) {
reports_len = BLI_listbase_count(&op->reports->list);
STRNCPY(name, md->name); /* Store name temporarily since the modifier is removed. */
}
if (!modifier_apply(bmain, op->reports, depsgraph, scene, ob, md, apply_as, keep_modifier)) {
if (!changed) {
return OPERATOR_CANCELLED;
}
if (ob->type == OB_MESH && do_merge_customdata &&
ELEM(mti->type, ModifierTypeType::Constructive, ModifierTypeType::Nonconstructive))
{
BKE_mesh_merge_customdata_for_apply_modifier((Mesh *)ob->data);
}
DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY);
DEG_relations_tag_update(bmain);
WM_event_add_notifier(C, NC_OBJECT | ND_MODIFIER, ob);
if (do_report) {
/* 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. */
@ -1839,6 +1918,7 @@ void OBJECT_OT_modifier_apply(wmOperatorType *ot)
"Make Data Single User",
"Make the object's data single user if needed");
RNA_def_property_flag(prop, (PropertyFlag)(PROP_HIDDEN | PROP_SKIP_SAVE));
modifier_register_use_selected_objects_prop(ot);
}
/** \} */
@ -1958,16 +2038,29 @@ static int modifier_copy_exec(bContext *C, wmOperator *op)
{
Main *bmain = CTX_data_main(C);
Scene *scene = CTX_data_scene(C);
Object *ob = context_active_object(C);
ModifierData *md = edit_modifier_property_get(op, ob, 0);
char name[MAX_NAME];
RNA_string_get(op->ptr, "modifier", name);
if (!md || !modifier_copy(op->reports, bmain, scene, ob, md)) {
return OPERATOR_CANCELLED;
bool changed = false;
for (const PointerRNA &ptr : modifier_get_edit_objects(*C, *op)) {
Object *ob = static_cast<Object *>(ptr.data);
ModifierData *md = BKE_modifiers_findby_name(ob, name);
if (!md) {
continue;
}
if (!modifier_copy(op->reports, bmain, scene, ob, md)) {
continue;
}
changed = true;
DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY);
DEG_relations_tag_update(bmain);
WM_event_add_notifier(C, NC_OBJECT | ND_MODIFIER, ob);
}
DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY);
DEG_relations_tag_update(bmain);
WM_event_add_notifier(C, NC_OBJECT | ND_MODIFIER, ob);
if (!changed) {
return OPERATOR_CANCELLED;
}
return OPERATOR_FINISHED;
}
@ -1994,6 +2087,7 @@ void OBJECT_OT_modifier_copy(wmOperatorType *ot)
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_INTERNAL;
edit_modifier_properties(ot);
modifier_register_use_selected_objects_prop(ot);
}
/** \} */

View File

@ -265,8 +265,7 @@ static void modifier_ops_extra_draw(bContext *C, uiLayout *layout, void *md_v)
uiItemS(layout);
/* Move to first. */
row = uiLayoutColumn(layout, false);
uiItemFullO(row,
uiItemFullO(layout,
"OBJECT_OT_modifier_move_to_index",
IFACE_("Move to First"),
ICON_TRIA_UP,
@ -275,13 +274,9 @@ static void modifier_ops_extra_draw(bContext *C, uiLayout *layout, void *md_v)
UI_ITEM_NONE,
&op_ptr);
RNA_int_set(&op_ptr, "index", 0);
if (!md->prev) {
uiLayoutSetEnabled(row, false);
}
/* Move to last. */
row = uiLayoutColumn(layout, false);
uiItemFullO(row,
uiItemFullO(layout,
"OBJECT_OT_modifier_move_to_index",
IFACE_("Move to Last"),
ICON_TRIA_DOWN,
@ -290,9 +285,6 @@ static void modifier_ops_extra_draw(bContext *C, uiLayout *layout, void *md_v)
UI_ITEM_NONE,
&op_ptr);
RNA_int_set(&op_ptr, "index", BLI_listbase_count(&ob->modifiers) - 1);
if (!md->next) {
uiLayoutSetEnabled(row, false);
}
if (md->type == eModifierType_Nodes) {
uiItemS(layout);