diff options
author | Julien Duroure <julien.duroure@gmail.com> | 2020-01-24 00:10:48 +0300 |
---|---|---|
committer | Julien Duroure <julien.duroure@gmail.com> | 2020-01-24 00:10:48 +0300 |
commit | 75855d723895e25da855087bccbd0266773bad15 (patch) | |
tree | c4d159ce2e5921f2d9239cc641c02746f173d5ed /io_scene_gltf2/blender/imp/gltf2_blender_node.py | |
parent | b3b274c5739de01685572032ac26ac5dcb50b950 (diff) |
glTF importer: fix skinning & hierarchy issues
See https://github.com/KhronosGroup/glTF-Blender-IO/pull/857 for details
Diffstat (limited to 'io_scene_gltf2/blender/imp/gltf2_blender_node.py')
-rwxr-xr-x | io_scene_gltf2/blender/imp/gltf2_blender_node.py | 322 |
1 files changed, 133 insertions, 189 deletions
diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_node.py b/io_scene_gltf2/blender/imp/gltf2_blender_node.py index a02514de..2f5d893a 100755 --- a/io_scene_gltf2/blender/imp/gltf2_blender_node.py +++ b/io_scene_gltf2/blender/imp/gltf2_blender_node.py @@ -13,13 +13,12 @@ # limitations under the License. import bpy +from mathutils import Vector from ..com.gltf2_blender_extras import set_extras from .gltf2_blender_mesh import BlenderMesh from .gltf2_blender_camera import BlenderCamera -from .gltf2_blender_skin import BlenderSkin from .gltf2_blender_light import BlenderLight -from ..com.gltf2_blender_conversion import scale_to_matrix, matrix_gltf_to_blender, correction_rotation - +from .gltf2_blender_vnode import VNode class BlenderNode(): """Blender Node.""" @@ -27,216 +26,161 @@ class BlenderNode(): raise RuntimeError("%s should not be instantiated" % cls) @staticmethod - def create(gltf, node_idx, parent): - """Node creation.""" - pynode = gltf.data.nodes[node_idx] - - # Blender attributes initialization - pynode.blender_object = "" - pynode.parent = parent + def create_vnode(gltf, vnode_id): + """Create VNode and all its descendants.""" + vnode = gltf.vnodes[vnode_id] + name = vnode.name gltf.display_current_node += 1 if bpy.app.debug_value == 101: - gltf.log.critical("Node " + str(gltf.display_current_node) + " of " + str(gltf.display_total_nodes) + " (idx " + str(node_idx) + ")") + gltf.log.critical("Node %d of %d (id %s)", gltf.display_current_node, len(gltf.vnodes), vnode_id) - if pynode.mesh is not None: + if vnode.type == VNode.Object: + BlenderNode.create_object(gltf, vnode_id) - instance = False - if gltf.data.meshes[pynode.mesh].blender_name is not None: - # Mesh is already created, only create instance - # Except is current node is animated with path weight - # Or if previous instance is animation at node level - if pynode.weight_animation is True: - instance = False - else: - if gltf.data.meshes[pynode.mesh].is_weight_animated is True: - instance = False - else: - instance = True - mesh = bpy.data.meshes[gltf.data.meshes[pynode.mesh].blender_name] - - if instance is False: - if pynode.name: - gltf.log.info("Blender create Mesh node " + pynode.name) - else: - gltf.log.info("Blender create Mesh node") + elif vnode.type == VNode.Bone: + BlenderNode.create_bone(gltf, vnode_id) - mesh = BlenderMesh.create(gltf, pynode.mesh, node_idx, parent) + elif vnode.type == VNode.DummyRoot: + # Don't actually create this + vnode.blender_object = None - if pynode.weight_animation is True: - # flag this mesh instance as created only for this node, because of weight animation - gltf.data.meshes[pynode.mesh].is_weight_animated = True + for child in vnode.children: + BlenderNode.create_vnode(gltf, child) - if pynode.name: - name = pynode.name - else: - # Take mesh name if exist - if gltf.data.meshes[pynode.mesh].name: - name = gltf.data.meshes[pynode.mesh].name - else: - name = "Object_" + str(node_idx) + @staticmethod + def create_object(gltf, vnode_id): + vnode = gltf.vnodes[vnode_id] + + if vnode.mesh_node_idx is not None: + pynode = gltf.data.nodes[vnode.mesh_node_idx] + obj = BlenderNode.create_mesh_object(gltf, pynode, name=vnode.name) + elif vnode.camera_node_idx is not None: + pynode = gltf.data.nodes[vnode.camera_node_idx] + obj = BlenderCamera.create(gltf, pynode.camera) + elif vnode.light_node_idx is not None: + pynode = gltf.data.nodes[vnode.light_node_idx] + obj = BlenderLight.create(gltf, pynode.extensions['KHR_lights_punctual']['light']) + elif vnode.is_arma: + armature = bpy.data.armatures.new(vnode.arma_name) + obj = bpy.data.objects.new(vnode.name, armature) + else: + obj = bpy.data.objects.new(vnode.name, None) + + vnode.blender_object = obj - obj = bpy.data.objects.new(name, mesh) + # Set extras (if came from a glTF node) + if isinstance(vnode_id, int): + pynode = gltf.data.nodes[vnode_id] set_extras(obj, pynode.extras) - obj.rotation_mode = 'QUATERNION' - if gltf.blender_active_collection is not None: - bpy.data.collections[gltf.blender_active_collection].objects.link(obj) - else: - bpy.data.scenes[gltf.blender_scene].collection.objects.link(obj) - # Transforms apply only if this mesh is not skinned - # See implementation node of gltf2 specification - if not (pynode.mesh is not None and pynode.skin is not None): - BlenderNode.set_transforms(gltf, node_idx, pynode, obj, parent) - pynode.blender_object = obj.name - BlenderNode.set_parent(gltf, obj, parent) + # Set transform + trans, rot, scale = vnode.trs + obj.location = trans + obj.rotation_mode = 'QUATERNION' + obj.rotation_quaternion = rot + obj.scale = scale - if instance == False: - BlenderMesh.set_mesh(gltf, gltf.data.meshes[pynode.mesh], mesh, obj) + # Set parent + if vnode.parent is not None: + parent_vnode = gltf.vnodes[vnode.parent] + if parent_vnode.type == VNode.Object: + obj.parent = parent_vnode.blender_object + elif parent_vnode.type == VNode.Bone: + arma_vnode = gltf.vnodes[parent_vnode.bone_arma] + obj.parent = arma_vnode.blender_object + obj.parent_type = 'BONE' + obj.parent_bone = parent_vnode.blender_bone_name - if pynode.children: - for child_idx in pynode.children: - BlenderNode.create(gltf, child_idx, node_idx) + # Nodes with a bone parent need to be translated + # backwards by their bone length (always 1 currently) + obj.location += Vector((0, -1, 0)) - return + bpy.data.scenes[gltf.blender_scene].collection.objects.link(obj) - if pynode.camera is not None: - if pynode.name: - gltf.log.info("Blender create Camera node " + pynode.name) - else: - gltf.log.info("Blender create Camera node") - obj = BlenderCamera.create(gltf, pynode.camera) - set_extras(obj, pynode.extras) - BlenderNode.set_transforms(gltf, node_idx, pynode, obj, parent) # TODO default rotation of cameras ? - pynode.blender_object = obj.name - BlenderNode.set_parent(gltf, obj, parent) + return obj - if pynode.children: - for child_idx in pynode.children: - BlenderNode.create(gltf, child_idx, node_idx) + @staticmethod + def create_bone(gltf, vnode_id): + vnode = gltf.vnodes[vnode_id] + blender_arma = gltf.vnodes[vnode.bone_arma].blender_object + armature = blender_arma.data + + # Switch into edit mode to create edit bone + if bpy.context.mode != 'OBJECT': + bpy.ops.object.mode_set(mode='OBJECT') + bpy.context.window.scene = bpy.data.scenes[gltf.blender_scene] + bpy.context.view_layer.objects.active = blender_arma + bpy.ops.object.mode_set(mode="EDIT") + editbone = armature.edit_bones.new(vnode.name) + vnode.blender_bone_name = editbone.name + + # Set extras (if came from a glTF node) + if isinstance(vnode_id, int): + pynode = gltf.data.nodes[vnode_id] + set_extras(editbone, pynode.extras) + + # TODO + editbone.use_connect = False + + # Give the position of the bone in armature space + arma_mat = vnode.bone_arma_mat + editbone.head = arma_mat @ Vector((0, 0, 0)) + editbone.tail = arma_mat @ Vector((0, 1, 0)) + editbone.align_roll(arma_mat @ Vector((0, 0, 1)) - editbone.head) + + # Set parent + parent_vnode = gltf.vnodes[vnode.parent] + if parent_vnode.type == VNode.Bone: + editbone.parent = armature.edit_bones[parent_vnode.blender_bone_name] + + bpy.ops.object.mode_set(mode="OBJECT") + pose_bone = blender_arma.pose.bones[vnode.blender_bone_name] + + # Put scale on the pose bone (can't go on the edit bone) + _, _, s = vnode.trs + pose_bone.scale = s + + if isinstance(vnode_id, int): + pynode = gltf.data.nodes[vnode_id] + set_extras(pose_bone, pynode.extras) - return + @staticmethod + def create_mesh_object(gltf, pynode, name): + instance = False + if gltf.data.meshes[pynode.mesh].blender_name.get(pynode.skin) is not None: + # Mesh is already created, only create instance + # Except is current node is animated with path weight + # Or if previous instance is animation at node level + if pynode.weight_animation is True: + instance = False + else: + if gltf.data.meshes[pynode.mesh].is_weight_animated is True: + instance = False + else: + instance = True + mesh = bpy.data.meshes[gltf.data.meshes[pynode.mesh].blender_name[pynode.skin]] - if pynode.is_joint: + if instance is False: if pynode.name: - gltf.log.info("Blender create Bone node " + pynode.name) + gltf.log.info("Blender create Mesh node " + pynode.name) else: - gltf.log.info("Blender create Bone node") - # Check if corresponding armature is already created, create it if needed - if gltf.data.skins[pynode.skin_id].blender_armature_name is None: - BlenderSkin.create_armature(gltf, pynode.skin_id, parent) - - BlenderSkin.create_bone(gltf, pynode.skin_id, node_idx, parent) - - if pynode.children: - for child_idx in pynode.children: - BlenderNode.create(gltf, child_idx, node_idx) - - return + gltf.log.info("Blender create Mesh node") - if pynode.extensions is not None: - if 'KHR_lights_punctual' in pynode.extensions.keys(): - obj = BlenderLight.create(gltf, pynode.extensions['KHR_lights_punctual']['light']) - set_extras(obj, pynode.extras) - obj.rotation_mode = 'QUATERNION' - BlenderNode.set_transforms(gltf, node_idx, pynode, obj, parent, correction=True) - pynode.blender_object = obj.name - pynode.correction_needed = True - BlenderNode.set_parent(gltf, obj, parent) + mesh = BlenderMesh.create(gltf, pynode.mesh, pynode.skin) - if pynode.children: - for child_idx in pynode.children: - BlenderNode.create(gltf, child_idx, node_idx) + if pynode.weight_animation is True: + # flag this mesh instance as created only for this node, because of weight animation + gltf.data.meshes[pynode.mesh].is_weight_animated = True - return + mesh_name = gltf.data.meshes[pynode.mesh].name + if not name and mesh_name: + name = mesh_name - # No mesh, no camera, no light. For now, create empty #TODO + obj = bpy.data.objects.new(name, mesh) - if pynode.name: - gltf.log.info("Blender create Empty node " + pynode.name) - obj = bpy.data.objects.new(pynode.name, None) - else: - gltf.log.info("Blender create Empty node") - obj = bpy.data.objects.new("Node", None) - set_extras(obj, pynode.extras) - obj.rotation_mode = 'QUATERNION' - if gltf.blender_active_collection is not None: - bpy.data.collections[gltf.blender_active_collection].objects.link(obj) - else: - bpy.data.scenes[gltf.blender_scene].collection.objects.link(obj) - - BlenderNode.set_transforms(gltf, node_idx, pynode, obj, parent) - pynode.blender_object = obj.name - BlenderNode.set_parent(gltf, obj, parent) - - if pynode.children: - for child_idx in pynode.children: - BlenderNode.create(gltf, child_idx, node_idx) + if instance == False: + BlenderMesh.set_mesh(gltf, gltf.data.meshes[pynode.mesh], obj) - @staticmethod - def set_parent(gltf, obj, parent): - """Set parent.""" - if parent is None: - return - - for node_idx, node in enumerate(gltf.data.nodes): - if node_idx == parent: - if node.is_joint is True: - bpy.ops.object.select_all(action='DESELECT') - bpy.data.objects[node.blender_armature_name].select_set(True) - bpy.context.view_layer.objects.active = bpy.data.objects[node.blender_armature_name] - - bpy.ops.object.mode_set(mode='EDIT') - bpy.data.objects[node.blender_armature_name].data.edit_bones.active = \ - bpy.data.objects[node.blender_armature_name].data.edit_bones[node.blender_bone_name] - bpy.ops.object.mode_set(mode='OBJECT') - bpy.ops.object.select_all(action='DESELECT') - obj.select_set(True) - bpy.data.objects[node.blender_armature_name].select_set(True) - bpy.context.view_layer.objects.active = bpy.data.objects[node.blender_armature_name] - bpy.context.view_layer.update() - bpy.ops.object.parent_set(type='BONE_RELATIVE', keep_transform=True) - # From world transform to local (-armature transform -bone transform) - bone_trans = bpy.data.objects[node.blender_armature_name] \ - .pose.bones[node.blender_bone_name].matrix.to_translation().copy() - bone_rot = bpy.data.objects[node.blender_armature_name] \ - .pose.bones[node.blender_bone_name].matrix.to_quaternion().copy() - bone_scale_mat = scale_to_matrix(node.blender_bone_matrix.to_scale()) - obj.location = bone_scale_mat @ obj.location - obj.location = bone_rot @ obj.location - obj.location += bone_trans - obj.location = bpy.data.objects[node.blender_armature_name].matrix_world.to_quaternion() \ - @ obj.location - obj.rotation_quaternion = obj.rotation_quaternion \ - @ bpy.data.objects[node.blender_armature_name].matrix_world.to_quaternion() - obj.scale = bone_scale_mat @ obj.scale - - return - if node.blender_object: - obj.parent = bpy.data.objects[node.blender_object] - return - - gltf.log.error("ERROR, parent not found") - - @staticmethod - def set_transforms(gltf, node_idx, pynode, obj, parent, correction=False): - """Set transforms.""" - if parent is None: - obj.matrix_world = matrix_gltf_to_blender(pynode.transform) - if correction is True: - obj.matrix_world = obj.matrix_world @ correction_rotation() - return - - for idx, node in enumerate(gltf.data.nodes): - if idx == parent: - if node.is_joint is True: - obj.matrix_world = matrix_gltf_to_blender(pynode.transform) - if correction is True: - obj.matrix_world = obj.matrix_world @ correction_rotation() - return - else: - if correction is True: - obj.matrix_world = obj.matrix_world @ correction_rotation() - obj.matrix_world = matrix_gltf_to_blender(pynode.transform) - return + return obj |