diff options
Diffstat (limited to 'io_scene_gltf2/blender/imp/gltf2_blender_skin.py')
-rwxr-xr-x | io_scene_gltf2/blender/imp/gltf2_blender_skin.py | 209 |
1 files changed, 209 insertions, 0 deletions
diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_skin.py b/io_scene_gltf2/blender/imp/gltf2_blender_skin.py new file mode 100755 index 00000000..db0e50f9 --- /dev/null +++ b/io_scene_gltf2/blender/imp/gltf2_blender_skin.py @@ -0,0 +1,209 @@ +# Copyright 2018 The glTF-Blender-IO authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +import bpy +from mathutils import Vector, Matrix +from ..com.gltf2_blender_conversion import matrix_gltf_to_blender, scale_to_matrix +from ...io.imp.gltf2_io_binary import BinaryData + + +class BlenderSkin(): + """Blender Skinning / Armature.""" + def __new__(cls, *args, **kwargs): + raise RuntimeError("%s should not be instantiated" % cls) + + @staticmethod + def create_armature(gltf, skin_id, parent): + """Armature creation.""" + pyskin = gltf.data.skins[skin_id] + + if pyskin.name is not None: + name = pyskin.name + else: + name = "Armature_" + str(skin_id) + + armature = bpy.data.armatures.new(name) + obj = bpy.data.objects.new(name, armature) + bpy.data.scenes[gltf.blender_scene].collection.objects.link(obj) + pyskin.blender_armature_name = obj.name + if parent is not None: + obj.parent = bpy.data.objects[gltf.data.nodes[parent].blender_object] + + @staticmethod + def set_bone_transforms(gltf, skin_id, bone, node_id, parent): + """Set bone transformations.""" + pyskin = gltf.data.skins[skin_id] + pynode = gltf.data.nodes[node_id] + + obj = bpy.data.objects[pyskin.blender_armature_name] + + # Set bone bind_pose by inverting bindpose matrix + if node_id in pyskin.joints: + index_in_skel = pyskin.joints.index(node_id) + inverse_bind_matrices = BinaryData.get_data_from_accessor(gltf, pyskin.inverse_bind_matrices) + # Needed to keep scale in matrix, as bone.matrix seems to drop it + if index_in_skel < len(inverse_bind_matrices): + pynode.blender_bone_matrix = matrix_gltf_to_blender( + inverse_bind_matrices[index_in_skel] + ).inverted() + bone.matrix = pynode.blender_bone_matrix + else: + gltf.log.error("Error with inverseBindMatrix for skin " + pyskin) + else: + print('No invBindMatrix for bone ' + str(node_id)) + pynode.blender_bone_matrix = Matrix() + + # Parent the bone + if parent is not None and hasattr(gltf.data.nodes[parent], "blender_bone_name"): + bone.parent = obj.data.edit_bones[gltf.data.nodes[parent].blender_bone_name] # TODO if in another scene + + # Switch to Pose mode + bpy.ops.object.mode_set(mode="POSE") + obj.data.pose_position = 'POSE' + + # Set posebone location/rotation/scale (in armature space) + # location is actual bone location minus it's original (bind) location + bind_location = Matrix.Translation(pynode.blender_bone_matrix.to_translation()) + bind_rotation = pynode.blender_bone_matrix.to_quaternion() + bind_scale = scale_to_matrix(pynode.blender_bone_matrix.to_scale()) + + location, rotation, scale = matrix_gltf_to_blender(pynode.transform).decompose() + if parent is not None and hasattr(gltf.data.nodes[parent], "blender_bone_matrix"): + parent_mat = gltf.data.nodes[parent].blender_bone_matrix + + # Get armature space location (bindpose + pose) + # Then, remove original bind location from armspace location, and bind rotation + final_location = (bind_location.inverted() @ parent_mat @ Matrix.Translation(location)).to_translation() + obj.pose.bones[pynode.blender_bone_name].location = \ + bind_rotation.inverted().to_matrix().to_4x4() @ final_location + + # Do the same for rotation + obj.pose.bones[pynode.blender_bone_name].rotation_quaternion = \ + (bind_rotation.to_matrix().to_4x4().inverted() @ parent_mat @ + rotation.to_matrix().to_4x4()).to_quaternion() + obj.pose.bones[pynode.blender_bone_name].scale = \ + (bind_scale.inverted() @ parent_mat @ scale_to_matrix(scale)).to_scale() + + else: + obj.pose.bones[pynode.blender_bone_name].location = bind_location.inverted() @ location + obj.pose.bones[pynode.blender_bone_name].rotation_quaternion = bind_rotation.inverted() @ rotation + obj.pose.bones[pynode.blender_bone_name].scale = bind_scale.inverted() @ scale + + @staticmethod + def create_bone(gltf, skin_id, node_id, parent): + """Bone creation.""" + pyskin = gltf.data.skins[skin_id] + pynode = gltf.data.nodes[node_id] + + scene = bpy.data.scenes[gltf.blender_scene] + obj = bpy.data.objects[pyskin.blender_armature_name] + + bpy.context.window.scene = scene + bpy.context.view_layer.objects.active = obj + bpy.ops.object.mode_set(mode="EDIT") + + if pynode.name: + name = pynode.name + else: + name = "Bone_" + str(node_id) + + bone = obj.data.edit_bones.new(name) + pynode.blender_bone_name = bone.name + pynode.blender_armature_name = pyskin.blender_armature_name + bone.tail = Vector((0.0, 1.0, 0.0)) # Needed to keep bone alive + + # set bind and pose transforms + BlenderSkin.set_bone_transforms(gltf, skin_id, bone, node_id, parent) + bpy.ops.object.mode_set(mode="OBJECT") + + @staticmethod + def create_vertex_groups(gltf, skin_id): + """Vertex Group creation.""" + pyskin = gltf.data.skins[skin_id] + for node_id in pyskin.node_ids: + obj = bpy.data.objects[gltf.data.nodes[node_id].blender_object] + for bone in pyskin.joints: + obj.vertex_groups.new(name=gltf.data.nodes[bone].blender_bone_name) + + @staticmethod + def assign_vertex_groups(gltf, skin_id): + """Assign vertex groups to vertices.""" + pyskin = gltf.data.skins[skin_id] + for node_id in pyskin.node_ids: + node = gltf.data.nodes[node_id] + obj = bpy.data.objects[node.blender_object] + + offset = 0 + for prim in gltf.data.meshes[node.mesh].primitives: + idx_already_done = {} + + if 'JOINTS_0' in prim.attributes.keys() and 'WEIGHTS_0' in prim.attributes.keys(): + joint_ = BinaryData.get_data_from_accessor(gltf, prim.attributes['JOINTS_0']) + weight_ = BinaryData.get_data_from_accessor(gltf, prim.attributes['WEIGHTS_0']) + + for poly in obj.data.polygons: + for loop_idx in range(poly.loop_start, poly.loop_start + poly.loop_total): + vert_idx = obj.data.loops[loop_idx].vertex_index + + if vert_idx in idx_already_done.keys(): + continue + idx_already_done[vert_idx] = True + + if vert_idx in range(offset, offset + prim.vertices_length): + + tab_index = vert_idx - offset + cpt = 0 + for joint_idx in joint_[tab_index]: + weight_val = weight_[tab_index][cpt] + if weight_val != 0.0: # It can be a problem to assign weights of 0 + # for bone index 0, if there is always 4 indices in joint_ + # tuple + group = obj.vertex_groups[gltf.data.nodes[ + pyskin.joints[joint_idx] + ].blender_bone_name] + group.add([vert_idx], weight_val, 'REPLACE') + cpt += 1 + else: + gltf.log.error("No Skinning ?????") # TODO + + offset = offset + prim.vertices_length + + @staticmethod + def create_armature_modifiers(gltf, skin_id): + """Create Armature modifier.""" + pyskin = gltf.data.skins[skin_id] + + if pyskin.blender_armature_name is None: + # TODO seems something is wrong + # For example, some joints are in skin 0, and are in another skin too + # Not sure this is glTF compliant, will check it + return + + for node_id in pyskin.node_ids: + node = gltf.data.nodes[node_id] + obj = bpy.data.objects[node.blender_object] + + for obj_sel in bpy.context.scene.objects: + obj_sel.select_set(False) + obj.select_set(True) + bpy.context.view_layer.objects.active = obj + + # bpy.ops.object.parent_clear(type='CLEAR_KEEP_TRANSFORM') + # Reparent skinned mesh to it's armature to avoid breaking + # skinning with interleaved transforms + obj.parent = bpy.data.objects[pyskin.blender_armature_name] + arma = obj.modifiers.new(name="Armature", type="ARMATURE") + arma.object = bpy.data.objects[pyskin.blender_armature_name] + |