Tests: Add test for node tools with data-block inputs

After the RNA changes for Geometry Nodes, we are finally very close
to being able to pass data as inputs to node tools, which allows
creating a test that creates and runs a node tool with a data-block
input. Historically this has broken a few times, so it makes sense for
a first test of this system.

A few tweaks are necessary to get this to work. First, because the
registration of node group operators is currently called by the notifier
system, it doesn't happen in background mode. I added a WindowManager
RNA function to trigger this manually. Second, passing a dictionary to
define the inputs of the operator is necessary because of the rather
complex RNA type hierarchy, however this dictionary conversion wasn't
handled recursively in the operator calling code in bpy_rna.cc. This is
corrected by simply also handling the PropertyGroup base type used for
these node group input properties.

Pull Request: https://projects.blender.org/blender/blender/pulls/157078
This commit is contained in:
Hans Goudey 2026-04-11 15:56:12 +02:00 • committed by Hans Goudey
parent 8deaa11ab0
commit 15b0a2a574
4 changed files with 97 additions and 3 deletions

View file

@ -537,6 +537,11 @@ if(TEST_SRC_DIR_EXISTS)
object_api
--python ${TEST_PYTHON_DIR}/bl_object.py
)
add_blender_test(
node_tools
--python ${TEST_PYTHON_DIR}/bl_node_tool.py
)
endif()
add_blender_test(

View file

@ -0,0 +1,76 @@
# SPDX-FileCopyrightText: 2026 Blender Authors
#
# SPDX-License-Identifier: GPL-2.0-or-later
import unittest
import bpy
def create_join_geometry_tool(tool_idname):
"""
Create a geometry node group set up as an object-mode tool.
Returns the node tree and the identifier of the Object input socket.
"""
tree = bpy.data.node_groups.new("TestNodeTool", "GeometryNodeTree")
tree.is_tool = True
tree.is_mode_object = True
tree.is_type_mesh = True
tree.node_tool_idname = tool_idname
tree.interface.new_socket("Geometry", in_out='INPUT', socket_type='NodeSocketGeometry')
obj_socket = tree.interface.new_socket("Object", in_out='INPUT', socket_type='NodeSocketObject')
tree.interface.new_socket("Geometry", in_out='OUTPUT', socket_type='NodeSocketGeometry')
group_input = tree.nodes.new("NodeGroupInput")
group_output = tree.nodes.new("NodeGroupOutput")
obj_info = tree.nodes.new("GeometryNodeObjectInfo")
join_geo = tree.nodes.new("GeometryNodeJoinGeometry")
tree.links.new(group_input.outputs["Geometry"], join_geo.inputs["Geometry"])
tree.links.new(group_input.outputs["Object"], obj_info.inputs["Object"])
tree.links.new(obj_info.outputs["Geometry"], join_geo.inputs["Geometry"])
tree.links.new(join_geo.outputs["Geometry"], group_output.inputs["Geometry"])
return tree, obj_socket.identifier
class TestNodeTool(unittest.TestCase):
def setUp(self):
bpy.ops.wm.read_factory_settings(use_empty=True)
def test_join_geometry_with_object_input(self):
from bpy.types import WindowManager
# Add a cube (the active object the tool will run on).
bpy.ops.mesh.primitive_cube_add()
cube = bpy.context.active_object
cube_vertex_count = len(cube.data.vertices)
# Add a monkey/Suzanne (the tool's object input, joined into the cube).
bpy.ops.mesh.primitive_monkey_add()
monkey = bpy.context.active_object
monkey_vertex_count = len(monkey.data.vertices)
# Make the cube the active, selected object.
bpy.context.view_layer.objects.active = cube
for obj in bpy.context.scene.objects:
obj.select_set(obj == cube)
_tree, obj_input_identifier = create_join_geometry_tool("geometry.test_node_tool_join_geometry")
WindowManager.register_node_group_operators()
bpy.ops.geometry.test_node_tool_join_geometry(
'EXEC_DEFAULT',
inputs={obj_input_identifier: {"value": monkey.name}},
)
# The cube's mesh should now contain geometry from both the cube and the
# monkey, so the vertex count should be the sum of both.
expected_vertex_count = cube_vertex_count + monkey_vertex_count
self.assertEqual(len(cube.data.vertices), expected_vertex_count)
if __name__ == "__main__":
import sys
sys.argv = [__file__] + (sys.argv[sys.argv.index("--") + 1:] if "--" in sys.argv else [])
unittest.main()