Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Blender 4.0 #104

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 48 additions & 9 deletions import_obj.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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:
Expand Down
95 changes: 77 additions & 18 deletions import_xnalara_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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__":
Expand Down
95 changes: 65 additions & 30 deletions material_creator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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'])
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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)
Expand Down