mirror of
https://github.com/blender/blender
synced 2026-09-29 04:37:17 +03:00
Nodes: Change group operator test for pass-through node groups
The group operator test has to distinguish internal from external nodes. The operators are only applied to internal nodes. This was done previously by placing external nodes outside of the frames which identify the test cases. However, the pass-through operator test does not work this way, because after ungrouping there are no internal sockets any more, and the pass-through connection between external sockets is never actually tested. Change the approach to identifying external sockets by labeling: - All nodes for a test case must be included in the frame node. - Nodes that should not be grouped must be labeled as "external". Pull Request: https://projects.blender.org/blender/blender/pulls/152762
This commit is contained in:
parent
1e05d96459
commit
30503d8fd1
2 changed files with 58 additions and 55 deletions
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:540cb913dbc58021b00d10972b7efa0717b178fbcf1d6f9645997c9304dc55b5
|
||||
size 248852
|
||||
oid sha256:189fba9c2e5f69fad6b1d3696994727c659812472880f61459a613ffcbfe942f
|
||||
size 249097
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import bpy
|
|||
|
||||
# Test cases listed below must have a test data in the "Tests" node group in the main test file.
|
||||
# Each test case should have a frame node whose content is used as input for operators.
|
||||
# Nodes labeled "external" are excluded from the grouping operator to test external links.
|
||||
#
|
||||
# A single test case can be tested using the '--subtest <NAME>' argument:
|
||||
#
|
||||
|
|
@ -73,22 +74,24 @@ class NodeMapping:
|
|||
self.tree_map[test_tree] = expected_tree
|
||||
|
||||
def add_node(self, test_node, expected_node):
|
||||
assert self.node_map.get(test_node, expected_node) == expected_node
|
||||
self.node_map[test_node] = expected_node
|
||||
# Add all sockets of mapped nodes to their own dictionary, assuming the socket order is the same.
|
||||
for test_socket, expected_socket in zip(test_node.inputs, expected_node.inputs):
|
||||
assert self.socket_map.get(test_socket, expected_socket) == expected_socket
|
||||
self.socket_map[test_socket] = expected_socket
|
||||
for test_socket, expected_socket in zip(test_node.outputs, expected_node.outputs):
|
||||
assert self.socket_map.get(test_socket, expected_socket) == expected_socket
|
||||
self.socket_map[test_socket] = expected_socket
|
||||
|
||||
def add_nodes_by_name(self, test_nodes, expected_nodes):
|
||||
expected_nodes_map = {node.name: node for node in expected_nodes}
|
||||
expected_node_by_name = {node.name: node for node in expected_nodes}
|
||||
for test_node in test_nodes:
|
||||
# Raises key error if not all test nodes can be mapped.
|
||||
expected_node = expected_nodes_map.pop(test_node.name)
|
||||
self.add_node(test_node, expected_node)
|
||||
expected_node = expected_node_by_name.pop(test_node.name)
|
||||
self.add_node(test_node, expected_node)
|
||||
# Should map all expected nodes.
|
||||
assert not expected_nodes_map
|
||||
assert not expected_node_by_name
|
||||
|
||||
|
||||
def open_test_file():
|
||||
|
|
@ -171,8 +174,12 @@ def find_test_frame(tree, test_name):
|
|||
|
||||
|
||||
# Find nodes inside a top level frame of the given name.
|
||||
def find_expected_nodes(tree, test_name):
|
||||
test_nodes = list()
|
||||
# Returns two lists: internal_nodes, external_nodes
|
||||
# Only internal nodes should be included in the grouping operation.
|
||||
# External nodes are identified by "external" prefix.
|
||||
def find_test_nodes(tree, test_name):
|
||||
internal_nodes = list()
|
||||
external_nodes = list()
|
||||
for node in tree.nodes:
|
||||
top_parent = node.parent
|
||||
while top_parent:
|
||||
|
|
@ -181,24 +188,28 @@ def find_expected_nodes(tree, test_name):
|
|||
top_parent = top_parent.parent
|
||||
|
||||
if top_parent and isinstance(top_parent, bpy.types.NodeFrame) and top_parent.label == test_name:
|
||||
test_nodes.append(node)
|
||||
return test_nodes
|
||||
if node.label.lower() == "external":
|
||||
external_nodes.append(node)
|
||||
else:
|
||||
internal_nodes.append(node)
|
||||
return internal_nodes, external_nodes
|
||||
|
||||
|
||||
# NOOP case when the test starts out with a node group.
|
||||
def execute_node_group_noop(test_case, test_tree, expected_tree=None):
|
||||
test_nodes = find_expected_nodes(test_tree, test_case)
|
||||
assert len(test_nodes) == 1
|
||||
group_node = test_nodes[0]
|
||||
test_nodes_internal, test_nodes_external = find_test_nodes(test_tree, test_case)
|
||||
assert len(test_nodes_internal) == 1
|
||||
group_node = test_nodes_internal[0]
|
||||
|
||||
if expected_tree:
|
||||
# Map resulting nodes to expected nodes.
|
||||
expected_nodes = find_expected_nodes(expected_tree, test_case)
|
||||
assert len(expected_nodes) == 1
|
||||
expected_node = expected_nodes[0]
|
||||
expected_nodes_internal, expected_nodes_external = find_test_nodes(expected_tree, test_case)
|
||||
assert len(expected_nodes_internal) == 1
|
||||
expected_node = expected_nodes_internal[0]
|
||||
mapping = NodeMapping()
|
||||
mapping.add_tree(group_node.node_tree, expected_node.node_tree)
|
||||
mapping.add_node(group_node, expected_node)
|
||||
mapping.add_nodes_by_name(test_nodes_external, expected_nodes_external)
|
||||
mapping.add_nodes_by_name(group_node.node_tree.nodes, expected_node.node_tree.nodes)
|
||||
return mapping
|
||||
|
||||
|
|
@ -209,9 +220,9 @@ def execute_make_group(test_case, test_tree, expected_tree=None):
|
|||
if 'NODE_GROUP' in test_options(test_case):
|
||||
return execute_node_group_noop(test_case, test_tree, expected_tree)
|
||||
|
||||
test_nodes = find_expected_nodes(test_tree, test_case)
|
||||
test_nodes_internal, test_nodes_external = find_test_nodes(test_tree, test_case)
|
||||
|
||||
with node_editor_context_override(bpy.context, test_tree, selected_nodes=test_nodes):
|
||||
with node_editor_context_override(bpy.context, test_tree, selected_nodes=test_nodes_internal):
|
||||
bpy.ops.node.group_make()
|
||||
group_node = test_tree.nodes.active
|
||||
# Re-attach to the parent frame to identify the operator result.
|
||||
|
|
@ -219,12 +230,13 @@ def execute_make_group(test_case, test_tree, expected_tree=None):
|
|||
|
||||
if expected_tree:
|
||||
# Map resulting nodes to expected nodes.
|
||||
expected_nodes = find_expected_nodes(expected_tree, test_case)
|
||||
assert len(expected_nodes) == 1
|
||||
expected_node = expected_nodes[0]
|
||||
expected_nodes_internal, expected_nodes_external = find_test_nodes(expected_tree, test_case)
|
||||
assert len(expected_nodes_internal) == 1
|
||||
expected_node = expected_nodes_internal[0]
|
||||
mapping = NodeMapping()
|
||||
mapping.add_tree(group_node.node_tree, expected_node.node_tree)
|
||||
mapping.add_node(group_node, expected_node)
|
||||
mapping.add_nodes_by_name(test_nodes_external, expected_nodes_external)
|
||||
mapping.add_nodes_by_name(group_node.node_tree.nodes, expected_node.node_tree.nodes)
|
||||
return mapping
|
||||
|
||||
|
|
@ -235,15 +247,15 @@ def execute_group_insert(test_case, test_tree, expected_tree=None):
|
|||
if 'NODE_GROUP' in test_options(test_case):
|
||||
return execute_node_group_noop(test_case, test_tree, expected_tree)
|
||||
|
||||
test_nodes = find_expected_nodes(test_tree, test_case)
|
||||
centroid = node_centroid(test_nodes)
|
||||
test_nodes_internal, test_nodes_external = find_test_nodes(test_tree, test_case)
|
||||
centroid = node_centroid(test_nodes_internal)
|
||||
|
||||
# Make empty node group.
|
||||
group_tree = bpy.data.node_groups.new(f"{test_case}_GroupInsert", 'GeometryNodeTree')
|
||||
# Copy nodes into the tree to force deduplication testing.
|
||||
# Note: this is not ideal since it depends on yet another operator,
|
||||
# but there is no way to retain the original nodes when inserting to enforce duplicate names.
|
||||
with node_editor_context_override(bpy.context, test_tree, selected_nodes=test_nodes):
|
||||
with node_editor_context_override(bpy.context, test_tree, selected_nodes=test_nodes_internal):
|
||||
bpy.ops.node.clipboard_copy()
|
||||
with node_editor_context_override(bpy.context, group_tree):
|
||||
bpy.ops.node.clipboard_paste()
|
||||
|
|
@ -260,49 +272,51 @@ def execute_group_insert(test_case, test_tree, expected_tree=None):
|
|||
group_node.parent = find_test_frame(test_tree, test_case)
|
||||
group_node.location = centroid
|
||||
# Insert nodes into the group.
|
||||
with node_editor_context_override(bpy.context, test_tree, selected_nodes=test_nodes + [group_node], active_node=group_node):
|
||||
with node_editor_context_override(bpy.context, test_tree, selected_nodes=test_nodes_internal + [group_node], active_node=group_node):
|
||||
bpy.ops.node.group_insert()
|
||||
|
||||
if expected_tree:
|
||||
# Map resulting nodes to expected nodes.
|
||||
expected_nodes = find_expected_nodes(expected_tree, test_case)
|
||||
assert len(expected_nodes) == 1
|
||||
expected_node = expected_nodes[0]
|
||||
expected_nodes_internal, expected_nodes_external = find_test_nodes(expected_tree, test_case)
|
||||
assert len(expected_nodes_internal) == 1
|
||||
expected_node = expected_nodes_internal[0]
|
||||
mapping = NodeMapping()
|
||||
mapping.add_tree(group_node.node_tree, expected_node.node_tree)
|
||||
mapping.add_node(group_node, expected_node)
|
||||
mapping.add_nodes_by_name(test_nodes_external, expected_nodes_external)
|
||||
mapping.add_nodes_by_name(group_node.node_tree.nodes, expected_node.node_tree.nodes)
|
||||
return mapping
|
||||
|
||||
|
||||
# Run the 'node.ungroup' operator on test nodes.
|
||||
def execute_ungroup(test_case, test_tree, expected_tree=None):
|
||||
test_nodes = find_expected_nodes(test_tree, test_case)
|
||||
test_nodes_internal, test_nodes_external = find_test_nodes(test_tree, test_case)
|
||||
|
||||
with node_editor_context_override(bpy.context, test_tree, selected_nodes=test_nodes):
|
||||
with node_editor_context_override(bpy.context, test_tree, selected_nodes=test_nodes_internal):
|
||||
bpy.ops.node.group_ungroup()
|
||||
internal_nodes = [node for node in test_tree.nodes if node.select]
|
||||
ungrouped_nodes = [node for node in test_tree.nodes if node.select]
|
||||
# Re-attach to the parent frame to identify the operator result.
|
||||
for node in internal_nodes:
|
||||
for node in ungrouped_nodes:
|
||||
if node.parent is None:
|
||||
node.parent = find_test_frame(test_tree, test_case)
|
||||
|
||||
if expected_tree:
|
||||
# Map resulting nodes to expected nodes.
|
||||
expected_nodes = find_expected_nodes(expected_tree, test_case)
|
||||
expected_nodes_internal, expected_nodes_external = find_test_nodes(expected_tree, test_case)
|
||||
mapping = NodeMapping()
|
||||
mapping.add_nodes_by_name(internal_nodes, expected_nodes)
|
||||
mapping.add_nodes_by_name(ungrouped_nodes, expected_nodes_internal)
|
||||
mapping.add_nodes_by_name(test_nodes_external, expected_nodes_external)
|
||||
return mapping
|
||||
|
||||
|
||||
# Run the 'node.group_separate' operator on test nodes.
|
||||
# type can be 'COPY' or 'MOVE'.
|
||||
def execute_group_separate(type, test_case, test_tree, expected_tree=None):
|
||||
test_nodes = find_expected_nodes(test_tree, test_case)
|
||||
test_nodes_internal, test_nodes_external = find_test_nodes(test_tree, test_case)
|
||||
|
||||
# Test nodes should be node groups
|
||||
assert len(test_nodes) == 1
|
||||
group_node = test_nodes[0]
|
||||
assert len(test_nodes_internal) == 1
|
||||
group_node = test_nodes_internal[0]
|
||||
assert isinstance(group_node, bpy.types.GeometryNodeGroup)
|
||||
|
||||
# Ensure single-user node group, so that moving nodes out does not modify a shared tree.
|
||||
|
|
@ -326,10 +340,11 @@ def execute_group_separate(type, test_case, test_tree, expected_tree=None):
|
|||
|
||||
if expected_tree:
|
||||
# Map resulting nodes to expected nodes.
|
||||
result_nodes = find_expected_nodes(test_tree, test_case)
|
||||
expected_nodes = find_expected_nodes(expected_tree, test_case)
|
||||
result_nodes_internal, _ = find_test_nodes(test_tree, test_case)
|
||||
expected_nodes_internal, expected_nodes_external = find_test_nodes(expected_tree, test_case)
|
||||
mapping = NodeMapping()
|
||||
mapping.add_nodes_by_name(result_nodes, expected_nodes)
|
||||
mapping.add_nodes_by_name(result_nodes_internal, expected_nodes_internal)
|
||||
mapping.add_nodes_by_name(test_nodes_external, expected_nodes_external)
|
||||
return mapping
|
||||
|
||||
|
||||
|
|
@ -397,15 +412,8 @@ class AbstractNodeCopyOperatorTest(unittest.TestCase):
|
|||
|
||||
for test_link in test_socket.links:
|
||||
test_target = link_target(test_link)
|
||||
# If there is no entry in the mapping for the connected test socket then the link is ignored.
|
||||
# External connections are not usually added to the map to keep test cases simple.
|
||||
# The total number of links must still match.
|
||||
expected_target = (
|
||||
mapping.node_map.get(
|
||||
test_target[0], None), mapping.socket_map.get(
|
||||
test_target[1], None))
|
||||
if expected_target[0]:
|
||||
self.assertIn(expected_target, expected_targets)
|
||||
expected_target = (mapping.node_map[test_target[0]], mapping.socket_map[test_target[1]])
|
||||
self.assertIn(expected_target, expected_targets)
|
||||
|
||||
# Validate a node against the expected data using the node map.
|
||||
def compare_node(self, test_node, expected_node, mapping):
|
||||
|
|
@ -474,14 +482,9 @@ class AbstractNodeCopyOperatorTest(unittest.TestCase):
|
|||
self.assertEqual(test_item.default_closed, expected_item.default_closed)
|
||||
|
||||
def compare(self, mapping):
|
||||
# New sockets may be added to this dictionary while comparing nodes!
|
||||
# Make a copy of the original nodes that should be compared.
|
||||
orig_test_nodes = list(mapping.node_map.keys())
|
||||
orig_expected_nodes = list(mapping.node_map.values())
|
||||
|
||||
for test_tree, expected_tree in mapping.tree_map.items():
|
||||
self.compare_tree_interface(test_tree, expected_tree)
|
||||
for test_node, expected_node in zip(orig_test_nodes, orig_expected_nodes):
|
||||
for test_node, expected_node in mapping.node_map.items():
|
||||
self.compare_node(test_node, expected_node, mapping)
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue