Fix #160692: Auto-removal of viewer socket crashes group operator

The `Make Group` operator for nodes expects that sockets remain valid
when removing a link to the node (to be replaced by a new external
link). This is not the case for highly dynamic nodes like viewer nodes.
A viewer socket is removed as soon as it is un-linked.

Make the group operator more robust by checking if sockets exist and
ignoring any link to group nodes when the socket disappears.
Ideally the viewer node would remain connected through these operations,
but that requires some detail special case for viewer behavior or some
way to ensure a viewer node copy has the same sockets as the original.

A regression test is also added for this case.

The `bl_node_copy_operators.py` has been modified to use the
`BLENDER_TEST_UPDATE` environment variable for updating the expected
test data, instead of the previous `--generate` script parameter.
This is consistent with other test scripts using the same mechanism.

A bug in `nearest_node_grid_coord` was fixed which created `nan` node
locations when running in `--background` mode due to zero UI scale.
Introduced by #157834.

Pull Request: https://projects.blender.org/blender/blender/pulls/160718
This commit is contained in:
Lukas Tönne 2026-06-26 14:59:43 +02:00
parent ae43e88abc
commit f7f4071e2e
6 changed files with 83 additions and 74 deletions

View file

@ -10,6 +10,9 @@ from mathutils import Vector
import bpy
# To update the test data, set the `BLENDER_TEST_UPDATE` environment variable:
# `BLENDER_TEST_UPDATE=1 ctest -R bl_node_copy_operators`
#
# 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.
@ -27,13 +30,6 @@ import bpy
# - bpy.data.node_groups["ExpectedUngroup"]: Result of node.ungroup operator.
# - bpy.data.node_groups["ExpectedGroupSeparateCopy"]: Result of node.group_separate operator with type='COPY'.
# - bpy.data.node_groups["ExpectedGroupSeparateMove"]: Result of node.group_separate operator with type='MOVE'.
#
# The script can be invoked with an additional argument '--generate' to update the ground truth test data.
# Nodes in the "Expected***" node trees are replaced with the result of operators applied to the "Tests" node tree.
#
# ./bin/blender --factory-startup --python <SOURCEPATH>/tests/python/bl_node_copy_operators.py
# --
# --testdir <SOURCEPATH>/tests/files/node_group --generate
args = None
testfile = "node_copy_operators.blend"
@ -65,6 +61,7 @@ all_test_cases = [
('test_insert_group_with_sockets', {'GROUP_INSERT'}),
('test_ungroup_multiple', {'NODE_GROUP'}),
('test_ungroup_proxy_nodes', {'NODE_GROUP'}),
('test_viewer_node', {}),
]
@ -404,7 +401,6 @@ class AbstractNodeCopyOperatorTest(unittest.TestCase):
self.assertTrue(args.testdir.exists(),
'Test dir {0} should exist'.format(args.testdir))
open_test_file()
self.assertEqual(bpy.data.version, (5, 2, 5))
def tearDown(self):
self._tempdir.cleanup()
@ -632,6 +628,7 @@ def generate_test_data():
def main():
global args
import argparse
import os
if '--' in sys.argv:
argv = [sys.argv[0]] + sys.argv[sys.argv.index('--') + 1:]
@ -640,17 +637,13 @@ def main():
parser = argparse.ArgumentParser()
parser.add_argument('--testdir', required=True, type=pathlib.Path)
parser.add_argument(
'--generate',
action='store_true',
help="Generate ground truth test data instead of running the test")
parser.add_argument('--subtest', default=None, help="Select a single test case")
args, remaining = parser.parse_known_args(argv)
if args.generate:
if os.getenv("BLENDER_TEST_UPDATE") is not None:
generate_test_data()
else:
unittest.main(argv=remaining)
unittest.main(argv=remaining)
if __name__ == "__main__":