diff --git a/source/blender/blenkernel/BKE_blender_version.h b/source/blender/blenkernel/BKE_blender_version.h index 225c824e058..8a7484e375b 100644 --- a/source/blender/blenkernel/BKE_blender_version.h +++ b/source/blender/blenkernel/BKE_blender_version.h @@ -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 diff --git a/source/blender/blenkernel/intern/node.cc b/source/blender/blenkernel/intern/node.cc index 0cf33edfd98..5e78cf0ecfb 100644 --- a/source/blender/blenkernel/intern/node.cc +++ b/source/blender/blenkernel/intern/node.cc @@ -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, + 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) +{ + if (idname == "NodeSocketFloat") { + const bNodeSocketValueFloat &float_data = *static_cast( + 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(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( + 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; } diff --git a/source/blender/blenloader/intern/versioning_400.cc b/source/blender/blenloader/intern/versioning_400.cc index c7b145cc921..5e82fd7fc28 100644 --- a/source/blender/blenloader/intern/versioning_400.cc +++ b/source/blender/blenloader/intern/versioning_400.cc @@ -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; + 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(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. diff --git a/tests/python/bl_node_group_compat.py b/tests/python/bl_node_group_compat.py index 3e3002618e8..6c2e0f23ad6 100644 --- a/tests/python/bl_node_group_compat.py +++ b/tests/python/bl_node_group_compat.py @@ -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: