Fix #143551: Cache invalidation causes crash when changing node tree item properties

Changing a node tree item property (such as the default value) was using a very
broad and generic "tag" function which invalidates the runtime items cache.
This also invalidates any python iterators due to the API using the runtime
cache. Changing node tree items in a loop will then crash.

It's not necessary to invalidate the runtime items cache when the actual item
pointers have not changed. Most RNA updates only change superficial properties,
or at most require a recursive node tree update due to change of identifiers or
types.

This PR introduces a simpler "tag" function to only tag for tree updates by not
rebuild the entire runtime items cache. It also renames existing functions and
docstrings to better explain what each of them does and should be used for.

The `NodeTreeInterfaceChangedFlag` is removed completely because it is only ever
used as a simple boolean indicator of "item changes" that require a cache
rebuild. It is replaced with an atomic bool like flags used for runtime caches.

Pull Request: https://projects.blender.org/blender/blender/pulls/143932
This commit is contained in:
Lukas Tönne
2025-08-12 11:00:10 +02:00
parent c3ae3926da
commit dab6b45336
9 changed files with 103 additions and 67 deletions

View File

@@ -29,11 +29,9 @@ class bNodeTreeInterfaceRuntime {
private:
/**
* Keeps track of what changed in the node tree until the next update.
* Should not be changed directly, instead use the functions in `BKE_node_tree_update.hh`.
* #NodeTreeInterfaceChangedFlag.
* Changes have been made to the interface items that invalidate dependent trees.
*/
uint32_t changed_flag_ = 0;
std::atomic<bool> interface_changed_ = true;
/**
* Protects access to item cache variables below. This is necessary so that the cache can be

View File

@@ -27,15 +27,6 @@
using blender::StringRef;
/**
* These flags are used by the `changed_flag` field in #bNodeTreeInterfaceRuntime.
*/
enum NodeTreeInterfaceChangedFlag {
NODE_INTERFACE_CHANGED_NOTHING = 0,
NODE_INTERFACE_CHANGED_ITEMS = (1 << 1),
NODE_INTERFACE_CHANGED_ALL = -1,
};
namespace blender::bke::node_interface {
namespace socket_types {
@@ -1526,24 +1517,39 @@ void bNodeTreeInterface::ensure_items_cache() const
});
}
void bNodeTreeInterface::tag_missing_runtime_data()
void bNodeTreeInterface::tag_interface_changed()
{
this->runtime->changed_flag_ |= NODE_INTERFACE_CHANGED_ALL;
this->runtime->items_cache_mutex_.tag_dirty();
this->runtime->interface_changed_.store(true);
}
bool bNodeTreeInterface::is_changed() const
bool bNodeTreeInterface::requires_dependent_tree_updates() const
{
return this->runtime->changed_flag_ != NODE_INTERFACE_CHANGED_NOTHING;
return this->runtime->interface_changed_.load(std::memory_order_relaxed);
}
void bNodeTreeInterface::tag_items_changed()
{
this->runtime->changed_flag_ |= NODE_INTERFACE_CHANGED_ITEMS;
this->tag_interface_changed();
this->runtime->items_cache_mutex_.tag_dirty();
}
void bNodeTreeInterface::reset_changed_flags()
void bNodeTreeInterface::tag_items_changed_generic()
{
this->runtime->changed_flag_ = NODE_INTERFACE_CHANGED_NOTHING;
/* Perform a full update since we don't know what changed exactly. */
this->tag_items_changed();
}
void bNodeTreeInterface::tag_item_property_changed()
{
this->tag_interface_changed();
}
void bNodeTreeInterface::tag_missing_runtime_data()
{
this->tag_items_changed();
}
void bNodeTreeInterface::reset_interface_changed()
{
this->runtime->interface_changed_.store(false);
}

View File

