Fix: Nodes: Occasional crashing when using swap operators

Calling the swap operators can result in Blender crashing. From my
testing, the behavior is inconsistent, as sometimes a crash is triggered
and other times it isn't. Though notably, they seem involve nodes
already linked to other nodes, or zones.

The crashing stems from the operators trying to access deleted data.
There were two places where this happens that were identified, one
involving removed links and the other involving removed nodes.

Pull Request: https://projects.blender.org/blender/blender/pulls/146909
This commit is contained in:
quackarooni
2025-09-28 19:40:55 +02:00
committed by Hans Goudey
parent f79896f5b9
commit e7cae0b220

View File

@@ -362,13 +362,12 @@ class NodeSwapOperator(NodeOperator):
if new_socket.hide or not new_socket.enabled:
continue
is_multi_input = link.to_socket.is_multi_input
new_link = tree.links.new(new_socket, link.to_socket)
try:
if link.to_socket.is_multi_input:
new_link.swap_multi_input_sort_id(link)
except AttributeError:
pass
if is_multi_input:
new_link.swap_multi_input_sort_id(link)
except KeyError:
pass
@@ -480,9 +479,10 @@ class NODE_OT_swap_node(NodeSwapOperator, Operator):
def execute(self, context):
tree = context.space_data.edit_tree
nodes_to_delete = set()
for old_node in context.selected_nodes[:]:
if tree.nodes.get(old_node.name) is None:
if old_node in nodes_to_delete:
continue
if old_node.bl_idname == self.type:
@@ -518,7 +518,7 @@ class NODE_OT_swap_node(NodeSwapOperator, Operator):
self.transfer_links(tree, output_node, new_node, is_input=False)
for node in zone_pair:
tree.nodes.remove(node)
nodes_to_delete.add(node)
else:
if (old_node.bl_idname in switch_nodes) and (new_node.bl_idname in switch_nodes):
self.transfer_switch_data(old_node, new_node)
@@ -528,7 +528,10 @@ class NODE_OT_swap_node(NodeSwapOperator, Operator):
self.transfer_links(tree, old_node, new_node, is_input=True)
self.transfer_links(tree, old_node, new_node, is_input=False)
tree.nodes.remove(old_node)
nodes_to_delete.add(old_node)
for node in nodes_to_delete:
tree.nodes.remove(node)
return {'FINISHED'}
@@ -733,9 +736,10 @@ class NODE_OT_swap_zone(ZoneOperator, NodeSwapOperator, Operator):
def execute(self, context):
tree = context.space_data.edit_tree
nodes_to_delete = set()
for old_node in context.selected_nodes[:]:
if tree.nodes.get(old_node.name) is None:
if old_node in nodes_to_delete:
continue
zone_pair = self.get_zone_pair(tree, old_node)
@@ -782,7 +786,7 @@ class NODE_OT_swap_zone(ZoneOperator, NodeSwapOperator, Operator):
self.transfer_links(tree, old_output_node, output_node, is_input=False)
for node in zone_pair:
tree.nodes.remove(node)
nodes_to_delete.add(node)
else:
with temporary_unframe((old_node,)):
input_node.location = old_node.location
@@ -799,7 +803,7 @@ class NODE_OT_swap_zone(ZoneOperator, NodeSwapOperator, Operator):
self.transfer_links(tree, old_node, input_node, is_input=True)
self.transfer_links(tree, old_node, output_node, is_input=False)
tree.nodes.remove(old_node)
nodes_to_delete.add(old_node)
if tree.type == "GEOMETRY" and self.add_default_geometry_link:
# Connect geometry sockets by default if available.
@@ -810,6 +814,9 @@ class NODE_OT_swap_zone(ZoneOperator, NodeSwapOperator, Operator):
if not (from_socket.is_linked or to_socket.is_linked):
tree.links.new(to_socket, from_socket)
for node in nodes_to_delete:
tree.nodes.remove(node)
return {'FINISHED'}