Fix #146946: Breaking a node zone crashes on valid pointer assumption

The Node Wrangler addon has a _Reset Nodes_ operator that can remove the input
node of a node zone. This crashes in reference set updates because the code
expects valid input/output node pairs in each zone.

The fix is two-fold:
1. Finding zones for the runtime now returns an empty result to ensure no
  invalid node pointers are being accessed. This should not happen in practice,
  all operators should make sure zone relationships are not broken.
2. The node wrangler addon is updated to ignore all zone types, including the
  newer repeat, closure, and for-each-element zones. The type filtering was
  outdated and now uses the `bl_idname` consistently.

Pull Request: https://projects.blender.org/blender/blender/pulls/147028
This commit is contained in:
Lukas Tönne
2025-09-30 10:49:20 +02:00
parent cd40f5b0f1
commit 8697bffe22
2 changed files with 30 additions and 9 deletions

View File

@@ -2261,13 +2261,28 @@ class NWResetNodes(bpy.types.Operator):
and nw_check_selected(cls, context)
and nw_check_active(cls, context))
@staticmethod
def is_frame_node(node):
return node.bl_idname == "NodeFrame"
group_node_types = {"CompositorNodeGroup", "GeometryNodeGroup", "ShaderNodeGroup"}
# TODO All zone nodes are ignored here for now, because replacing one of the input/output pair breaks the zone.
# It's possible to handle zones by using the `paired_output` function of an input node
# and reconstruct the zone using the `pair_with_output` function.
zone_node_types = {"GeometryNodeRepeatInput", "GeometryNodeRepeatOutput", "NodeClosureInput",
"NodeClosureOutput", "GeometryNodeSimulationInput", "GeometryNodeSimulationOutput",
"GeometryNodeForeachGeometryElementInput", "GeometryNodeForeachGeometryElementOutput"}
node_ignore = group_node_types | zone_node_types | {"NodeFrame", "NodeReroute"}
@classmethod
def ignore_node(cls, node):
return node.bl_idname in cls.node_ignore
def execute(self, context):
node_active = context.active_node
node_selected = context.selected_nodes
node_ignore = ["FRAME", "REROUTE", "GROUP", "SIMULATION_INPUT", "SIMULATION_OUTPUT"]
active_node_name = node_active.name if node_active.select else None
valid_nodes = [n for n in node_selected if n.type not in node_ignore]
valid_nodes = [n for n in node_selected if not self.ignore_node(n)]
# Create output lists
selected_node_names = [n.name for n in node_selected]
@@ -2275,18 +2290,18 @@ class NWResetNodes(bpy.types.Operator):
# Reset all valid children in a frame
node_active_is_frame = False
if len(node_selected) == 1 and node_active.type == "FRAME":
if len(node_selected) == 1 and self.is_frame_node(node_active):
node_tree = node_active.id_data
children = [n for n in node_tree.nodes if n.parent == node_active]
if children:
valid_nodes = [n for n in children if n.type not in node_ignore]
selected_node_names = [n.name for n in children if n.type not in node_ignore]
valid_nodes = [n for n in children if not self.ignore_node(n)]
selected_node_names = [n.name for n in children if not self.ignore_node(n)]
node_active_is_frame = True
# Check if valid nodes in selection
if not (len(valid_nodes) > 0):
# Check for frames only
frames_selected = [n for n in node_selected if n.type == "FRAME"]
frames_selected = [n for n in node_selected if self.is_frame_node(n)]
if (len(frames_selected) > 1 and len(frames_selected) == len(node_selected)):
self.report({'ERROR'}, "Please select only 1 frame to reset")
else:
@@ -2306,7 +2321,6 @@ class NWResetNodes(bpy.types.Operator):
# Run through all valid nodes
for node in valid_nodes:
parent = node.parent if node.parent else None
node_loc = [node.location.x, node.location.y]

View File

@@ -46,7 +46,7 @@ static Vector<std::unique_ptr<bNodeTreeZone>> find_zone_nodes(
zone->index = zones.size();
zone->output_node_id = node->identifier;
r_zone_by_inout_node.add(node, zone.get());
zones.append_and_get_index(std::move(zone));
zones.append(std::move(zone));
}
for (const bNodeZoneType *zone_type : zone_types) {
for (const bNode *input_node : tree.nodes_by_type(zone_type->input_idname)) {
@@ -58,6 +58,13 @@ static Vector<std::unique_ptr<bNodeTreeZone>> find_zone_nodes(
}
}
}
/* Avoid incomplete zones, all zones must have a valid input and output node. */
for (const std::unique_ptr<bNodeTreeZone> &zone : zones) {
if (!zone->input_node_id || !zone->output_node_id) {
r_zone_by_inout_node.clear();
return {};
}
}
return zones;
}