@@ -196,7 +196,8 @@ static int get_internal_link_type_priority(const bNodeSocketType *from, const bN
/* Check both the tree's own tags and the interface tags. */
static bool is_tree_changed(const bNodeTree &tree)
{
return tree.runtime->changed_flag != NTREE_CHANGED_NOTHING || tree.tree_interface.is_changed();
return tree.runtime->changed_flag != NTREE_CHANGED_NOTHING ||
tree.tree_interface.requires_dependent_tree_updates();
}
using TreeNodePair = std::pair<bNodeTree *, bNode *>;
@@ -571,7 +572,7 @@ class NodeTreeMainUpdater {
ntreeTexCheckCyclics(&ntree);
}
if (ntree.tree_interface.is_changed()) {
if (ntree.tree_interface.requires_dependent_tree_updates()) {
result.interface_changed = true;
}
@@ -658,7 +659,7 @@ class NodeTreeMainUpdater {
/* Currently we have no way to tell if a node needs to be updated when a link changed. */
return true;
}
if (ntree.tree_interface.is_changed()) {
if (ntree.tree_interface.requires_dependent_tree_updates()) {
if (node.is_group_input() || node.is_group_output()) {
return true;
}
@@ -1888,7 +1889,7 @@ class NodeTreeMainUpdater {
}
}
ntree.tree_interface.reset_changed_flags();
ntree.tree_interface.reset_interface_changed();
}
/**

View File

@@ -150,7 +150,7 @@ class NodeSocketViewItem : public BasicTreeViewItem {
MEM_SAFE_FREE(socket_.name);
socket_.name = BLI_strdup(new_name.c_str());
nodetree_.tree_interface.tag_items_changed();
nodetree_.tree_interface.tag_item_property_changed();
BKE_main_ensure_invariants(*CTX_data_main(&C), nodetree_.id);
ED_undo_push(&const_cast<bContext &>(C), new_name.c_str());
return true;

View File

@@ -467,19 +467,26 @@ typedef struct bNodeTreeInterface {
/** Ensure the items cache can be accessed. */
void ensure_items_cache() const;
/** True if any runtime change flag is set. */
bool is_changed() const;
/** True if any trees and nodes depending on the interface require updates. */
bool requires_dependent_tree_updates() const;
/** Call after changing the items list. */
void tag_items_changed();
/** Call after generic user changes through the API. */
void tag_items_changed_generic();
/** Call after changing an item property. */
void tag_item_property_changed();
/**
* Tag runtime data and invalidate the cache.
* Must be called after any direct change to interface DNA data.
* Reset flag to indicate that dependent trees have been updated.
* Should only be called by #NodeTreeMainUpdater.
*/
void tag_items_changed();
/** Reset runtime flags after updates have been processed. */
void reset_changed_flags();
void reset_interface_changed();
private:
/** Tag after interface changes that require updates to dependent trees. */
void tag_interface_changed();
/** Invalidate caches and force full tree update after loading DNA. */
void tag_missing_runtime_data();
#endif

View File

@@ -1062,7 +1062,7 @@ static void rna_def_node_socket_interface_float(BlenderRNA *brna,
RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
RNA_def_property_ui_text(prop, "Subtype", "Subtype of the default value");
RNA_def_property_translation_context(prop, BLT_I18NCONTEXT_UNIT);
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_NodeTreeInterfaceSocket_value_update");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_NodeTreeInterfaceItem_update");
prop = RNA_def_property(srna, "default_value", PROP_FLOAT, subtype);
RNA_def_property_float_sdna(prop, nullptr, "value");
@@ -1071,19 +1071,19 @@ static void rna_def_node_socket_interface_float(BlenderRNA *brna,
RNA_def_property_float_funcs(
prop, nullptr, nullptr, "rna_NodeTreeInterfaceSocketFloat_default_value_range");
RNA_def_property_ui_text(prop, "Default Value", "Input value used for unconnected socket");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_NodeTreeInterfaceSocket_value_update");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_NodeTreeInterfaceItem_update");
prop = RNA_def_property(srna, "min_value", PROP_FLOAT, PROP_NONE);
RNA_def_property_float_sdna(prop, nullptr, "min");
RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
RNA_def_property_ui_text(prop, "Minimum Value", "Minimum value");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_NodeTreeInterfaceSocket_value_update");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_NodeTreeInterfaceItem_update");
prop = RNA_def_property(srna, "max_value", PROP_FLOAT, PROP_NONE);
RNA_def_property_float_sdna(prop, nullptr, "max");
RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
RNA_def_property_ui_text(prop, "Maximum Value", "Maximum value");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_NodeTreeInterfaceSocket_value_update");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_NodeTreeInterfaceItem_update");
RNA_def_struct_sdna_from(srna, "bNodeTreeInterfaceSocket", nullptr);
@@ -1151,7 +1151,7 @@ static void rna_def_node_socket_interface_int(BlenderRNA *brna,
RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
RNA_def_property_ui_text(prop, "Subtype", "Subtype of the default value");
RNA_def_property_translation_context(prop, BLT_I18NCONTEXT_UNIT);
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_NodeTreeInterfaceSocket_value_update");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_NodeTreeInterfaceItem_update");
prop = RNA_def_property(srna, "default_value", PROP_INT, subtype);
RNA_def_property_int_sdna(prop, nullptr, "value");
@@ -1159,19 +1159,19 @@ static void rna_def_node_socket_interface_int(BlenderRNA *brna,
RNA_def_property_int_funcs(
prop, nullptr, nullptr, "rna_NodeTreeInterfaceSocketInt_default_value_range");
RNA_def_property_ui_text(prop, "Default Value", "Input value used for unconnected socket");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_NodeTreeInterfaceSocket_value_update");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_NodeTreeInterfaceItem_update");
prop = RNA_def_property(srna, "min_value", PROP_INT, PROP_NONE);
RNA_def_property_int_sdna(prop, nullptr, "min");
RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
RNA_def_property_ui_text(prop, "Minimum Value", "Minimum value");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_NodeTreeInterfaceSocket_value_update");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_NodeTreeInterfaceItem_update");
prop = RNA_def_property(srna, "max_value", PROP_INT, PROP_NONE);
RNA_def_property_int_sdna(prop, nullptr, "max");
RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
RNA_def_property_ui_text(prop, "Maximum Value", "Maximum value");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_NodeTreeInterfaceSocket_value_update");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_NodeTreeInterfaceItem_update");
RNA_def_struct_sdna_from(srna, "bNodeTreeInterfaceSocket", nullptr);
@@ -1215,7 +1215,7 @@ static void rna_def_node_socket_interface_bool(BlenderRNA *brna, const char *ide
RNA_def_property_boolean_sdna(prop, nullptr, "value", 1);
RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
RNA_def_property_ui_text(prop, "Default Value", "Input value used for unconnected socket");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_NodeTreeInterfaceSocket_value_update");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_NodeTreeInterfaceItem_update");
RNA_def_struct_sdna_from(srna, "bNodeTreeInterfaceSocket", nullptr);
@@ -1260,7 +1260,7 @@ static void rna_def_node_socket_interface_rotation(BlenderRNA *brna, const char
RNA_def_property_float_sdna(prop, nullptr, "value_euler");
RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
RNA_def_property_ui_text(prop, "Default Value", "Input value used for unconnected socket");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_NodeTreeInterfaceSocket_value_update");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_NodeTreeInterfaceItem_update");
RNA_def_struct_sdna_from(srna, "bNodeTreeInterfaceSocket", nullptr);
@@ -1341,7 +1341,7 @@ static void rna_def_node_socket_interface_vector(BlenderRNA *brna,
RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
RNA_def_property_ui_text(prop, "Subtype", "Subtype of the default value");
RNA_def_property_translation_context(prop, BLT_I18NCONTEXT_UNIT);
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_NodeTreeInterfaceSocket_value_update");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_NodeTreeInterfaceItem_update");
prop = RNA_def_property(srna, "dimensions", PROP_INT, PROP_NONE);
RNA_def_property_int_sdna(prop, nullptr, "dimensions");
@@ -1358,19 +1358,19 @@ static void rna_def_node_socket_interface_vector(BlenderRNA *brna,
RNA_def_property_float_funcs(
prop, nullptr, nullptr, "rna_NodeTreeInterfaceSocketVector_default_value_range");
RNA_def_property_ui_text(prop, "Default Value", "Input value used for unconnected socket");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_NodeTreeInterfaceSocket_value_update");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_NodeTreeInterfaceItem_update");
prop = RNA_def_property(srna, "min_value", PROP_FLOAT, PROP_NONE);
RNA_def_property_float_sdna(prop, nullptr, "min");
RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
RNA_def_property_ui_text(prop, "Minimum Value", "Minimum value");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_NodeTreeInterfaceSocket_value_update");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_NodeTreeInterfaceItem_update");
prop = RNA_def_property(srna, "max_value", PROP_FLOAT, PROP_NONE);
RNA_def_property_float_sdna(prop, nullptr, "max");
RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
RNA_def_property_ui_text(prop, "Maximum Value", "Maximum value");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_NodeTreeInterfaceSocket_value_update");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_NodeTreeInterfaceItem_update");
RNA_def_struct_sdna_from(srna, "bNodeTreeInterfaceSocket", nullptr);
@@ -1414,7 +1414,7 @@ static void rna_def_node_socket_interface_color(BlenderRNA *brna, const char *id
RNA_def_property_float_sdna(prop, nullptr, "value");
RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
RNA_def_property_ui_text(prop, "Default Value", "Input value used for unconnected socket");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_NodeTreeInterfaceSocket_value_update");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_NodeTreeInterfaceItem_update");
RNA_def_struct_sdna_from(srna, "bNodeTreeInterfaceSocket", nullptr);
@@ -1470,14 +1470,14 @@ static void rna_def_node_socket_interface_string(BlenderRNA *brna,
RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
RNA_def_property_ui_text(prop, "Subtype", "Subtype of the default value");
RNA_def_property_translation_context(prop, BLT_I18NCONTEXT_UNIT);
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_NodeTreeInterfaceSocket_value_update");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_NodeTreeInterfaceItem_update");
prop = RNA_def_property(srna, "default_value", PROP_STRING, subtype);
RNA_def_property_string_sdna(prop, nullptr, "value");
RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
// TODO: Do I need to call RNA_def_property_string_funcs() ?
RNA_def_property_ui_text(prop, "Default Value", "Input value used for unconnected socket");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_NodeTreeInterfaceSocket_value_update");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_NodeTreeInterfaceItem_update");
RNA_def_struct_sdna_from(srna, "bNodeTreeInterfaceSocket", nullptr);
@@ -1526,7 +1526,7 @@ static void rna_def_node_socket_interface_menu(BlenderRNA *brna, const char *ide
RNA_def_property_enum_funcs(prop, nullptr, nullptr, "RNA_node_tree_interface_socket_menu_itemf");
RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
RNA_def_property_ui_text(prop, "Default Value", "Input value used for unconnected socket");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_NodeTreeInterfaceSocket_value_update");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_NodeTreeInterfaceItem_update");
RNA_def_struct_sdna_from(srna, "bNodeTreeInterfaceSocket", nullptr);
@@ -1591,7 +1591,7 @@ static void rna_def_node_socket_interface_object(BlenderRNA *brna, const char *i
RNA_def_property_pointer_sdna(prop, nullptr, "value");
RNA_def_property_struct_type(prop, "Object");
RNA_def_property_ui_text(prop, "Default Value", "Input value used for unconnected socket");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_NodeTreeInterfaceSocket_value_update");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_NodeTreeInterfaceItem_update");
RNA_def_property_flag(prop, PROP_EDITABLE | PROP_ID_REFCOUNT);
RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
@@ -1635,7 +1635,7 @@ static void rna_def_node_socket_interface_image(BlenderRNA *brna, const char *id
RNA_def_property_pointer_sdna(prop, nullptr, "value");
RNA_def_property_struct_type(prop, "Image");
RNA_def_property_ui_text(prop, "Default Value", "Input value used for unconnected socket");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_NodeTreeInterfaceSocket_value_update");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_NodeTreeInterfaceItem_update");
RNA_def_property_flag(prop, PROP_EDITABLE | PROP_ID_REFCOUNT);
RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
@@ -1742,7 +1742,7 @@ static void rna_def_node_socket_interface_collection(BlenderRNA *brna, const cha
RNA_def_property_pointer_sdna(prop, nullptr, "value");
RNA_def_property_struct_type(prop, "Collection");
RNA_def_property_ui_text(prop, "Default Value", "Input value used for unconnected socket");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_NodeTreeInterfaceSocket_value_update");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_NodeTreeInterfaceItem_update");
RNA_def_property_flag(prop, PROP_EDITABLE | PROP_ID_REFCOUNT);
RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
@@ -1786,7 +1786,7 @@ static void rna_def_node_socket_interface_texture(BlenderRNA *brna, const char *
RNA_def_property_pointer_sdna(prop, nullptr, "value");
RNA_def_property_struct_type(prop, "Texture");
RNA_def_property_ui_text(prop, "Default Value", "Input value used for unconnected socket");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_NodeTreeInterfaceSocket_value_update");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_NodeTreeInterfaceItem_update");
RNA_def_property_flag(prop, PROP_EDITABLE | PROP_ID_REFCOUNT);
RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
@@ -1832,7 +1832,7 @@ static void rna_def_node_socket_interface_material(BlenderRNA *brna, const char
RNA_def_property_pointer_funcs(
prop, nullptr, nullptr, nullptr, "rna_NodeTreeInterfaceSocketMaterial_default_value_poll");
RNA_def_property_ui_text(prop, "Default Value", "Input value used for unconnected socket");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_NodeTreeInterfaceSocket_value_update");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_NodeTreeInterfaceItem_update");
RNA_def_property_flag(prop, PROP_EDITABLE | PROP_ID_REFCOUNT);
RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);

