Fix versioning old 2.6 node groups, causing dangling link pointers #111704
@ -458,7 +458,7 @@ void BKE_modifier_set_error(const Object *ob, ModifierData *md, const char *_for
|
||||
}
|
||||
#endif
|
||||
|
||||
CLOG_ERROR(&LOG, "Object: \"%s\", Modifier: \"%s\", %s", ob->id.name + 2, md->name, md->error);
|
||||
CLOG_WARN(&LOG, "Object: \"%s\", Modifier: \"%s\", %s", ob->id.name + 2, md->name, md->error);
|
||||
}
|
||||
|
||||
void BKE_modifier_set_warning(const Object *ob, ModifierData *md, const char *_format, ...)
|
||||
|
@ -1358,14 +1358,16 @@ void ntreeSetTypes(const bContext *C, bNodeTree *ntree)
|
||||
blender::bke::ntree_set_typeinfo(ntree, ntreeTypeFind(ntree->idname));
|
||||
|
||||
for (bNode *node : ntree->all_nodes()) {
|
||||
blender::bke::node_set_typeinfo(C, ntree, node, nodeTypeFind(node->idname));
|
||||
|
||||
/* Set socket typeinfo first because node initialization may rely on socket typeinfo for
|
||||
* generating declarations. */
|
||||
LISTBASE_FOREACH (bNodeSocket *, sock, &node->inputs) {
|
||||
blender::bke::node_socket_set_typeinfo(ntree, sock, nodeSocketTypeFind(sock->idname));
|
||||
}
|
||||
LISTBASE_FOREACH (bNodeSocket *, sock, &node->outputs) {
|
||||
blender::bke::node_socket_set_typeinfo(ntree, sock, nodeSocketTypeFind(sock->idname));
|
||||
}
|
||||
|
||||
blender::bke::node_set_typeinfo(C, ntree, node, nodeTypeFind(node->idname));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1871,7 +1871,6 @@ void blo_do_versions_250(FileData *fd, Library * /*lib*/, Main *bmain)
|
||||
*/
|
||||
bNodeLink *link = static_cast<bNodeLink *>(MEM_callocN(sizeof(bNodeLink), "link"));
|
||||
BLI_addtail(&ntree->links, link);
|
||||
nodeUniqueID(ntree, node);
|
||||
link->fromnode = nullptr;
|
||||
link->fromsock = gsock;
|
||||
link->tonode = node;
|
||||
@ -1896,7 +1895,6 @@ void blo_do_versions_250(FileData *fd, Library * /*lib*/, Main *bmain)
|
||||
*/
|
||||
bNodeLink *link = static_cast<bNodeLink *>(MEM_callocN(sizeof(bNodeLink), "link"));
|
||||
BLI_addtail(&ntree->links, link);
|
||||
nodeUniqueID(ntree, node);
|
||||
link->fromnode = node;
|
||||
link->fromsock = sock;
|
||||
link->tonode = nullptr;
|
||||
@ -2123,56 +2121,8 @@ void blo_do_versions_250(FileData *fd, Library * /*lib*/, Main *bmain)
|
||||
}
|
||||
}
|
||||
|
||||
/* updates group node socket identifier so that
|
||||
* external links to/from the group node are preserved.
|
||||
*/
|
||||
static void lib_node_do_versions_group_indices(bNode *gnode)
|
||||
{
|
||||
bNodeTree *ngroup = (bNodeTree *)gnode->id;
|
||||
|
||||
LISTBASE_FOREACH (bNodeSocket *, sock, &gnode->outputs) {
|
||||
int old_index = sock->to_index;
|
||||
|
||||
LISTBASE_FOREACH (bNodeLink *, link, &ngroup->links) {
|
||||
if (link->tonode == nullptr && link->fromsock->own_index == old_index) {
|
||||
STRNCPY(sock->identifier, link->fromsock->identifier);
|
||||
/* deprecated */
|
||||
sock->own_index = link->fromsock->own_index;
|
||||
sock->to_index = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
LISTBASE_FOREACH (bNodeSocket *, sock, &gnode->inputs) {
|
||||
int old_index = sock->to_index;
|
||||
|
||||
LISTBASE_FOREACH (bNodeLink *, link, &ngroup->links) {
|
||||
if (link->fromnode == nullptr && link->tosock->own_index == old_index) {
|
||||
STRNCPY(sock->identifier, link->tosock->identifier);
|
||||
/* deprecated */
|
||||
sock->own_index = link->tosock->own_index;
|
||||
sock->to_index = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void do_versions_after_linking_250(Main *bmain)
|
||||
{
|
||||
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 256, 2)) {
|
||||
FOREACH_NODETREE_BEGIN (bmain, ntree, id) {
|
||||
/* updates external links for all group nodes in a tree */
|
||||
LISTBASE_FOREACH (bNode *, node, &ntree->nodes) {
|
||||
if (node->type == NODE_GROUP) {
|
||||
bNodeTree *ngroup = (bNodeTree *)node->id;
|
||||
if (ngroup) {
|
||||
lib_node_do_versions_group_indices(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
FOREACH_NODETREE_END;
|
||||
}
|
||||
|
||||
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 258, 0)) {
|
||||
/* Some very old (original comments claim pre-2.57) versioning that was wrongly done in
|
||||
* lib-linking code... Putting it here just to be sure (this is also checked at runtime anyway
|
||||
|
@ -6,6 +6,7 @@
|
||||
* \ingroup blenloader
|
||||
*/
|
||||
|
||||
#include "BKE_idprop.h"
|
||||
#include "BLI_utildefines.h"
|
||||
|
||||
/* allow readfile to use deprecated functionality */
|
||||
@ -52,6 +53,7 @@
|
||||
#include "BKE_mesh_legacy_convert.hh"
|
||||
#include "BKE_modifier.h"
|
||||
#include "BKE_node_runtime.hh"
|
||||
#include "BKE_node_tree_update.h"
|
||||
#include "BKE_particle.h"
|
||||
#include "BKE_pointcache.h"
|
||||
#include "BKE_scene.h"
|
||||
@ -1034,6 +1036,97 @@ static bool seq_set_wipe_angle_cb(Sequence *seq, void * /*user_data*/)
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Create a socket stub without typeinfo. */
|
||||
static bNodeSocket *version_make_socket_stub(const char *idname,
|
||||
eNodeSocketDatatype type,
|
||||
eNodeSocketInOut in_out,
|
||||
const char *identifier,
|
||||
const char *name,
|
||||
const void *default_value,
|
||||
const IDProperty *prop)
|
||||
{
|
||||
bNodeSocket *socket = MEM_cnew<bNodeSocket>(__func__);
|
||||
socket->runtime = MEM_new<blender::bke::bNodeSocketRuntime>(__func__);
|
||||
STRNCPY(socket->idname, idname);
|
||||
socket->type = int(type);
|
||||
socket->in_out = int(in_out);
|
||||
|
||||
socket->limit = (in_out == SOCK_IN ? 1 : 0xFFF);
|
||||
|
||||
BLI_strncpy(socket->identifier, identifier, sizeof(socket->identifier));
|
||||
BLI_strncpy(socket->name, name, sizeof(socket->name));
|
||||
socket->storage = nullptr;
|
||||
socket->flag |= SOCK_COLLAPSED;
|
||||
|
||||
/* Note: technically socket values can store ref-counted ID pointers, but at this stage the
|
||||
* refcount can be ignored. It gets recomputed after lib-linking for all ID pointers. Socket
|
||||
* values don't have allocated data, so a simple dupalloc works here. */
|
||||
socket->default_value = default_value ? MEM_dupallocN(default_value) : nullptr;
|
||||
socket->prop = prop ? IDP_CopyProperty(prop) : nullptr;
|
||||
|
||||
return socket;
|
||||
}
|
||||
|
||||
/* Same as nodeAddStaticNode but does not rely on node typeinfo. */
|
||||
static bNode *version_add_group_in_out_node(bNodeTree *ntree, const int type)
|
||||
{
|
||||
ListBase *ntree_socket_list = nullptr;
|
||||
ListBase *node_socket_list = nullptr;
|
||||
eNodeSocketInOut socket_in_out = SOCK_IN;
|
||||
|
||||
bNode *node = MEM_cnew<bNode>("new node");
|
||||
switch (type) {
|
||||
case NODE_GROUP_INPUT:
|
||||
STRNCPY(node->idname, "NodeGroupInput");
|
||||
ntree_socket_list = &ntree->inputs_legacy;
|
||||
/* Group input has only outputs. */
|
||||
node_socket_list = &node->outputs;
|
||||
socket_in_out = SOCK_OUT;
|
||||
break;
|
||||
case NODE_GROUP_OUTPUT:
|
||||
STRNCPY(node->idname, "NodeGroupOutput");
|
||||
ntree_socket_list = &ntree->outputs_legacy;
|
||||
/* Group output has only inputs. */
|
||||
node_socket_list = &node->inputs;
|
||||
socket_in_out = SOCK_IN;
|
||||
break;
|
||||
default:
|
||||
BLI_assert_unreachable();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
node->runtime = MEM_new<blender::bke::bNodeRuntime>(__func__);
|
||||
BLI_addtail(&ntree->nodes, node);
|
||||
nodeUniqueID(ntree, node);
|
||||
|
||||
/* Manual initialization of the node,
|
||||
* node->typeinfo is only set after versioning. */
|
||||
node->type = type;
|
||||
{
|
||||
if (ntree->typeinfo && ntree->typeinfo->node_add_init) {
|
||||
ntree->typeinfo->node_add_init(ntree, node);
|
||||
}
|
||||
|
||||
/* Add sockets without relying on declarations or typeinfo.
|
||||
* These are stubs for links, full typeinfo is defined later. */
|
||||
LISTBASE_FOREACH (bNodeSocket *, tree_socket, ntree_socket_list) {
|
||||
bNodeSocket *node_socket = version_make_socket_stub(tree_socket->idname,
|
||||
eNodeSocketDatatype(tree_socket->type),
|
||||
socket_in_out,
|
||||
tree_socket->identifier,
|
||||
tree_socket->name,
|
||||
tree_socket->default_value,
|
||||
tree_socket->prop);
|
||||
|
||||
BLI_addtail(node_socket_list, node_socket);
|
||||
BKE_ntree_update_tag_socket_type(ntree, node_socket);
|
||||
BKE_ntree_update_tag_socket_new(ntree, node_socket);
|
||||
}
|
||||
}
|
||||
BKE_ntree_update_tag_node_new(ntree, node);
|
||||
return node;
|
||||
}
|
||||
|
||||
/* NOLINTNEXTLINE: readability-function-size */
|
||||
void blo_do_versions_260(FileData *fd, Library * /*lib*/, Main *bmain)
|
||||
{
|
||||
@ -2170,6 +2263,128 @@ void blo_do_versions_260(FileData *fd, Library * /*lib*/, Main *bmain)
|
||||
}
|
||||
FOREACH_NODETREE_END;
|
||||
}
|
||||
|
||||
/* Convert the previously used ntree->inputs/ntree->outputs lists to interface nodes.
|
||||
* Pre 2.56.2 node trees automatically have all unlinked sockets exposed already,
|
||||
* see do_versions_after_linking_250.
|
||||
|
||||
*
|
||||
* This assumes valid typeinfo pointers, as set in lib_link_ntree.
|
||||
*
|
||||
* NOTE: theoretically only needed in node groups (main->nodetree),
|
||||
* but due to a temporary bug such links could have been added in all trees,
|
||||
* so have to clean up all of them ...
|
||||
*
|
||||
* NOTE: this always runs, without it links with nullptr fromnode and tonode remain
|
||||
* which causes problems.
|
||||
*/
|
||||
FOREACH_NODETREE_BEGIN (bmain, ntree, id) {
|
||||
bNode *input_node = nullptr, *output_node = nullptr;
|
||||
int num_inputs = 0, num_outputs = 0;
|
||||
bNodeLink *link, *next_link;
|
||||
/* Only create new interface nodes for actual older files.
|
||||
* New file versions already have input/output nodes with duplicate links,
|
||||
* in that case just remove the invalid links.
|
||||
*/
|
||||
const bool create_io_nodes = MAIN_VERSION_FILE_OLDER(bmain, 266, 2);
|
||||
|
||||
float input_locx = 1000000.0f, input_locy = 0.0f;
|
||||
float output_locx = -1000000.0f, output_locy = 0.0f;
|
||||
/* Rough guess, not nice but we don't have access to UI constants here. */
|
||||
const float offsetx = 42 + 3 * 20 + 20;
|
||||
// const float offsety = 0.0f;
|
||||
|
||||
if (create_io_nodes) {
|
||||
if (ntree->inputs_legacy.first) {
|
||||
input_node = version_add_group_in_out_node(ntree, NODE_GROUP_INPUT);
|
||||
}
|
||||
|
||||
if (ntree->outputs_legacy.first) {
|
||||
output_node = version_add_group_in_out_node(ntree, NODE_GROUP_OUTPUT);
|
||||
}
|
||||
}
|
||||
|
||||
/* Redirect links from/to the node tree interface to input/output node.
|
||||
* If the fromnode/tonode pointers are nullptr, this means a link from/to
|
||||
* the ntree interface sockets, which need to be redirected to new interface nodes.
|
||||
*/
|
||||
for (link = static_cast<bNodeLink *>(ntree->links.first); link != nullptr; link = next_link)
|
||||
{
|
||||
bool free_link = false;
|
||||
next_link = link->next;
|
||||
|
||||
if (link->fromnode == nullptr) {
|
||||
if (input_node) {
|
||||
link->fromnode = input_node;
|
||||
link->fromsock = node_group_input_find_socket(input_node, link->fromsock->identifier);
|
||||
BLI_assert(link->fromsock != nullptr);
|
||||
num_inputs++;
|
||||
|
||||
if (link->tonode) {
|
||||
if (input_locx > link->tonode->locx - offsetx) {
|
||||
input_locx = link->tonode->locx - offsetx;
|
||||
}
|
||||
input_locy += link->tonode->locy;
|
||||
}
|
||||
}
|
||||
else {
|
||||
free_link = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (link->tonode == nullptr) {
|
||||
if (output_node) {
|
||||
link->tonode = output_node;
|
||||
link->tosock = node_group_output_find_socket(output_node, link->tosock->identifier);
|
||||
BLI_assert(link->tosock != nullptr);
|
||||
num_outputs++;
|
||||
|
||||
if (link->fromnode) {
|
||||
if (output_locx < link->fromnode->locx + offsetx) {
|
||||
output_locx = link->fromnode->locx + offsetx;
|
||||
}
|
||||
output_locy += link->fromnode->locy;
|
||||
}
|
||||
}
|
||||
else {
|
||||
free_link = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (free_link) {
|
||||
nodeRemLink(ntree, link);
|
||||
}
|
||||
}
|
||||
|
||||
if (num_inputs > 0) {
|
||||
input_locy /= num_inputs;
|
||||
input_node->locx = input_locx;
|
||||
input_node->locy = input_locy;
|
||||
}
|
||||
if (num_outputs > 0) {
|
||||
output_locy /= num_outputs;
|
||||
output_node->locx = output_locx;
|
||||
output_node->locy = output_locy;
|
||||
}
|
||||
}
|
||||
FOREACH_NODETREE_END;
|
||||
}
|
||||
|
||||
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 280, 60)) {
|
||||
Jacques Lucke
commented
This versioning looks like it's at the wrong place (see version number) This versioning looks like it's at the wrong place (see version number)
Lukas Tönne
commented
Correct. I considered moving it to the 280 file, and the first thing i saw at the bottom of the that file was a bit of versioning code for ... 3.0 :P I find it extremely difficult to reason about why something works or doesn't in nodes versioning, so i just left it next to the other bit for now ... Correct. I considered moving it to the 280 file, and the first thing i saw at the bottom of the _that_ file was a bit of versioning code for ... 3.0 :P
https://projects.blender.org/blender/blender/src/branch/main/source/blender/blenloader/intern/versioning_280.cc#L6300-L6324
I find it extremely difficult to reason about why something works or doesn't in nodes versioning, so i just left it next to the other bit for now ...
Jacques Lucke
commented
I see. It's not super horrible if it works. The code between here and where this versioning code would usually live is unlikely to change in major ways. I see. It's not super horrible if it works. The code between here and where this versioning code would usually live is unlikely to change in major ways.
|
||||
/* From this point we no longer write incomplete links for forward
|
||||
* compatibility with 2.66, we have to clean them up for all previous
|
||||
* versions. */
|
||||
FOREACH_NODETREE_BEGIN (bmain, ntree, id) {
|
||||
bNodeLink *link, *next_link;
|
||||
|
||||
for (link = static_cast<bNodeLink *>(ntree->links.first); link != nullptr; link = next_link)
|
||||
{
|
||||
next_link = link->next;
|
||||
if (link->fromnode == nullptr || link->tonode == nullptr) {
|
||||
nodeRemLink(ntree, link);
|
||||
}
|
||||
}
|
||||
}
|
||||
FOREACH_NODETREE_END;
|
||||
}
|
||||
|
||||
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 266, 4)) {
|
||||
@ -2692,127 +2907,4 @@ void blo_do_versions_260(FileData *fd, Library * /*lib*/, Main *bmain)
|
||||
}
|
||||
}
|
||||
|
||||
void do_versions_after_linking_260(Main *bmain)
|
||||
{
|
||||
/* Convert the previously used ntree->inputs/ntree->outputs lists to interface nodes.
|
||||
* Pre 2.56.2 node trees automatically have all unlinked sockets exposed already,
|
||||
* see do_versions_after_linking_250.
|
||||
*
|
||||
* This assumes valid typeinfo pointers, as set in lib_link_ntree.
|
||||
*
|
||||
* NOTE: theoretically only needed in node groups (main->nodetree),
|
||||
* but due to a temporary bug such links could have been added in all trees,
|
||||
* so have to clean up all of them ...
|
||||
*
|
||||
* NOTE: this always runs, without it links with nullptr fromnode and tonode remain
|
||||
* which causes problems.
|
||||
*/
|
||||
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 266, 3)) {
|
||||
FOREACH_NODETREE_BEGIN (bmain, ntree, id) {
|
||||
bNode *input_node = nullptr, *output_node = nullptr;
|
||||
int num_inputs = 0, num_outputs = 0;
|
||||
bNodeLink *link, *next_link;
|
||||
/* Only create new interface nodes for actual older files.
|
||||
* New file versions already have input/output nodes with duplicate links,
|
||||
* in that case just remove the invalid links.
|
||||
*/
|
||||
const bool create_io_nodes = MAIN_VERSION_FILE_OLDER(bmain, 266, 2);
|
||||
|
||||
float input_locx = 1000000.0f, input_locy = 0.0f;
|
||||
float output_locx = -1000000.0f, output_locy = 0.0f;
|
||||
/* Rough guess, not nice but we don't have access to UI constants here. */
|
||||
const float offsetx = 42 + 3 * 20 + 20;
|
||||
// const float offsety = 0.0f;
|
||||
|
||||
if (create_io_nodes) {
|
||||
if (ntree->inputs_legacy.first) {
|
||||
input_node = nodeAddStaticNode(nullptr, ntree, NODE_GROUP_INPUT);
|
||||
}
|
||||
|
||||
if (ntree->outputs_legacy.first) {
|
||||
output_node = nodeAddStaticNode(nullptr, ntree, NODE_GROUP_OUTPUT);
|
||||
}
|
||||
}
|
||||
|
||||
/* Redirect links from/to the node tree interface to input/output node.
|
||||
* If the fromnode/tonode pointers are nullptr, this means a link from/to
|
||||
* the ntree interface sockets, which need to be redirected to new interface nodes.
|
||||
*/
|
||||
for (link = static_cast<bNodeLink *>(ntree->links.first); link != nullptr; link = next_link)
|
||||
{
|
||||
bool free_link = false;
|
||||
next_link = link->next;
|
||||
|
||||
if (link->fromnode == nullptr) {
|
||||
if (input_node) {
|
||||
link->fromnode = input_node;
|
||||
link->fromsock = node_group_input_find_socket(input_node, link->fromsock->identifier);
|
||||
num_inputs++;
|
||||
|
||||
if (link->tonode) {
|
||||
if (input_locx > link->tonode->locx - offsetx) {
|
||||
input_locx = link->tonode->locx - offsetx;
|
||||
}
|
||||
input_locy += link->tonode->locy;
|
||||
}
|
||||
}
|
||||
else {
|
||||
free_link = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (link->tonode == nullptr) {
|
||||
if (output_node) {
|
||||
link->tonode = output_node;
|
||||
link->tosock = node_group_output_find_socket(output_node, link->tosock->identifier);
|
||||
num_outputs++;
|
||||
|
||||
if (link->fromnode) {
|
||||
if (output_locx < link->fromnode->locx + offsetx) {
|
||||
output_locx = link->fromnode->locx + offsetx;
|
||||
}
|
||||
output_locy += link->fromnode->locy;
|
||||
}
|
||||
}
|
||||
else {
|
||||
free_link = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (free_link) {
|
||||
nodeRemLink(ntree, link);
|
||||
}
|
||||
}
|
||||
|
||||
if (num_inputs > 0) {
|
||||
input_locy /= num_inputs;
|
||||
input_node->locx = input_locx;
|
||||
input_node->locy = input_locy;
|
||||
}
|
||||
if (num_outputs > 0) {
|
||||
output_locy /= num_outputs;
|
||||
output_node->locx = output_locx;
|
||||
output_node->locy = output_locy;
|
||||
}
|
||||
}
|
||||
FOREACH_NODETREE_END;
|
||||
}
|
||||
|
||||
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 280, 60)) {
|
||||
/* From this point we no longer write incomplete links for forward
|
||||
* compatibility with 2.66, we have to clean them up for all previous
|
||||
* versions. */
|
||||
FOREACH_NODETREE_BEGIN (bmain, ntree, id) {
|
||||
bNodeLink *link, *next_link;
|
||||
|
||||
for (link = static_cast<bNodeLink *>(ntree->links.first); link != nullptr; link = next_link)
|
||||
{
|
||||
next_link = link->next;
|
||||
if (link->fromnode == nullptr || link->tonode == nullptr) {
|
||||
nodeRemLink(ntree, link);
|
||||
}
|
||||
}
|
||||
}
|
||||
FOREACH_NODETREE_END;
|
||||
}
|
||||
}
|
||||
void do_versions_after_linking_260(Main * /*bmain*/) {}
|
||||
|
Loading…
Reference in New Issue
Block a user
This comment hints at a potential problem, because now
do_versions_after_linking_250
also runs after this versioning code now. I don't even know what node systems we had pre 2.5 tbh.I've started adding a simple compatibility test, loading files from 2.5, 2.6, 3.6, etc (trying to cover major transitions in node code). Loading a simple 2.5 file already fails an assert in unrelated versioning code, because a node gets added to the runtime
nodes_by_id
set twice.I hope there aren't too many other issues and i can actually test the
after_linking
parts. This should have been done years ago, but better late than never ...The offending
nodeUniqueName
was added byab4926bcff
but it re-generates the identifier for every socket in a node and adds the node tonodes_by_id
as many times.I find so many broken things with 2.5 nodes versioning, might be preferable to complete this PR for 2.6 compatibility and continue fixing 2.5 nodes separately.
Getting back to the original question: "Does this
after_linking
code still work after regular versioning to 4.0?"Yes! And in this case running in
after_linking
is necessary, because the socket identifier update looks at the node tree pointers of group nodes (i.e. other ID data blocks). It reads frombNodeSocket::own_index
(deprecated by 2.5.2) andbNodeSocket::identifier
(stable, existing socket identifiers never change). So this should be pretty safe to keep inafter_linking
.The question was mostly whether it's a problem that now the order of the versioning is reversed, since the after-linking code runs after the code that is moved in this patch (but it used to run before).
In #111800 i'm adding a compatibility regression test for node groups. The 3.6 case already required some minor fixes, i want to add a 2.5 test if feasible.
Took me a long time to unravel the timeline of node changes, here's what i reconstructed so far:
TL;DR: We need to update after_linking code to find group socket matches and preserve external links.
Before 2.56.2 all the unconnected internal sockets were exposed. To find the internal socket for a group node input or output one would search for the internal socket whose
own_index
matches the externalto_index
.Starting with 2.56.2 and before 2.66.3 this behavior changed and now node groups would only show an explicit set of inputs and outputs (stored in
bNodeTree
inputs
/outputs
). This required versioning totonode==nullptr
orfromnode==nullptr
which indicate a connection to the node tree interface.code link
All of this happens inside the same
bNodeTree
as part of regular versioning. Theafter_linking
versioning is needed for updating theown_index
of group nodes (code link):own_index
is still used to identify sockets at this point (replaced with identifiers later).to_index
gets deprecated, the socket in the node tree and its counterpart on a group node instance should simply have the sameown_index
. In order to version this, however, each group node needs to inspect its linked node group, which is not available during regular versioning.Note: Socket identifier strings are already generated by 2.56.2 versioning, but not sure if they would have been used for anything. This might be some apocryphal versioning code added later?
In 2.66.2+ there was another set of changes:
own_index
mapping is replaced with unique socket identifier strings code linknullptrs
allowed) code linkThe 2.66.3 versioning code was also running in the
after_linking
stage, but as demonstrated here this is only for the convenience ofbNodeSocketType
callbacks and not really necessary.What could happen if we execute the 2.56.2
after_linking
code after all the 4.0 versioning? Inside the node group all the unconnected sockets have been exposed first asntree->inputs
/ntree-outputs
with nullptr links, those have then been replaced with group input/output nodes and regular links, and then thentree->inputs
/ntree-outputs
have been replaced bytree_interface.ui_items
. Even after all this the deprecatedown_index
of the sockets in the tree that should be exposed to mimick 2.5 behavior still exist, which would make it possible to find internal sockets and update the external socket identifiers.However, the versioning code also expects there to be special links with node nullptrs now - so the 2.56.2
after_linking
versioning code already includes changes from 2.66.3 in order to function correctly. The special links have been replaced by versioning code, so this part is now ineffective and never runs.The consequence is that socket identifiers for node groups are not fully updated and some links get broken. This is because the identifiers of existing sockets are first generated by 2.66.2 versioning code (code link) but they are only based on names and unique suffixes. Sockets without a matching group tree socket are replaced later and any links discarded.
We need to update
do_versions_after_linking_250
to take this into account and search for matching internal socket like so:own_index
matches the group socket'sto_index
:There is a lingering question of what might happen if some intermediate versioning depends on correct group socket identifiers? But i think this is as far as we can reasonably support such ancient files.
Thanks for looking into this. I think it's ok when we are not able to load these ancient files perfectly. Would be nice, but it is what it is. If people really rely on those files, they may have to load and save the files in a more recent version. It's good to know why things are not working anyway. Also, it's great that you are adding some tests for this!
Do you happen to know when the node systems that we use nowadays have been introduced?
Depends on what you mean by "systems that we use nowadays". Oldest commit to the
nodes
folder is from 2007. From then on the nodes system has been a Ship of Theseus: 2.5 saw the change to explicit node group sockets, then group input/output nodes and python nodes in 2.6, and other parts of the system have been modified and swapped out over the years.I can't figure this out (in reasonable time and with acceptable sanity loss). I "fixed" the 2.5 after_linking code, but it still does not work because socket identifiers in the bNodeTree are changing so much that links cannot be restored from the 2.5 file.
do_versions_nodetree_customnodes
in versioning_260 to aFooBar.001
formatFooBar_001
formatFooBar.001
format in the after_linking codeFooBar_001
format (not sure where from)