Fix #116477: Node group sockets with subtypes have broken idnames #117133

Merged
Lukas Tönne merged 7 commits from LukasTonne/blender:fix-node-socket-subtype-idnames into main 2024-01-16 15:32:43 +01:00
4 changed files with 154 additions and 11 deletions

View File

@ -29,7 +29,7 @@ extern "C" {
/* Blender file format version. */
#define BLENDER_FILE_VERSION BLENDER_VERSION
#define BLENDER_FILE_SUBVERSION 10
#define BLENDER_FILE_SUBVERSION 11
/* Minimum Blender version that supports reading file written with the current
* version. Older Blender versions will test this and cancel loading the file, showing a warning to

View File

@ -476,6 +476,7 @@ static void write_node_socket_interface(BlendWriter *writer, const bNodeSocket *
static bNodeSocket *make_socket(bNodeTree *ntree,
const eNodeSocketInOut in_out,
const StringRef idname,
Review

unnecessary newline

unnecessary newline
const StringRef name,
const StringRef identifier)
{
@ -501,6 +502,59 @@ static bNodeSocket *make_socket(bNodeTree *ntree,
return sock;
}
/* Include the subtype suffix for old socket idnames. */
static StringRef get_legacy_socket_subtype_idname(StringRef idname, const void *socket_data)
LukasTonne marked this conversation as resolved Outdated

Can you put legacy in this function name?

So it's clear this does not have to be kept up to date or get used by new code.

Can you put `legacy` in this function name? So it's clear this does not have to be kept up to date or get used by new code.
{
if (idname == "NodeSocketFloat") {
const bNodeSocketValueFloat &float_data = *static_cast<const bNodeSocketValueFloat *>(
socket_data);
switch (float_data.subtype) {
case PROP_UNSIGNED:
return "NodeSocketFloatUnsigned";
case PROP_PERCENTAGE:
return "NodeSocketFloatPercentage";
case PROP_FACTOR:
return "NodeSocketFloatFactor";
case PROP_ANGLE:
return "NodeSocketFloatAngle";
case PROP_TIME:
return "NodeSocketFloatTime";
case PROP_TIME_ABSOLUTE:
return "NodeSocketFloatTimeAbsolute";
case PROP_DISTANCE:
return "NodeSocketFloatDistance";
}
}
if (idname == "NodeSocketInt") {
const bNodeSocketValueInt &int_data = *static_cast<const bNodeSocketValueInt *>(socket_data);
switch (int_data.subtype) {
case PROP_UNSIGNED:
return "NodeSocketIntUnsigned";
case PROP_PERCENTAGE:
return "NodeSocketIntPercentage";
case PROP_FACTOR:
return "NodeSocketIntFactor";
}
}
if (idname == "NodeSocketVector") {
const bNodeSocketValueVector &vector_data = *static_cast<const bNodeSocketValueVector *>(
socket_data);
switch (vector_data.subtype) {
case PROP_TRANSLATION:
return "NodeSocketVectorTranslation";
case PROP_DIRECTION:
return "NodeSocketVectorDirection";
case PROP_VELOCITY:
return "NodeSocketVectorVelocity";
case PROP_ACCELERATION:
return "NodeSocketVectorAcceleration";
case PROP_EULER:
return "NodeSocketVectorEuler";
}
}
return idname;
}
/**
* Socket interface reconstruction for forward compatibility.
* To enable previous Blender versions to read the new interface DNA data,
@ -516,7 +570,11 @@ static void construct_interface_as_legacy_sockets(bNodeTree *ntree)
auto make_legacy_socket = [&](const bNodeTreeInterfaceSocket &socket,
eNodeSocketInOut in_out) -> bNodeSocket * {
bNodeSocket *iosock = make_socket(
ntree, in_out, socket.socket_type, socket.name ? socket.name : "", socket.identifier);
ntree,
in_out,
get_legacy_socket_subtype_idname(socket.socket_type, socket.socket_data),
socket.name ? socket.name : "",
socket.identifier);
if (!iosock) {
return nullptr;
}

View File

@ -1386,6 +1386,36 @@ static void version_geometry_nodes_use_rotation_socket(bNodeTree &ntree)
}
}
}
/* Find the base socket name for an idname that may include a subtype. */
static blender::StringRef legacy_socket_idname_to_socket_type(blender::StringRef idname)
{
using string_pair = std::pair<const char *, const char *>;
static const string_pair subtypes_map[] = {{"NodeSocketFloatUnsigned", "NodeSocketFloat"},
{"NodeSocketFloatPercentage", "NodeSocketFloat"},
{"NodeSocketFloatFactor", "NodeSocketFloat"},
{"NodeSocketFloatAngle", "NodeSocketFloat"},
{"NodeSocketFloatTime", "NodeSocketFloat"},
{"NodeSocketFloatTimeAbsolute", "NodeSocketFloat"},
{"NodeSocketFloatDistance", "NodeSocketFloat"},
{"NodeSocketIntUnsigned", "NodeSocketInt"},
{"NodeSocketIntPercentage", "NodeSocketInt"},
{"NodeSocketIntFactor", "NodeSocketInt"},
{"NodeSocketVectorTranslation", "NodeSocketVector"},
{"NodeSocketVectorDirection", "NodeSocketVector"},
{"NodeSocketVectorVelocity", "NodeSocketVector"},
{"NodeSocketVectorAcceleration", "NodeSocketVector"},
{"NodeSocketVectorEuler", "NodeSocketVector"},
{"NodeSocketVectorXYZ", "NodeSocketVector"}};
for (const string_pair &pair : subtypes_map) {
if (pair.first == idname) {
return pair.second;
}
}
/* Unchanged socket idname. */
return idname;
}
static bNodeTreeInterfaceItem *legacy_socket_move_to_interface(bNodeSocket &legacy_socket,
const eNodeSocketInOut in_out)
{
@ -1396,7 +1426,10 @@ static bNodeTreeInterfaceItem *legacy_socket_move_to_interface(bNodeSocket &lega
new_socket->name = BLI_strdup(legacy_socket.name);
new_socket->identifier = BLI_strdup(legacy_socket.identifier);
new_socket->description = BLI_strdup(legacy_socket.description);
new_socket->socket_type = BLI_strdup(legacy_socket.idname);
/* If the socket idname includes a subtype (e.g. "NodeSocketFloatFactor") this will convert it to
* the base type name ("NodeSocketFloat"). */
new_socket->socket_type = BLI_strdup(
legacy_socket_idname_to_socket_type(legacy_socket.idname).data());
new_socket->flag = (in_out == SOCK_IN ? NODE_INTERFACE_SOCKET_INPUT :
NODE_INTERFACE_SOCKET_OUTPUT);
SET_FLAG_FROM_TEST(
@ -1444,6 +1477,28 @@ static void versioning_convert_node_tree_socket_lists_to_interface(bNodeTree *nt
}
}
/**
* Original node tree interface conversion in did not convert socket idnames with subtype suffixes
* to correct socket base types (see #versioning_convert_node_tree_socket_lists_to_interface).
*/
static void versioning_fix_socket_subtype_idnames(bNodeTree *ntree)
{
bNodeTreeInterface &tree_interface = ntree->tree_interface;
tree_interface.foreach_item([](bNodeTreeInterfaceItem &item) -> bool {
if (item.item_type == NODE_INTERFACE_SOCKET) {
bNodeTreeInterfaceSocket &socket = reinterpret_cast<bNodeTreeInterfaceSocket &>(item);
blender::StringRef corrected_socket_type = legacy_socket_idname_to_socket_type(
socket.socket_type);
if (socket.socket_type != corrected_socket_type) {
MEM_freeN(socket.socket_type);
socket.socket_type = BLI_strdup(corrected_socket_type.data());
}
}
return true;
});
}
/* Convert coat inputs on the Principled BSDF. */
static void version_principled_bsdf_coat(bNodeTree *ntree)
{
@ -2577,6 +2632,14 @@ void blo_do_versions_400(FileData *fd, Library * /*lib*/, Main *bmain)
}
}
if (MAIN_VERSION_FILE_ATLEAST(bmain, 400, 20) && !MAIN_VERSION_FILE_ATLEAST(bmain, 401, 11)) {
/* Convert old socket lists into new interface items. */
FOREACH_NODETREE_BEGIN (bmain, ntree, id) {
versioning_fix_socket_subtype_idnames(ntree);
}
FOREACH_NODETREE_END;
}
/**
* Always bump subversion in BKE_blender_version.h when adding versioning
* code here, and wrap it inside a MAIN_VERSION_FILE_ATLEAST check.

View File

@ -14,7 +14,25 @@ import bpy
args = None
type_info = {
base_idname = {
"VALUE": "NodeSocketFloat",
"INT": "NodeSocketInt",
"BOOLEAN": "NodeSocketBool",
"ROTATION": "NodeSocketRotation",
"VECTOR": "NodeSocketVector",
"RGBA": "NodeSocketColor",
"STRING": "NodeSocketString",
"SHADER": "NodeSocketShader",
"OBJECT": "NodeSocketObject",
"IMAGE": "NodeSocketImage",
"GEOMETRY": "NodeSocketGeometry",
"COLLECTION": "NodeSocketCollection",
"TEXTURE": "NodeSocketTexture",
"MATERIAL": "NodeSocketMaterial",
}
subtype_idname = {
("VALUE", "NONE"): "NodeSocketFloat",
("VALUE", "UNSIGNED"): "NodeSocketFloatUnsigned",
("VALUE", "PERCENTAGE"): "NodeSocketFloatPercentage",
@ -63,8 +81,12 @@ class SocketSpec():
external_links: int = 1
@property
def idname(self):
return type_info[(self.type, self.subtype)]
def base_idname(self):
return base_idname[self.type]
@property
def subtype_idname(self):
return subtype_idname[(self.type, self.subtype)]
class AbstractNodeGroupInterfaceTest(unittest.TestCase):
@ -95,7 +117,7 @@ class AbstractNodeGroupInterfaceTest(unittest.TestCase):
# Examine the interface item.
self.assertEqual(item.name, spec.name)
self.assertEqual(item.bl_socket_idname, spec.idname)
self.assertEqual(item.bl_socket_idname, spec.base_idname)
self.assertEqual(item.identifier, spec.identifier)
# Types that have subtypes.
@ -134,7 +156,7 @@ class AbstractNodeGroupInterfaceTest(unittest.TestCase):
socket = next(s for s in node.inputs if s.identifier == spec.identifier)
self.assertIsNotNone(socket, f"Could not find socket for group input identifier {spec.identifier}")
self.assertEqual(socket.name, spec.name)
self.assertEqual(socket.bl_idname, spec.idname)
self.assertEqual(socket.bl_idname, spec.subtype_idname)
self.assertEqual(socket.type, spec.type)
self.assertEqual(socket.hide_value, spec.hide_value)
if test_links:
@ -147,7 +169,7 @@ class AbstractNodeGroupInterfaceTest(unittest.TestCase):
self.assertIsNotNone(
socket, f"Could not find group input socket for group input identifier {spec.identifier}")
self.assertEqual(socket.name, spec.name)
self.assertEqual(socket.bl_idname, spec.idname)
self.assertEqual(socket.bl_idname, spec.subtype_idname)
self.assertEqual(socket.type, spec.type)
self.assertEqual(socket.hide_value, spec.hide_value)
if test_links:
@ -158,7 +180,7 @@ class AbstractNodeGroupInterfaceTest(unittest.TestCase):
socket = next(s for s in node.outputs if s.identifier == spec.identifier)
self.assertIsNotNone(socket, f"Could not find socket for group output identifier {spec.identifier}")
self.assertEqual(socket.name, spec.name)
self.assertEqual(socket.bl_idname, spec.idname)
self.assertEqual(socket.bl_idname, spec.subtype_idname)
self.assertEqual(socket.type, spec.type)
self.assertEqual(socket.hide_value, spec.hide_value)
if test_links:
@ -171,7 +193,7 @@ class AbstractNodeGroupInterfaceTest(unittest.TestCase):
self.assertIsNotNone(
socket, f"Could not find group output socket for group output identifier {spec.identifier}")
self.assertEqual(socket.name, spec.name)
self.assertEqual(socket.bl_idname, spec.idname)
self.assertEqual(socket.bl_idname, spec.subtype_idname)
self.assertEqual(socket.type, spec.type)
self.assertEqual(socket.hide_value, spec.hide_value)
if test_links: