From 92e17340cc89fbbd5e15f22e9499fd8978049bb6 Mon Sep 17 00:00:00 2001 From: Thomas Barlow Date: Wed, 15 Nov 2023 00:51:22 +0000 Subject: [PATCH 1/3] Fix NodeTree socket creation/clearing on Blender 4.0 --- material_creator.py | 92 +++++++++++++++++++++++++++++++-------------- 1 file changed, 63 insertions(+), 29 deletions(-) diff --git a/material_creator.py b/material_creator.py index bc9b564..59a74c2 100644 --- a/material_creator.py +++ b/material_creator.py @@ -69,6 +69,43 @@ GREY_COLOR = (0.5, 0.5, 0.5, 1) +if bpy.app.version < (4, 0): + def new_input_socket(node_tree, socket_type, socket_name): + return node_tree.inputs.new(socket_type, socket_name) + + def new_output_socket(node_tree, socket_type, socket_name): + return node_tree.outputs.new(socket_type, socket_name) + + def clear_sockets(node_tree): + node_tree.inputs.clear() + node_tree.outputs.clear() +else: + # Blender 4.0 moved NodeTree inputs and outputs into a combined interface. + # Additionally, only base socket types can be created directly. Subtypes must be set explicitly after socket + # creation. + NODE_SOCKET_SUBTYPES = { + # There are a lot more, but this is the only one in use currently. + NODE_SOCKET_FLOAT_FACTOR: ('FACTOR', NODE_SOCKET_FLOAT), + } + + def _new_socket(node_tree, socket_type, socket_name, in_out): + subtype, base_type = NODE_SOCKET_SUBTYPES.get(socket_type, (None, None)) + new_socket = node_tree.interface.new_socket(socket_name, in_out=in_out, + socket_type=base_type if base_type else socket_type) + if subtype: + new_socket.subtype = subtype + return new_socket + + def new_input_socket(node_tree, socket_type, socket_name): + return _new_socket(node_tree, socket_type, socket_name, 'INPUT') + + def new_output_socket(node_tree, socket_type, socket_name): + return _new_socket(node_tree, socket_type, socket_name, 'OUTPUT') + + def clear_sockets(node_tree): + node_tree.interface.clear() + + def makeMaterialOutputNode(node_tree): node = node_tree.nodes.new(OUTPUT_NODE) node.location = 600, 0 @@ -343,17 +380,16 @@ def mix_normal_group(): group_inputs.location = mainNormalSeparateNode.location + Vector((-200, -100)) group_outputs = node_tree.nodes.new(NODE_GROUP_OUTPUT) group_outputs.location = mainNormalSeparateNode.location + Vector((1200, -100)) - node_tree.inputs.clear() - node_tree.outputs.clear() + clear_sockets(node_tree) # Input Sockets - main_normal_socket = node_tree.inputs.new(NODE_SOCKET_COLOR, 'Main') + main_normal_socket = new_input_socket(node_tree, NODE_SOCKET_COLOR, 'Main') main_normal_socket.default_value = NORMAL_COLOR - detail_normal_socket = node_tree.inputs.new(NODE_SOCKET_COLOR, 'Detail') + detail_normal_socket = new_input_socket(node_tree, NODE_SOCKET_COLOR, 'Detail') detail_normal_socket.default_value = NORMAL_COLOR # Output Sockets - output_value = node_tree.outputs.new(NODE_SOCKET_COLOR, 'Color') + output_value = new_output_socket(node_tree, NODE_SOCKET_COLOR, 'Color') # Links Input links = node_tree.links @@ -410,26 +446,25 @@ def invert_channel_group(): group_inputs.location = separateRgbNode.location + Vector((-200, -100)) group_outputs = node_tree.nodes.new(NODE_GROUP_OUTPUT) group_outputs.location = combineRgbNode.location + Vector((200, 0)) - node_tree.inputs.clear() - node_tree.outputs.clear() + clear_sockets(node_tree) # Input/Output Sockets - input_color = node_tree.inputs.new(NODE_SOCKET_COLOR, 'Color') + input_color = new_input_socket(node_tree, NODE_SOCKET_COLOR, 'Color') input_color.default_value = GREY_COLOR - invert_r = node_tree.inputs.new(NODE_SOCKET_FLOAT_FACTOR, 'R') + invert_r = new_input_socket(node_tree, NODE_SOCKET_FLOAT_FACTOR, 'R') invert_r.default_value = 0 invert_r.min_value = 0 invert_r.max_value = 1 - invert_g = node_tree.inputs.new(NODE_SOCKET_FLOAT_FACTOR, 'G') + invert_g = new_input_socket(node_tree, NODE_SOCKET_FLOAT_FACTOR, 'G') invert_g.default_value = 0 invert_g.min_value = 0 invert_g.max_value = 1 - invert_b = node_tree.inputs.new(NODE_SOCKET_FLOAT_FACTOR, 'B') + invert_b = new_input_socket(node_tree, NODE_SOCKET_FLOAT_FACTOR, 'B') invert_b.default_value = 0 invert_b.min_value = 0 invert_b.max_value = 1 - output_value = node_tree.outputs.new(NODE_SOCKET_COLOR, 'Color') + output_value = new_output_socket(node_tree, NODE_SOCKET_COLOR, 'Color') # Links Input links = node_tree.links @@ -497,18 +532,17 @@ def normal_mask_group(): group_inputs.location = maskSeparateNode.location + Vector((-200, -100)) group_outputs = node_tree.nodes.new(NODE_GROUP_OUTPUT) group_outputs.location = normalMixNode.location + Vector((200, 0)) - node_tree.inputs.clear() - node_tree.outputs.clear() + clear_sockets(node_tree) # Input/Output Sockets - mask_color = node_tree.inputs.new(NODE_SOCKET_COLOR, 'Mask') + mask_color = new_input_socket(node_tree, NODE_SOCKET_COLOR, 'Mask') mask_color.default_value = LIGHTMAP_COLOR - normalMain_color = node_tree.inputs.new(NODE_SOCKET_COLOR, 'Normal1') + normalMain_color = new_input_socket(node_tree, NODE_SOCKET_COLOR, 'Normal1') normalMain_color.default_value = NORMAL_COLOR - normalDetail_color = node_tree.inputs.new(NODE_SOCKET_COLOR, 'Normal2') + normalDetail_color = new_input_socket(node_tree, NODE_SOCKET_COLOR, 'Normal2') normalDetail_color.default_value = NORMAL_COLOR - output_value = node_tree.outputs.new(NODE_SOCKET_COLOR, 'Normal') + output_value = new_output_socket(node_tree, NODE_SOCKET_COLOR, 'Normal') # Link Inputs/Output node_tree.links.new(group_inputs.outputs['Mask'], maskSeparateNode.inputs['Image']) @@ -537,28 +571,28 @@ def xps_shader_group(): group_output = shader.nodes.new(NODE_GROUP_OUTPUT) group_output.location += Vector((600, 0)) - output_diffuse = shader.inputs.new(NODE_SOCKET_COLOR, 'Diffuse') + output_diffuse = new_input_socket(shader, NODE_SOCKET_COLOR, 'Diffuse') output_diffuse.default_value = (DIFFUSE_COLOR) - output_lightmap = shader.inputs.new(NODE_SOCKET_COLOR, 'Lightmap') + output_lightmap = new_input_socket(shader, NODE_SOCKET_COLOR, 'Lightmap') output_lightmap.default_value = (LIGHTMAP_COLOR) - output_specular = shader.inputs.new(NODE_SOCKET_COLOR, 'Specular') + output_specular = new_input_socket(shader, NODE_SOCKET_COLOR, 'Specular') output_specular.default_value = (SPECULAR_COLOR) - output_emission = shader.inputs.new(NODE_SOCKET_COLOR, 'Emission') - output_normal = shader.inputs.new(NODE_SOCKET_COLOR, 'Bump Map') + output_emission = new_input_socket(shader, NODE_SOCKET_COLOR, 'Emission') + output_normal = new_input_socket(shader, NODE_SOCKET_COLOR, 'Bump Map') output_normal.default_value = (NORMAL_COLOR) - output_bump_mask = shader.inputs.new(NODE_SOCKET_COLOR, 'Bump Mask') - output_microbump1 = shader.inputs.new(NODE_SOCKET_COLOR, 'MicroBump 1') + output_bump_mask = new_input_socket(shader, NODE_SOCKET_COLOR, 'Bump Mask') + output_microbump1 = new_input_socket(shader, NODE_SOCKET_COLOR, 'MicroBump 1') output_microbump1.default_value = (NORMAL_COLOR) - output_microbump2 = shader.inputs.new(NODE_SOCKET_COLOR, 'MicroBump 2') + output_microbump2 = new_input_socket(shader, NODE_SOCKET_COLOR, 'MicroBump 2') output_microbump2.default_value = (NORMAL_COLOR) - output_environment = shader.inputs.new(NODE_SOCKET_COLOR, 'Environment') - output_alpha = shader.inputs.new(NODE_SOCKET_FLOAT_FACTOR, 'Alpha') + output_environment = new_input_socket(shader, NODE_SOCKET_COLOR, 'Environment') + output_alpha = new_input_socket(shader, NODE_SOCKET_FLOAT_FACTOR, 'Alpha') output_alpha.min_value = 0 output_alpha.max_value = 1 output_alpha.default_value = 1 # Group outputs - shader.outputs.new(NODE_SOCKET_SHADER, 'Shader') + new_output_socket(shader, NODE_SOCKET_SHADER, 'Shader') principled = shader.nodes.new(PRINCIPLED_SHADER_NODE) From ca9a00623c705e54f5127209575d275b27a81a4f Mon Sep 17 00:00:00 2001 From: Thomas Barlow Date: Wed, 15 Nov 2023 00:56:24 +0000 Subject: [PATCH 2/3] Fix PrincipledBSDF emission input access on Blender 4.0 --- material_creator.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/material_creator.py b/material_creator.py index 59a74c2..2c19fcf 100644 --- a/material_creator.py +++ b/material_creator.py @@ -623,7 +623,8 @@ def xps_shader_group(): # Alpha & Emission shader.links.new(group_input.outputs['Alpha'], principled.inputs['Alpha']) - shader.links.new(group_input.outputs['Emission'], principled.inputs['Emission']) + emission_input_name = 'Emission' if bpy.app.version < (4, 0) else 'Emission Color' + shader.links.new(group_input.outputs['Emission'], principled.inputs[emission_input_name]) # Normals normal_invert_channel = getNodeGroup(shader, INVERT_CHANNEL_NODE) From 4042fac555e15aeb47c4b1b9207fdccc15b3e379 Mon Sep 17 00:00:00 2001 From: Thomas Barlow Date: Wed, 15 Nov 2023 02:33:24 +0000 Subject: [PATCH 3/3] Replace armature/bone layers and bone groups with bone collections 4.0 removed both armature/bone layers and bone groups. The bone colors that were set on bone groups are now set on bones individually. The new method of hiding and unhiding bones doesn't work very well because it relies on all bone collections being hidden aside from the "Visible Bones" collection and requires that all bones are added to at least one collection because bones not in any collections are always visible (unless they have been individually hidden). --- import_obj.py | 57 +++++++++++++++++++++---- import_xnalara_model.py | 95 +++++++++++++++++++++++++++++++++-------- 2 files changed, 125 insertions(+), 27 deletions(-) diff --git a/import_obj.py b/import_obj.py index 5281206..899690b 100644 --- a/import_obj.py +++ b/import_obj.py @@ -440,18 +440,45 @@ def load_material_image(blender_material, context_material_name, img_data, type) mtl.close() -def hideBone(bone): - bone.layers[1] = True - bone.layers[0] = False - +if bpy.app.version < (4, 0): + def hideBone(bone): + bone.layers[1] = True + bone.layers[0] = False + + + def showBone(bone): + bone.layers[0] = True + bone.layers[1] = False + + + def visibleBone(bone): + return bone.layers[0] +else: + # Bone/Armature layers were removed in 4.0, replaced with Bone Collections. + # Because we cannot set both EditBone and Bone visibility without changing between Object/Pose and Edit modes, we + # control individual bone visibility by adding all bones to a hidden "Bones" collection and then adding/removing + # bones to/from a visible "Visible Bones" collection. + # This is not a good system now and I would recommend replacing it with simply hiding the bones. Though note that + # this would only hide the bone in the current mode. Hiding `Bone` hides only in Pose mode. Hiding `EditBone` hides + # only in Edit mode. + def _ensure_visibility_bones_collection(armature): + col = armature.collections.get("Visible Bones") + if col is None: + return armature.collections.new("Visible Bones") + else: + return col -def showBone(bone): - bone.layers[0] = True - bone.layers[1] = False + def hideBone(bone): + col = _ensure_visibility_bones_collection(bone.id_data) + col.unassign(bone) + def showBone(bone): + col = _ensure_visibility_bones_collection(bone.id_data) + col.assign(bone) -def visibleBone(bone): - return bone.layers[0] + def visibleBone(bone): + col = _ensure_visibility_bones_collection(bone.id_data) + return bone.name in col.bones def setMinimumLenght(bone): @@ -537,6 +564,18 @@ def create_armatures(filepath, relpath, bone.head = bone_heads[bone_id] bone.tail = bone.head # + mathutils.Vector((0,.01,0)) + if bpy.app.version >= (4, 0): + # Create collection to store all bones. + bones_collection = me.collections.new("Bones") + bones_collection.is_visible = False + # Create collection used to toggle bone visibility by adding/removing them from the collection. + visible_bones_collection = me.collections.new("Visible Bones") + + # Assign all bones to both Bone Collections. + for bone in me.edit_bones: + bones_collection.assign(bone) + visible_bones_collection.assign(bone) + # Set bone heirarchy for bone_id, bone_parent_id in enumerate(bone_parents): if bone_parent_id >= 0: diff --git a/import_xnalara_model.py b/import_xnalara_model.py index ed6fed3..cd23fc7 100644 --- a/import_xnalara_model.py +++ b/import_xnalara_model.py @@ -246,18 +246,45 @@ def recurBones(bone, vertexgroups, name): return visibleChain -def hideBone(bone): - bone.layers[1] = True - bone.layers[0] = False - +if bpy.app.version < (4, 0): + def hideBone(bone): + bone.layers[1] = True + bone.layers[0] = False + + + def showBone(bone): + bone.layers[0] = True + bone.layers[1] = False + + + def visibleBone(bone): + return bone.layers[0] +else: + # Bone/Armature layers were removed in 4.0, replaced with Bone Collections. + # Because we cannot set both EditBone and Bone visibility without changing between Object/Pose and Edit modes, we + # control individual bone visibility by adding all bones to a hidden "Bones" collection and then adding/removing + # bones to/from a visible "Visible Bones" collection. + # This is not a good system now and I would recommend replacing it with simply hiding the bones. Though note that + # this would only hide the bone in the current mode. Hiding `Bone` hides only in Pose mode. Hiding `EditBone` hides + # only in Edit mode. + def _ensure_visibility_bones_collection(armature): + col = armature.collections.get("Visible Bones") + if col is None: + return armature.collections.new("Visible Bones") + else: + return col -def showBone(bone): - bone.layers[0] = True - bone.layers[1] = False + def hideBone(bone): + col = _ensure_visibility_bones_collection(bone.id_data) + col.unassign(bone) + def showBone(bone): + col = _ensure_visibility_bones_collection(bone.id_data) + col.assign(bone) -def visibleBone(bone): - return bone.layers[0] + def visibleBone(bone): + col = _ensure_visibility_bones_collection(bone.id_data) + return bone.name in col.bones def showAllBones(armature_objs): @@ -341,6 +368,18 @@ def importBones(armature_ob): editBone.tail = Vector(editBone.head) + Vector((0, 0, -.1)) setMinimumLenght(editBone) + if bpy.app.version >= (4, 0): + # Create collection to store all bones. + bones_collection = armature_ob.data.collections.new("Bones") + bones_collection.is_visible = False + # Create collection used to toggle bone visibility by adding/removing them from the collection. + visible_bones_collection = armature_ob.data.collections.new("Visible Bones") + + # Assign all bones to both Bone Collections. + for bone in armature_ob.data.edit_bones: + bones_collection.assign(bone) + visible_bones_collection.assign(bone) + # set all bone parents for bone in bones: if (bone.parentId >= 0): @@ -758,17 +797,37 @@ def makeBoneGroups(armature_ob, mesh_ob): bone_pose_color = (color2) bone_pose_active_color = (color3) - boneGroup = armature_ob.pose.bone_groups.new(name=mesh_ob.name) + if bpy.app.version < (4, 0): + boneGroup = armature_ob.pose.bone_groups.new(name=mesh_ob.name) + + boneGroup.color_set = 'CUSTOM' + boneGroup.colors.normal = bone_pose_surface_color + boneGroup.colors.select = bone_pose_color + boneGroup.colors.active = bone_pose_active_color - boneGroup.color_set = 'CUSTOM' - boneGroup.colors.normal = bone_pose_surface_color - boneGroup.colors.select = bone_pose_color - boneGroup.colors.active = bone_pose_active_color + vertexGroups = mesh_ob.vertex_groups.keys() + poseBones = armature_ob.pose.bones + for boneName in vertexGroups: + poseBones[boneName].bone_group = boneGroup + else: + # Bone Groups have been removed, replaced with Bone Collections. + # Additionally, bone colors are now set on each bone individually. + bone_collection = armature_ob.data.collections.new(name=mesh_ob.name) + # All extra bone collections are hidden by default so that bone visibility can be toggled by adding/removing the + # bone from the "Visible Bones" bone collection. + bone_collection.is_visible = False + vertexGroups = mesh_ob.vertex_groups.keys() + poseBones = armature_ob.pose.bones + for boneName in vertexGroups: + pose_bone = poseBones[boneName] + bone_collection.assign(pose_bone) + color = pose_bone.color + color.palette = 'CUSTOM' + custom_colors = color.custom + custom_colors.normal = bone_pose_surface_color + custom_colors.select = bone_pose_color + custom_colors.active = bone_pose_active_color - vertexGroups = mesh_ob.vertex_groups.keys() - poseBones = armature_ob.pose.bones - for boneName in vertexGroups: - poseBones[boneName].bone_group = boneGroup if __name__ == "__main__":