View File

@@ -119,7 +119,7 @@ static void rna_NodeTreeInterfaceItem_update(Main *bmain, Scene * /*scene*/, Poi
/* This can happen because of the dummy socket in #rna_NodeTreeInterfaceSocket_register. */
return;
}
ntree->tree_interface.tag_items_changed();
ntree->tree_interface.tag_item_property_changed();
BKE_main_ensure_invariants(*bmain, ntree->id);
}
@@ -908,13 +908,6 @@ static const EnumPropertyItem *rna_NodeTreeInterfaceSocketString_subtype_itemf(
return rna_subtype_filter_itemf({PROP_FILEPATH, PROP_NONE}, r_free);
}
/* using a context update function here, to avoid searching the node if possible */
static void rna_NodeTreeInterfaceSocket_value_update(Main *bmain, Scene *scene, PointerRNA *ptr)
{
/* default update */
rna_NodeTreeInterfaceItem_update(bmain, scene, ptr);
}
/* If the dimensions of the vector socket changed, we need to update the socket type, since each
* dimensions value has its own sub-type. */
static void rna_NodeTreeInterfaceSocketVector_dimensions_update(Main *bmain,
@@ -937,7 +930,7 @@ static void rna_NodeTreeInterfaceSocketVector_dimensions_update(Main *bmain,
/* Restore existing default value. */
*static_cast<bNodeSocketValueVector *>(socket->socket_data) = default_value;
rna_NodeTreeInterfaceSocket_value_update(bmain, scene, ptr);
rna_NodeTreeInterfaceItem_update(bmain, scene, ptr);
}
static bool rna_NodeTreeInterfaceSocketMaterial_default_value_poll(PointerRNA * /*ptr*/,

View File

@@ -1704,7 +1704,7 @@ static void rna_NodeTree_debug_zone_lazy_function_graph(
static void rna_NodeTree_interface_update(bNodeTree *ntree, bContext *C)
{
Main *bmain = CTX_data_main(C);
ntree->tree_interface.tag_items_changed();
ntree->tree_interface.tag_items_changed_generic();
BKE_main_ensure_invariants(*bmain, ntree->id);
}

View File

@@ -469,6 +469,37 @@ class CompositorNodeGroupInterfaceTest(AbstractNodeGroupInterfaceTest, NodeGroup
self.do_test_remove("NodeSocketFloat")
class NodeTreeItemsIteratorTest(AbstractNodeGroupInterfaceTest, NodeGroupInterfaceTests):
tree_type = "ShaderNodeTree"
group_node_type = "ShaderNodeGroup"
def setUp(self):
super().setUp()
self.material = bpy.data.materials.new("test")
self.material.use_nodes = True
self.main_tree = self.material.node_tree
# Regression test for changes while iterating over tree interface items (#143551).
# The iterator should remain valid when changing properties of a tree item.
def test_items_iterator(self):
tree, group_node = self.make_group_and_instance()
tree.interface.new_socket("Input 0", socket_type="NodeSocketFloat", in_out='INPUT')
tree.interface.new_socket("Input 1", socket_type="NodeSocketBool", in_out='INPUT')
# The cache vector has a fixed buffer for small sizes, add enough sockets to force reallocation.
for i in range(20):
tree.interface.new_socket(f"Input {2+i}", socket_type="NodeSocketColor", in_out='INPUT')
# Iterate over items and change properties. The loop iterator must remain valid.
for item in tree.interface.items_tree:
if item.socket_type == "NodeSocketFloat":
item.default_value = 500.0
elif item.socket_type == "NodeSocketColor":
item.default_value = (1, 0, 0, 1)
elif item.socket_type == "NodeSocketBool":
item.default_value = True
def main():
global args
import argparse