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__": diff --git a/material_creator.py b/material_creator.py index bc9b564..2c19fcf 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) @@ -589,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)