mirror of
https://github.com/blender/blender
synced 2026-09-29 04:37:17 +03:00
Fix #158280: Node Tools: Menu inputs broken since recent refactor
Caused by 1561c1ea4a
There were a few problems. First, the enum items generation callback
didn't get the pointer I had expected when writing this. Instead, generate
them when registering the operator. Second, the asset meta-data stored
things in a confusing way, resulting in the Python-accessible enum items
using numbers as the identifiers instead of the names like similar properties
on modifiers. Add a test to keep this all working in the future.
This commit is contained in:
parent
8f7d756880
commit
b68a2f3921
3 changed files with 101 additions and 37 deletions
|
|
@ -10,6 +10,7 @@
|
|||
|
||||
#include "MEM_guardedalloc.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
|
@ -2231,14 +2232,21 @@ IDProperty *node_create_asset_meta_data_properties(const bNodeTree &node_tree)
|
|||
}
|
||||
case SOCK_MENU: {
|
||||
const auto &value = node_interface::get_socket_data_as<bNodeSocketValueMenu>(*socket);
|
||||
IDP_AddToGroup(input.get(), idprop::create("default_value", value.value).release());
|
||||
if (value.enum_items) {
|
||||
if (std::ranges::any_of(value.enum_items->items, [&](const RuntimeNodeEnumItem &item) {
|
||||
return item.identifier == value.value;
|
||||
}))
|
||||
{
|
||||
/* Only add the default value property if it's contained in the enum items. */
|
||||
IDP_AddToGroup(input.get(), idprop::create("default_value", value.value).release());
|
||||
}
|
||||
auto items = idprop::create_group("items");
|
||||
for (const RuntimeNodeEnumItem &enum_item : value.enum_items->items) {
|
||||
auto item = idprop::create_group(std::to_string(enum_item.identifier));
|
||||
IDP_AddToGroup(item.get(), idprop::create("name", enum_item.name).release());
|
||||
auto item = idprop::create_group(enum_item.name);
|
||||
IDP_AddToGroup(item.get(), idprop::create("value", enum_item.identifier).release());
|
||||
IDP_AddToGroup(item.get(),
|
||||
idprop::create("description", enum_item.description).release());
|
||||
IDP_AddToGroup(items.get(), item.release());
|
||||
}
|
||||
IDP_AddToGroup(input.get(), items.release());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -127,6 +127,7 @@ struct OperatorTypeData : public wmOperatorType::TypeData {
|
|||
|
||||
std::unique_ptr<IDProperty, bke::idprop::IDPropertyDeleter> asset_meta_data_properties;
|
||||
Vector<StructRNA *> generated_structs;
|
||||
Vector<Array<EnumPropertyItem, 0>> enum_item_storage;
|
||||
|
||||
struct LocalRef {
|
||||
uint32_t session_uid;
|
||||
|
|
@ -1115,35 +1116,31 @@ static bool run_node_group_poll(bContext *C, wmOperatorType *ot)
|
|||
return true;
|
||||
}
|
||||
|
||||
static const EnumPropertyItem *enum_input_items_fn(bContext * /*C*/,
|
||||
PointerRNA *ptr,
|
||||
PropertyRNA *prop,
|
||||
bool *r_free)
|
||||
static Array<EnumPropertyItem, 0> get_input_enum_items(const IDProperty &input_idprop)
|
||||
{
|
||||
const wmOperator *op = ptr->data_as<wmOperator>();
|
||||
const OperatorTypeData &type_data = *static_cast<const OperatorTypeData *>(op->customdata);
|
||||
const IDProperty &inputs_props = *IDP_GetPropertyFromGroup(
|
||||
type_data.asset_meta_data_properties.get(), "inputs");
|
||||
const IDProperty &input_idprop = *IDP_GetPropertyFromGroup(&inputs_props,
|
||||
RNA_property_identifier(prop));
|
||||
|
||||
const IDProperty *items_idprop = IDP_GetPropertyFromGroup(&input_idprop, "items");
|
||||
if (!items_idprop || items_idprop->type != IDP_GROUP) {
|
||||
return rna_enum_dummy_NULL_items;
|
||||
return {rna_enum_dummy_NULL_items[0]};
|
||||
}
|
||||
|
||||
int totitem = 0;
|
||||
EnumPropertyItem *items = nullptr;
|
||||
for (IDProperty &item_idprop : items_idprop->data.group) {
|
||||
EnumPropertyItem item;
|
||||
item.identifier = item_idprop.name;
|
||||
item.name = IDP_group_lookup_string(item_idprop, "name").value_or("").c_str();
|
||||
item.description = IDP_group_lookup_string(item_idprop, "description").value_or("").c_str();
|
||||
item.value = std::stoi(item_idprop.name);
|
||||
RNA_enum_item_add(&items, &totitem, &item);
|
||||
if (!items_idprop->data.children_map || items_idprop->data.children_map->children.is_empty()) {
|
||||
return {rna_enum_dummy_NULL_items[0]};
|
||||
}
|
||||
|
||||
*r_free = true;
|
||||
const int items_num = items_idprop->data.children_map->children.size();
|
||||
Array<EnumPropertyItem, 0> items(items_num + 1);
|
||||
for (const auto [i, item_idprop] : items_idprop->data.group.enumerate()) {
|
||||
items[i] = EnumPropertyItem{
|
||||
.value = IDP_group_lookup_int(item_idprop, "value").value_or(0),
|
||||
.identifier = item_idprop.name,
|
||||
.icon = ICON_NONE,
|
||||
.name = item_idprop.name,
|
||||
.description = IDP_group_lookup_string(item_idprop, "description").value_or("").c_str(),
|
||||
};
|
||||
}
|
||||
|
||||
items.last() = {0, nullptr, 0, nullptr, nullptr};
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
|
|
@ -1188,9 +1185,8 @@ static void make_common_value_props(StructRNA &srna)
|
|||
}
|
||||
|
||||
static StructRNA *get_input_socket_struct_rna(IDProperty &input_idprop,
|
||||
Vector<StructRNA *> &r_generated)
|
||||
OperatorTypeData &type_data)
|
||||
{
|
||||
|
||||
const StringRefNull identifier = input_idprop.name;
|
||||
const std::optional<int> type = IDP_group_lookup_int(input_idprop, "type");
|
||||
if (!type) {
|
||||
|
|
@ -1199,7 +1195,7 @@ static StructRNA *get_input_socket_struct_rna(IDProperty &input_idprop,
|
|||
StructRNA *srna = RNA_def_struct_ptr(
|
||||
&RNA_blender_rna_get(), identifier.c_str(), RNA_PropertyGroup);
|
||||
BLI_assert(!RNA_struct_in_public_namespace(srna));
|
||||
r_generated.append(srna);
|
||||
type_data.generated_structs.append(srna);
|
||||
// RNA_def_struct_path_func_runtime(srna, rna_NodesModifierPropertyInput_path);
|
||||
const StringRefNull name = IDP_group_lookup_string(input_idprop, "name").value_or(identifier);
|
||||
const StringRefNull description =
|
||||
|
|
@ -1333,9 +1329,21 @@ static StructRNA *get_input_socket_struct_rna(IDProperty &input_idprop,
|
|||
break;
|
||||
}
|
||||
case SOCK_MENU: {
|
||||
PropertyRNA *prop = RNA_def_enum(
|
||||
srna, "value", rna_enum_dummy_NULL_items, 0, name.c_str(), description.c_str());
|
||||
RNA_def_enum_funcs(prop, enum_input_items_fn);
|
||||
type_data.enum_item_storage.append_as(get_input_enum_items(input_idprop));
|
||||
int default_value = IDP_group_lookup_int(input_idprop, "default_value").value_or(0);
|
||||
if (std::ranges::none_of(
|
||||
type_data.enum_item_storage.last(),
|
||||
[&](const EnumPropertyItem &item) { return item.value == default_value; }))
|
||||
{
|
||||
/* Default value must be used by one of the enum items. */
|
||||
default_value = 0;
|
||||
}
|
||||
RNA_def_enum(srna,
|
||||
"value",
|
||||
type_data.enum_item_storage.last().data(),
|
||||
IDP_group_lookup_int(input_idprop, "default_value").value_or(0),
|
||||
name.c_str(),
|
||||
description.c_str());
|
||||
make_common_value_props(*srna);
|
||||
break;
|
||||
}
|
||||
|
|
@ -1346,13 +1354,12 @@ static StructRNA *get_input_socket_struct_rna(IDProperty &input_idprop,
|
|||
return srna;
|
||||
}
|
||||
|
||||
static StructRNA *create_inputs_srna(const IDProperty &properties,
|
||||
Vector<StructRNA *> &r_generated)
|
||||
static StructRNA *create_inputs_srna(const IDProperty &properties, OperatorTypeData &type_data)
|
||||
{
|
||||
StructRNA *srna = RNA_def_struct_ptr(
|
||||
&RNA_blender_rna_get(), "GeometryNodesInterfaceInputs", RNA_PropertyGroup);
|
||||
BLI_assert(!RNA_struct_in_public_namespace(srna));
|
||||
r_generated.append(srna);
|
||||
type_data.generated_structs.append(srna);
|
||||
|
||||
const IDProperty &inputs_props = *IDP_GetPropertyFromGroup(&properties, "inputs");
|
||||
|
||||
|
|
@ -1360,7 +1367,7 @@ static StructRNA *create_inputs_srna(const IDProperty &properties,
|
|||
if (input_idprop.type != IDP_GROUP) {
|
||||
continue;
|
||||
}
|
||||
StructRNA *input_srna = get_input_socket_struct_rna(input_idprop, r_generated);
|
||||
StructRNA *input_srna = get_input_socket_struct_rna(input_idprop, type_data);
|
||||
if (!input_srna) {
|
||||
continue;
|
||||
}
|
||||
|
|
@ -1413,8 +1420,7 @@ static void register_node_tool(wmOperatorType *ot,
|
|||
ot->flag |= OPTYPE_DEPENDS_ON_CURSOR;
|
||||
}
|
||||
|
||||
StructRNA *inputs_srna = create_inputs_srna(*type_data.asset_meta_data_properties,
|
||||
type_data.generated_structs);
|
||||
StructRNA *inputs_srna = create_inputs_srna(*type_data.asset_meta_data_properties, type_data);
|
||||
RNA_def_pointer_runtime(ot->srna, "inputs", inputs_srna, "Inputs", "Settings for input sockets");
|
||||
if (StructRNA *panels_srna = create_panels_srna(*type_data.asset_meta_data_properties,
|
||||
type_data.generated_structs))
|
||||
|
|
|
|||
|
|
@ -115,6 +115,56 @@ class TestNodeTool(unittest.TestCase):
|
|||
self.assertIn("attr_with_default", attribute_names)
|
||||
self.assertIn("attr_without_default", attribute_names)
|
||||
|
||||
def test_menu_input(self):
|
||||
from bpy.types import WindowManager
|
||||
|
||||
bpy.ops.mesh.primitive_cube_add()
|
||||
cube = bpy.context.active_object
|
||||
|
||||
tree = create_object_mode_mesh_tool_tree(
|
||||
"TestNodeToolMenu", "geometry.test_node_tool_menu"
|
||||
)
|
||||
tree.interface.new_socket("Geometry", in_out='INPUT', socket_type='NodeSocketGeometry')
|
||||
mode_socket = tree.interface.new_socket(
|
||||
"Mode", in_out='INPUT', socket_type='NodeSocketMenu')
|
||||
tree.interface.new_socket("Geometry", in_out='OUTPUT', socket_type='NodeSocketGeometry')
|
||||
|
||||
group_input = tree.nodes.new("NodeGroupInput")
|
||||
group_output = tree.nodes.new("NodeGroupOutput")
|
||||
menu_switch = tree.nodes.new("GeometryNodeMenuSwitch")
|
||||
menu_switch.data_type = 'VECTOR'
|
||||
transform = tree.nodes.new("GeometryNodeTransform")
|
||||
|
||||
# The menu switch starts with default items "A" and "B". Rename and add a third so the items become X, Y, Z.
|
||||
menu_switch.enum_items[0].name = "X"
|
||||
menu_switch.enum_items[1].name = "Y"
|
||||
menu_switch.enum_items.new("Z")
|
||||
|
||||
menu_switch.inputs["X"].default_value = (10.0, 0.0, 0.0)
|
||||
menu_switch.inputs["Y"].default_value = (0.0, 10.0, 0.0)
|
||||
menu_switch.inputs["Z"].default_value = (0.0, 0.0, 10.0)
|
||||
|
||||
tree.links.new(group_input.outputs["Geometry"], transform.inputs["Geometry"])
|
||||
tree.links.new(group_input.outputs["Mode"], menu_switch.inputs["Menu"])
|
||||
tree.links.new(menu_switch.outputs["Output"], transform.inputs["Translation"])
|
||||
tree.links.new(transform.outputs["Geometry"], group_output.inputs["Geometry"])
|
||||
|
||||
WindowManager.register_node_group_operators()
|
||||
|
||||
bpy.ops.geometry.test_node_tool_menu(
|
||||
'EXEC_DEFAULT',
|
||||
inputs={mode_socket.identifier: {"value": "Y"}},
|
||||
)
|
||||
|
||||
verts = cube.data.vertices
|
||||
n = len(verts)
|
||||
center_x = sum(v.co.x for v in verts) / n
|
||||
center_y = sum(v.co.y for v in verts) / n
|
||||
center_z = sum(v.co.z for v in verts) / n
|
||||
self.assertAlmostEqual(center_x, 0.0, places=4)
|
||||
self.assertAlmostEqual(center_y, 10.0, places=4)
|
||||
self.assertAlmostEqual(center_z, 0.0, places=4)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue