Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJulien Duroure <julien.duroure@gmail.com>2020-01-24 00:10:48 +0300
committerJulien Duroure <julien.duroure@gmail.com>2020-01-24 00:10:48 +0300
commit75855d723895e25da855087bccbd0266773bad15 (patch)
treec4d159ce2e5921f2d9239cc641c02746f173d5ed /io_scene_gltf2/blender/imp/gltf2_blender_node.py
parentb3b274c5739de01685572032ac26ac5dcb50b950 (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-xio_scene_gltf2/blender/imp/gltf2_blender_node.py322
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