glTF importer: fix skinning & hierarchy issues
See https://github.com/KhronosGroup/glTF-Blender-IO/pull/857 for details
This commit is contained in:
parent
b3b274c573
commit
75855d7238
|
@ -15,7 +15,7 @@
|
|||
bl_info = {
|
||||
'name': 'glTF 2.0 format',
|
||||
'author': 'Julien Duroure, Norbert Nopper, Urs Hanselmann, Moritz Becher, Benjamin Schmithüsen, Jim Eckerlein, and many external contributors',
|
||||
"version": (1, 2, 4),
|
||||
"version": (1, 2, 5),
|
||||
'blender': (2, 81, 6),
|
||||
'location': 'File > Import-Export',
|
||||
'description': 'Import-Export as glTF 2.0',
|
||||
|
|
|
@ -15,38 +15,6 @@
|
|||
from mathutils import Matrix, Quaternion
|
||||
from math import sqrt, sin, cos
|
||||
|
||||
def matrix_gltf_to_blender(mat_input):
|
||||
"""Matrix from glTF format to Blender format."""
|
||||
mat = Matrix([mat_input[0:4], mat_input[4:8], mat_input[8:12], mat_input[12:16]])
|
||||
mat.transpose()
|
||||
return mat
|
||||
|
||||
def loc_gltf_to_blender(loc):
|
||||
"""Location."""
|
||||
return loc
|
||||
|
||||
def scale_gltf_to_blender(scale):
|
||||
"""Scaling."""
|
||||
return scale
|
||||
|
||||
def quaternion_gltf_to_blender(q):
|
||||
"""Quaternion from glTF to Blender."""
|
||||
return Quaternion([q[3], q[0], q[1], q[2]])
|
||||
|
||||
def scale_to_matrix(scale):
|
||||
"""Scale to matrix."""
|
||||
mat = Matrix()
|
||||
for i in range(3):
|
||||
mat[i][i] = scale[i]
|
||||
|
||||
return mat
|
||||
|
||||
def correction_rotation():
|
||||
"""Correction of Rotation."""
|
||||
# Correction is needed for lamps, because Yup2Zup is not written in vertices
|
||||
# and lamps has no vertices :)
|
||||
return Quaternion((sqrt(2)/2, -sqrt(2)/2, 0.0, 0.0)).to_matrix().to_4x4()
|
||||
|
||||
def texture_transform_blender_to_gltf(mapping_transform):
|
||||
"""
|
||||
Converts the offset/rotation/scale from a Mapping node applied in Blender's
|
||||
|
|
|
@ -18,6 +18,7 @@ from .gltf2_blender_animation_bone import BlenderBoneAnim
|
|||
from .gltf2_blender_animation_node import BlenderNodeAnim
|
||||
from .gltf2_blender_animation_weight import BlenderWeightAnim
|
||||
from .gltf2_blender_animation_utils import restore_animation_on_object
|
||||
from .gltf2_blender_vnode import VNode
|
||||
|
||||
|
||||
class BlenderAnimation():
|
||||
|
@ -26,34 +27,36 @@ class BlenderAnimation():
|
|||
raise RuntimeError("%s should not be instantiated" % cls)
|
||||
|
||||
@staticmethod
|
||||
def anim(gltf, anim_idx, node_idx):
|
||||
def anim(gltf, anim_idx, vnode_id):
|
||||
"""Dispatch Animation to bone or object."""
|
||||
if gltf.data.nodes[node_idx].is_joint:
|
||||
BlenderBoneAnim.anim(gltf, anim_idx, node_idx)
|
||||
else:
|
||||
BlenderNodeAnim.anim(gltf, anim_idx, node_idx)
|
||||
BlenderWeightAnim.anim(gltf, anim_idx, node_idx)
|
||||
if isinstance(vnode_id, int):
|
||||
if gltf.vnodes[vnode_id].type == VNode.Bone:
|
||||
BlenderBoneAnim.anim(gltf, anim_idx, vnode_id)
|
||||
elif gltf.vnodes[vnode_id].type == VNode.Object:
|
||||
BlenderNodeAnim.anim(gltf, anim_idx, vnode_id)
|
||||
|
||||
if gltf.data.nodes[node_idx].children:
|
||||
for child in gltf.data.nodes[node_idx].children:
|
||||
BlenderAnimation.anim(gltf, anim_idx, child)
|
||||
BlenderWeightAnim.anim(gltf, anim_idx, vnode_id)
|
||||
|
||||
for child in gltf.vnodes[vnode_id].children:
|
||||
BlenderAnimation.anim(gltf, anim_idx, child)
|
||||
|
||||
@staticmethod
|
||||
def restore_animation(gltf, node_idx, animation_name):
|
||||
def restore_animation(gltf, vnode_id, animation_name):
|
||||
"""Restores the actions for an animation by its track name on
|
||||
the subtree starting at node_idx."""
|
||||
node = gltf.data.nodes[node_idx]
|
||||
vnode = gltf.vnodes[vnode_id]
|
||||
|
||||
if node.is_joint:
|
||||
obj = bpy.data.objects[gltf.data.skins[node.skin_id].blender_armature_name]
|
||||
else:
|
||||
obj = bpy.data.objects[node.blender_object]
|
||||
obj = None
|
||||
if vnode.type == VNode.Bone:
|
||||
obj = gltf.vnodes[vnode.bone_arma].blender_object
|
||||
elif vnode.type == VNode.Object:
|
||||
obj = vnode.blender_object
|
||||
|
||||
restore_animation_on_object(obj, animation_name)
|
||||
if obj.data and hasattr(obj.data, 'shape_keys'):
|
||||
restore_animation_on_object(obj.data.shape_keys, animation_name)
|
||||
if obj is not None:
|
||||
restore_animation_on_object(obj, animation_name)
|
||||
if obj.data and hasattr(obj.data, 'shape_keys'):
|
||||
restore_animation_on_object(obj.data.shape_keys, animation_name)
|
||||
|
||||
if gltf.data.nodes[node_idx].children:
|
||||
for child in gltf.data.nodes[node_idx].children:
|
||||
BlenderAnimation.restore_animation(gltf, child, animation_name)
|
||||
for child in gltf.vnodes[vnode_id].children:
|
||||
BlenderAnimation.restore_animation(gltf, child, animation_name)
|
||||
|
||||
|
|
|
@ -14,51 +14,56 @@
|
|||
|
||||
import json
|
||||
import bpy
|
||||
from mathutils import Matrix
|
||||
from mathutils import Vector
|
||||
|
||||
from ..com.gltf2_blender_conversion import loc_gltf_to_blender, quaternion_gltf_to_blender, scale_to_matrix
|
||||
from ...io.imp.gltf2_io_binary import BinaryData
|
||||
from .gltf2_blender_animation_utils import simulate_stash, make_fcurve
|
||||
|
||||
|
||||
# The glTF curves store the value of the final transform, but in Blender
|
||||
# curves animate a pose bone that is relative to the edit bone
|
||||
#
|
||||
# Final = EditBone * PoseBone
|
||||
# where
|
||||
# Final = Trans[ft] Rot[fr] Scale[fs]
|
||||
# EditBone = Trans[et] Rot[er] (edit bones have no scale)
|
||||
# PoseBone = Trans[pt] Rot[pr] Scale[ps]
|
||||
#
|
||||
# Solving for the PoseBone gives the change we need to apply to the curves
|
||||
#
|
||||
# pt = Rot[er^{-1}] (ft - et)
|
||||
# pr = er^{-1} fr
|
||||
# ps = fs
|
||||
|
||||
class BlenderBoneAnim():
|
||||
"""Blender Bone Animation."""
|
||||
def __new__(cls, *args, **kwargs):
|
||||
raise RuntimeError("%s should not be instantiated" % cls)
|
||||
|
||||
@staticmethod
|
||||
def parse_translation_channel(gltf, node, obj, bone, channel, animation):
|
||||
def parse_translation_channel(gltf, vnode, obj, bone, channel, animation):
|
||||
"""Manage Location animation."""
|
||||
blender_path = "pose.bones[" + json.dumps(bone.name) + "].location"
|
||||
group_name = bone.name
|
||||
|
||||
keys = BinaryData.get_data_from_accessor(gltf, animation.samplers[channel.sampler].input)
|
||||
values = BinaryData.get_data_from_accessor(gltf, animation.samplers[channel.sampler].output)
|
||||
inv_bind_matrix = node.blender_bone_matrix.to_quaternion().to_matrix().to_4x4().inverted() \
|
||||
@ Matrix.Translation(node.blender_bone_matrix.to_translation()).inverted()
|
||||
|
||||
if animation.samplers[channel.sampler].interpolation == "CUBICSPLINE":
|
||||
# TODO manage tangent?
|
||||
translation_keyframes = (
|
||||
loc_gltf_to_blender(values[idx * 3 + 1])
|
||||
gltf.loc_gltf_to_blender(values[idx * 3 + 1])
|
||||
for idx in range(0, len(keys))
|
||||
)
|
||||
else:
|
||||
translation_keyframes = (loc_gltf_to_blender(vals) for vals in values)
|
||||
if node.parent is None:
|
||||
parent_mat = Matrix()
|
||||
else:
|
||||
if not gltf.data.nodes[node.parent].is_joint:
|
||||
parent_mat = Matrix()
|
||||
else:
|
||||
parent_mat = gltf.data.nodes[node.parent].blender_bone_matrix
|
||||
translation_keyframes = (gltf.loc_gltf_to_blender(vals) for vals in values)
|
||||
|
||||
bind_trans, bind_rot, _ = vnode.trs
|
||||
bind_rot_inv = bind_rot.conjugated()
|
||||
|
||||
# Pose is in object (armature) space and it's value if the offset from the bind pose
|
||||
# (which is also in object space)
|
||||
# Scale is not taken into account
|
||||
final_translations = [
|
||||
inv_bind_matrix @ (parent_mat @ Matrix.Translation(translation_keyframe)).to_translation()
|
||||
for translation_keyframe in translation_keyframes
|
||||
bind_rot_inv @ (trans - bind_trans)
|
||||
for trans in translation_keyframes
|
||||
]
|
||||
|
||||
BlenderBoneAnim.fill_fcurves(
|
||||
|
@ -71,48 +76,31 @@ class BlenderBoneAnim():
|
|||
)
|
||||
|
||||
@staticmethod
|
||||
def parse_rotation_channel(gltf, node, obj, bone, channel, animation):
|
||||
def parse_rotation_channel(gltf, vnode, obj, bone, channel, animation):
|
||||
"""Manage rotation animation."""
|
||||
blender_path = "pose.bones[" + json.dumps(bone.name) + "].rotation_quaternion"
|
||||
group_name = bone.name
|
||||
|
||||
keys = BinaryData.get_data_from_accessor(gltf, animation.samplers[channel.sampler].input)
|
||||
values = BinaryData.get_data_from_accessor(gltf, animation.samplers[channel.sampler].output)
|
||||
bind_rotation = node.blender_bone_matrix.to_quaternion()
|
||||
|
||||
if animation.samplers[channel.sampler].interpolation == "CUBICSPLINE":
|
||||
# TODO manage tangent?
|
||||
quat_keyframes = [
|
||||
quaternion_gltf_to_blender(values[idx * 3 + 1])
|
||||
gltf.quaternion_gltf_to_blender(values[idx * 3 + 1])
|
||||
for idx in range(0, len(keys))
|
||||
]
|
||||
else:
|
||||
quat_keyframes = [quaternion_gltf_to_blender(vals) for vals in values]
|
||||
quat_keyframes = [gltf.quaternion_gltf_to_blender(vals) for vals in values]
|
||||
|
||||
_, bind_rot, _ = vnode.trs
|
||||
bind_rot_inv = bind_rot.conjugated()
|
||||
|
||||
|
||||
if node.parent is None:
|
||||
final_rots = [
|
||||
bind_rotation.inverted() @ quat_keyframe
|
||||
for quat_keyframe in quat_keyframes
|
||||
]
|
||||
else:
|
||||
if not gltf.data.nodes[node.parent].is_joint:
|
||||
parent_mat = Matrix()
|
||||
else:
|
||||
parent_mat = gltf.data.nodes[node.parent].blender_bone_matrix
|
||||
|
||||
if parent_mat != parent_mat.inverted():
|
||||
final_rots = [
|
||||
bind_rotation.rotation_difference(
|
||||
(parent_mat @ quat_keyframe.to_matrix().to_4x4()).to_quaternion()
|
||||
)
|
||||
for quat_keyframe in quat_keyframes
|
||||
]
|
||||
else:
|
||||
final_rots = [
|
||||
bind_rotation.rotation_difference(quat_keyframe)
|
||||
for quat_keyframe in quat_keyframes
|
||||
]
|
||||
final_rots = [
|
||||
bind_rot_inv @ rot
|
||||
for rot in quat_keyframes
|
||||
]
|
||||
|
||||
# Manage antipodal quaternions
|
||||
for i in range(1, len(final_rots)):
|
||||
|
@ -129,38 +117,22 @@ class BlenderBoneAnim():
|
|||
)
|
||||
|
||||
@staticmethod
|
||||
def parse_scale_channel(gltf, node, obj, bone, channel, animation):
|
||||
def parse_scale_channel(gltf, vnode, obj, bone, channel, animation):
|
||||
"""Manage scaling animation."""
|
||||
blender_path = "pose.bones[" + json.dumps(bone.name) + "].scale"
|
||||
group_name = bone.name
|
||||
|
||||
keys = BinaryData.get_data_from_accessor(gltf, animation.samplers[channel.sampler].input)
|
||||
values = BinaryData.get_data_from_accessor(gltf, animation.samplers[channel.sampler].output)
|
||||
bind_scale = scale_to_matrix(node.blender_bone_matrix.to_scale())
|
||||
|
||||
if animation.samplers[channel.sampler].interpolation == "CUBICSPLINE":
|
||||
# TODO manage tangent?
|
||||
scale_mats = (
|
||||
scale_to_matrix(loc_gltf_to_blender(values[idx * 3 + 1]))
|
||||
final_scales = [
|
||||
gltf.scale_gltf_to_blender(values[idx * 3 + 1])
|
||||
for idx in range(0, len(keys))
|
||||
)
|
||||
else:
|
||||
scale_mats = (scale_to_matrix(loc_gltf_to_blender(vals)) for vals in values)
|
||||
if node.parent is None:
|
||||
final_scales = [
|
||||
(bind_scale.inverted() @ scale_mat).to_scale()
|
||||
for scale_mat in scale_mats
|
||||
]
|
||||
else:
|
||||
if not gltf.data.nodes[node.parent].is_joint:
|
||||
parent_mat = Matrix()
|
||||
else:
|
||||
parent_mat = gltf.data.nodes[node.parent].blender_bone_matrix
|
||||
|
||||
final_scales = [
|
||||
(bind_scale.inverted() @ scale_to_matrix(parent_mat.to_scale()) @ scale_mat).to_scale()
|
||||
for scale_mat in scale_mats
|
||||
]
|
||||
final_scales = [gltf.scale_gltf_to_blender(vals) for vals in values]
|
||||
|
||||
BlenderBoneAnim.fill_fcurves(
|
||||
obj.animation_data.action,
|
||||
|
@ -194,21 +166,21 @@ class BlenderBoneAnim():
|
|||
def anim(gltf, anim_idx, node_idx):
|
||||
"""Manage animation."""
|
||||
node = gltf.data.nodes[node_idx]
|
||||
blender_armature_name = gltf.data.skins[node.skin_id].blender_armature_name
|
||||
obj = bpy.data.objects[blender_armature_name]
|
||||
bone = obj.pose.bones[node.blender_bone_name]
|
||||
vnode = gltf.vnodes[node_idx]
|
||||
obj = gltf.vnodes[vnode.bone_arma].blender_object
|
||||
bone = obj.pose.bones[vnode.blender_bone_name]
|
||||
|
||||
if anim_idx not in node.animations.keys():
|
||||
return
|
||||
|
||||
animation = gltf.data.animations[anim_idx]
|
||||
|
||||
action = gltf.arma_cache.get(blender_armature_name)
|
||||
action = gltf.action_cache.get(obj.name)
|
||||
if not action:
|
||||
name = animation.track_name + "_" + obj.name
|
||||
action = bpy.data.actions.new(name)
|
||||
gltf.needs_stash.append((obj, animation.track_name, action))
|
||||
gltf.arma_cache[blender_armature_name] = action
|
||||
gltf.action_cache[obj.name] = action
|
||||
|
||||
if not obj.animation_data:
|
||||
obj.animation_data_create()
|
||||
|
@ -218,11 +190,11 @@ class BlenderBoneAnim():
|
|||
channel = animation.channels[channel_idx]
|
||||
|
||||
if channel.target.path == "translation":
|
||||
BlenderBoneAnim.parse_translation_channel(gltf, node, obj, bone, channel, animation)
|
||||
BlenderBoneAnim.parse_translation_channel(gltf, vnode, obj, bone, channel, animation)
|
||||
|
||||
elif channel.target.path == "rotation":
|
||||
BlenderBoneAnim.parse_rotation_channel(gltf, node, obj, bone, channel, animation)
|
||||
BlenderBoneAnim.parse_rotation_channel(gltf, vnode, obj, bone, channel, animation)
|
||||
|
||||
elif channel.target.path == "scale":
|
||||
BlenderBoneAnim.parse_scale_channel(gltf, node, obj, bone, channel, animation)
|
||||
BlenderBoneAnim.parse_scale_channel(gltf, vnode, obj, bone, channel, animation)
|
||||
|
||||
|
|
|
@ -15,10 +15,9 @@
|
|||
import bpy
|
||||
from mathutils import Vector
|
||||
|
||||
from ..com.gltf2_blender_conversion import loc_gltf_to_blender, quaternion_gltf_to_blender, scale_gltf_to_blender
|
||||
from ..com.gltf2_blender_conversion import correction_rotation
|
||||
from ...io.imp.gltf2_io_binary import BinaryData
|
||||
from .gltf2_blender_animation_utils import simulate_stash, make_fcurve
|
||||
from .gltf2_blender_vnode import VNode
|
||||
|
||||
|
||||
class BlenderNodeAnim():
|
||||
|
@ -30,7 +29,8 @@ class BlenderNodeAnim():
|
|||
def anim(gltf, anim_idx, node_idx):
|
||||
"""Manage animation."""
|
||||
node = gltf.data.nodes[node_idx]
|
||||
obj = bpy.data.objects[node.blender_object]
|
||||
vnode = gltf.vnodes[node_idx]
|
||||
obj = vnode.blender_object
|
||||
fps = bpy.context.scene.render.fps
|
||||
|
||||
animation = gltf.data.animations[anim_idx]
|
||||
|
@ -45,9 +45,12 @@ class BlenderNodeAnim():
|
|||
else:
|
||||
return
|
||||
|
||||
name = animation.track_name + "_" + obj.name
|
||||
action = bpy.data.actions.new(name)
|
||||
gltf.needs_stash.append((obj, animation.track_name, action))
|
||||
action = gltf.action_cache.get(obj.name)
|
||||
if not action:
|
||||
name = animation.track_name + "_" + obj.name
|
||||
action = bpy.data.actions.new(name)
|
||||
gltf.needs_stash.append((obj, animation.track_name, action))
|
||||
gltf.action_cache[obj.name] = action
|
||||
|
||||
if not obj.animation_data:
|
||||
obj.animation_data_create()
|
||||
|
@ -62,10 +65,6 @@ class BlenderNodeAnim():
|
|||
if channel.target.path not in ['translation', 'rotation', 'scale']:
|
||||
continue
|
||||
|
||||
# There is an animation on object
|
||||
# We can't remove Yup2Zup object
|
||||
gltf.animation_object = True
|
||||
|
||||
if animation.samplers[channel.sampler].interpolation == "CUBICSPLINE":
|
||||
# TODO manage tangent?
|
||||
values = [values[idx * 3 + 1] for idx in range(0, len(keys))]
|
||||
|
@ -74,20 +73,20 @@ class BlenderNodeAnim():
|
|||
blender_path = "location"
|
||||
group_name = "Location"
|
||||
num_components = 3
|
||||
values = [loc_gltf_to_blender(vals) for vals in values]
|
||||
|
||||
if vnode.parent is not None and gltf.vnodes[vnode.parent].type == VNode.Bone:
|
||||
# Nodes with a bone parent need to be translated
|
||||
# backwards by their bone length (always 1 currently)
|
||||
off = Vector((0, -1, 0))
|
||||
values = [gltf.loc_gltf_to_blender(vals) + off for vals in values]
|
||||
else:
|
||||
values = [gltf.loc_gltf_to_blender(vals) for vals in values]
|
||||
|
||||
elif channel.target.path == "rotation":
|
||||
blender_path = "rotation_quaternion"
|
||||
group_name = "Rotation"
|
||||
num_components = 4
|
||||
if node.correction_needed is True:
|
||||
values = [
|
||||
(quaternion_gltf_to_blender(vals).to_matrix().to_4x4() @ correction_rotation()).to_quaternion()
|
||||
for vals in values
|
||||
]
|
||||
else:
|
||||
values = [quaternion_gltf_to_blender(vals) for vals in values]
|
||||
|
||||
values = [gltf.quaternion_gltf_to_blender(vals) for vals in values]
|
||||
|
||||
# Manage antipodal quaternions
|
||||
for i in range(1, len(values)):
|
||||
|
@ -98,7 +97,7 @@ class BlenderNodeAnim():
|
|||
blender_path = "scale"
|
||||
group_name = "Scale"
|
||||
num_components = 3
|
||||
values = [scale_gltf_to_blender(vals) for vals in values]
|
||||
values = [gltf.scale_gltf_to_blender(vals) for vals in values]
|
||||
|
||||
coords = [0] * (2 * len(keys))
|
||||
coords[::2] = (key[0] * fps for key in keys)
|
||||
|
|
|
@ -25,10 +25,16 @@ class BlenderWeightAnim():
|
|||
raise RuntimeError("%s should not be instantiated" % cls)
|
||||
|
||||
@staticmethod
|
||||
def anim(gltf, anim_idx, node_idx):
|
||||
def anim(gltf, anim_idx, vnode_id):
|
||||
"""Manage animation."""
|
||||
vnode = gltf.vnodes[vnode_id]
|
||||
|
||||
node_idx = vnode.mesh_node_idx
|
||||
if node_idx is None:
|
||||
return
|
||||
|
||||
node = gltf.data.nodes[node_idx]
|
||||
obj = bpy.data.objects[node.blender_object]
|
||||
obj = vnode.blender_object
|
||||
fps = bpy.context.scene.render.fps
|
||||
|
||||
animation = gltf.data.animations[anim_idx]
|
||||
|
|
|
@ -44,9 +44,5 @@ class BlenderCamera():
|
|||
cam.clip_end = pycamera.zfar
|
||||
|
||||
obj = bpy.data.objects.new(pycamera.name, cam)
|
||||
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)
|
||||
return obj
|
||||
|
||||
|
|
|
@ -13,8 +13,8 @@
|
|||
# limitations under the License.
|
||||
|
||||
import bpy
|
||||
from mathutils import Vector, Quaternion, Matrix
|
||||
from .gltf2_blender_scene import BlenderScene
|
||||
from ...io.com.gltf2_io_trs import TRS
|
||||
|
||||
|
||||
class BlenderGlTF():
|
||||
|
@ -25,79 +25,52 @@ class BlenderGlTF():
|
|||
@staticmethod
|
||||
def create(gltf):
|
||||
"""Create glTF main method."""
|
||||
if bpy.context.scene.render.engine not in ['CYCLES', 'BLENDER_EEVEE']:
|
||||
bpy.context.scene.render.engine = 'BLENDER_EEVEE'
|
||||
BlenderGlTF.set_convert_functions(gltf)
|
||||
BlenderGlTF.pre_compute(gltf)
|
||||
BlenderScene.create(gltf)
|
||||
|
||||
@staticmethod
|
||||
def set_convert_functions(gltf):
|
||||
yup2zup = bpy.app.debug_value != 100
|
||||
|
||||
if yup2zup:
|
||||
# glTF Y-Up space --> Blender Z-up space
|
||||
# X,Y,Z --> X,-Z,Y
|
||||
def convert_loc(x): return Vector([x[0], -x[2], x[1]])
|
||||
def convert_quat(q): return Quaternion([q[3], q[0], -q[2], q[1]])
|
||||
def convert_normal(n): return Vector([n[0], -n[2], n[1]])
|
||||
def convert_scale(s): return Vector([s[0], s[2], s[1]])
|
||||
def convert_matrix(m):
|
||||
return Matrix([
|
||||
[ m[0], -m[ 8], m[4], m[12]],
|
||||
[-m[2], m[10], -m[6], -m[14]],
|
||||
[ m[1], -m[ 9], m[5], m[13]],
|
||||
[ m[3], -m[11], m[7], m[15]],
|
||||
])
|
||||
|
||||
# Correction for cameras and lights.
|
||||
# glTF: right = +X, forward = -Z, up = +Y
|
||||
# glTF after Yup2Zup: right = +X, forward = +Y, up = +Z
|
||||
# Blender: right = +X, forward = -Z, up = +Y
|
||||
# Need to carry Blender --> glTF after Yup2Zup
|
||||
gltf.camera_correction = Quaternion((2**0.5/2, 2**0.5/2, 0.0, 0.0))
|
||||
|
||||
gltf.display_current_node = 0
|
||||
if gltf.data.nodes is not None:
|
||||
gltf.display_total_nodes = len(gltf.data.nodes)
|
||||
else:
|
||||
gltf.display_total_nodes = "?"
|
||||
def convert_loc(x): return Vector(x)
|
||||
def convert_quat(q): return Quaternion([q[3], q[0], q[1], q[2]])
|
||||
def convert_normal(n): return Vector(n)
|
||||
def convert_scale(s): return Vector(s)
|
||||
def convert_matrix(m):
|
||||
return Matrix([m[0::4], m[1::4], m[2::4], m[3::4]])
|
||||
|
||||
active_object_name_at_end = None
|
||||
if gltf.data.scenes is not None:
|
||||
for scene_idx, scene in enumerate(gltf.data.scenes):
|
||||
BlenderScene.create(gltf, scene_idx)
|
||||
# keep active object name if needed (to be able to set as active object at end)
|
||||
if gltf.data.scene is not None:
|
||||
if scene_idx == gltf.data.scene:
|
||||
active_object_name_at_end = bpy.context.view_layer.objects.active.name
|
||||
else:
|
||||
if scene_idx == 0:
|
||||
active_object_name_at_end = bpy.context.view_layer.objects.active.name
|
||||
else:
|
||||
# special case where there is no scene in glTF file
|
||||
# generate all objects in current scene
|
||||
BlenderScene.create(gltf, None)
|
||||
active_object_name_at_end = bpy.context.view_layer.objects.active.name
|
||||
# Same convention, no correction needed.
|
||||
gltf.camera_correction = None
|
||||
|
||||
# Armature correction
|
||||
# Try to detect bone chains, and set bone lengths
|
||||
# To detect if a bone is in a chain, we try to detect if a bone head is aligned
|
||||
# with parent_bone :
|
||||
# Parent bone defined a line (between head & tail)
|
||||
# Bone head defined a point
|
||||
# Calcul of distance between point and line
|
||||
# If < threshold --> In a chain
|
||||
# Based on an idea of @Menithal, but added alignment detection to avoid some bad cases
|
||||
|
||||
threshold = 0.001
|
||||
for armobj in [obj for obj in bpy.data.objects if obj.type == "ARMATURE"]:
|
||||
# Take into account only armature from this scene
|
||||
if armobj.name not in bpy.context.view_layer.objects:
|
||||
continue
|
||||
bpy.context.view_layer.objects.active = armobj
|
||||
armature = armobj.data
|
||||
bpy.ops.object.mode_set(mode="EDIT")
|
||||
for bone in armature.edit_bones:
|
||||
if bone.parent is None:
|
||||
continue
|
||||
|
||||
parent = bone.parent
|
||||
|
||||
# case where 2 bones are aligned (not in chain, same head)
|
||||
if (bone.head - parent.head).length < threshold:
|
||||
continue
|
||||
|
||||
u = (parent.tail - parent.head).normalized()
|
||||
point = bone.head
|
||||
distance = ((point - parent.head).cross(u)).length / u.length
|
||||
if distance < threshold:
|
||||
save_parent_direction = (parent.tail - parent.head).normalized().copy()
|
||||
save_parent_tail = parent.tail.copy()
|
||||
parent.tail = bone.head
|
||||
|
||||
# case where 2 bones are aligned (not in chain, same head)
|
||||
# bone is no more is same direction
|
||||
if (parent.tail - parent.head).normalized().dot(save_parent_direction) < 0.9:
|
||||
parent.tail = save_parent_tail
|
||||
|
||||
bpy.ops.object.mode_set(mode="OBJECT")
|
||||
|
||||
# Set active object
|
||||
if active_object_name_at_end is not None:
|
||||
bpy.context.view_layer.objects.active = bpy.data.objects[active_object_name_at_end]
|
||||
gltf.loc_gltf_to_blender = convert_loc
|
||||
gltf.quaternion_gltf_to_blender = convert_quat
|
||||
gltf.normal_gltf_to_blender = convert_normal
|
||||
gltf.scale_gltf_to_blender = convert_scale
|
||||
gltf.matrix_gltf_to_blender = convert_matrix
|
||||
|
||||
@staticmethod
|
||||
def pre_compute(gltf):
|
||||
|
@ -208,45 +181,6 @@ class BlenderGlTF():
|
|||
# Lights management
|
||||
node.correction_needed = False
|
||||
|
||||
# transform management
|
||||
if node.matrix:
|
||||
node.transform = node.matrix
|
||||
continue
|
||||
|
||||
# No matrix, but TRS
|
||||
mat = [1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0] # init
|
||||
|
||||
if node.scale:
|
||||
mat = TRS.scale_to_matrix(node.scale)
|
||||
|
||||
if node.rotation:
|
||||
q_mat = TRS.quaternion_to_matrix(node.rotation)
|
||||
mat = TRS.matrix_multiply(q_mat, mat)
|
||||
|
||||
if node.translation:
|
||||
loc_mat = TRS.translation_to_matrix(node.translation)
|
||||
mat = TRS.matrix_multiply(loc_mat, mat)
|
||||
|
||||
node.transform = mat
|
||||
|
||||
|
||||
# joint management
|
||||
for node_idx, node in enumerate(gltf.data.nodes):
|
||||
is_joint, skin_idx = gltf.is_node_joint(node_idx)
|
||||
if is_joint:
|
||||
node.is_joint = True
|
||||
node.skin_id = skin_idx
|
||||
else:
|
||||
node.is_joint = False
|
||||
|
||||
if gltf.data.skins:
|
||||
for skin_id, skin in enumerate(gltf.data.skins):
|
||||
# init blender values
|
||||
skin.blender_armature_name = None
|
||||
# if skin.skeleton and skin.skeleton not in skin.joints:
|
||||
# gltf.data.nodes[skin.skeleton].is_joint = True
|
||||
# gltf.data.nodes[skin.skeleton].skin_id = skin_id
|
||||
|
||||
# Dispatch animation
|
||||
if gltf.data.animations:
|
||||
for node_idx, node in enumerate(gltf.data.nodes):
|
||||
|
@ -274,7 +208,7 @@ class BlenderGlTF():
|
|||
# Meshes
|
||||
if gltf.data.meshes:
|
||||
for mesh in gltf.data.meshes:
|
||||
mesh.blender_name = None
|
||||
mesh.blender_name = {} # cache Blender mesh (keyed by skin_idx)
|
||||
mesh.is_weight_animated = False
|
||||
|
||||
# Calculate names for each mesh's shapekeys
|
||||
|
|
|
@ -42,11 +42,6 @@ class BlenderLight():
|
|||
|
||||
# TODO range
|
||||
|
||||
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)
|
||||
|
||||
set_extras(obj.data, pylight.get('extras'))
|
||||
|
||||
return obj
|
||||
|
|
|
@ -20,7 +20,6 @@ from ..com.gltf2_blender_extras import set_extras
|
|||
from .gltf2_blender_material import BlenderMaterial
|
||||
from .gltf2_blender_primitive import BlenderPrimitive
|
||||
from ...io.imp.gltf2_io_binary import BinaryData
|
||||
from ..com.gltf2_blender_conversion import loc_gltf_to_blender
|
||||
|
||||
|
||||
class BlenderMesh():
|
||||
|
@ -29,7 +28,7 @@ class BlenderMesh():
|
|||
raise RuntimeError("%s should not be instantiated" % cls)
|
||||
|
||||
@staticmethod
|
||||
def create(gltf, mesh_idx, node_idx, parent):
|
||||
def create(gltf, mesh_idx, skin_idx):
|
||||
"""Mesh creation."""
|
||||
pymesh = gltf.data.meshes[mesh_idx]
|
||||
|
||||
|
@ -64,7 +63,7 @@ class BlenderMesh():
|
|||
materials.append(material.name)
|
||||
material_idx = len(materials) - 1
|
||||
|
||||
BlenderPrimitive.add_primitive_to_bmesh(gltf, bme, pymesh, prim, material_idx)
|
||||
BlenderPrimitive.add_primitive_to_bmesh(gltf, bme, pymesh, prim, skin_idx, material_idx)
|
||||
|
||||
name = pymesh.name or 'Mesh_' + str(mesh_idx)
|
||||
mesh = bpy.data.meshes.new(name)
|
||||
|
@ -76,7 +75,7 @@ class BlenderMesh():
|
|||
|
||||
set_extras(mesh, pymesh.extras, exclude=['targetNames'])
|
||||
|
||||
pymesh.blender_name = mesh.name
|
||||
pymesh.blender_name[skin_idx] = mesh.name
|
||||
|
||||
# Clear accessor cache after all primitives are done
|
||||
gltf.accessor_cache = {}
|
||||
|
@ -84,7 +83,7 @@ class BlenderMesh():
|
|||
return mesh
|
||||
|
||||
@staticmethod
|
||||
def set_mesh(gltf, pymesh, mesh, obj):
|
||||
def set_mesh(gltf, pymesh, obj):
|
||||
"""Sets mesh data after creation."""
|
||||
# set default weights for shape keys, and names, if not set by convention on extras data
|
||||
if pymesh.weights is not None:
|
||||
|
|
|
@ -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:
|
||||
elif vnode.type == VNode.Bone:
|
||||
BlenderNode.create_bone(gltf, vnode_id)
|
||||
|
||||
elif vnode.type == VNode.DummyRoot:
|
||||
# Don't actually create this
|
||||
vnode.blender_object = None
|
||||
|
||||
for child in vnode.children:
|
||||
BlenderNode.create_vnode(gltf, child)
|
||||
|
||||
@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
|
||||
|
||||
# Set extras (if came from a glTF node)
|
||||
if isinstance(vnode_id, int):
|
||||
pynode = gltf.data.nodes[vnode_id]
|
||||
set_extras(obj, pynode.extras)
|
||||
|
||||
# Set transform
|
||||
trans, rot, scale = vnode.trs
|
||||
obj.location = trans
|
||||
obj.rotation_mode = 'QUATERNION'
|
||||
obj.rotation_quaternion = rot
|
||||
obj.scale = scale
|
||||
|
||||
# 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
|
||||
|
||||
# Nodes with a bone parent need to be translated
|
||||
# backwards by their bone length (always 1 currently)
|
||||
obj.location += Vector((0, -1, 0))
|
||||
|
||||
bpy.data.scenes[gltf.blender_scene].collection.objects.link(obj)
|
||||
|
||||
return obj
|
||||
|
||||
@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)
|
||||
|
||||
@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:
|
||||
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")
|
||||
|
||||
mesh = BlenderMesh.create(gltf, pynode.mesh, node_idx, parent)
|
||||
|
||||
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
|
||||
instance = True
|
||||
mesh = bpy.data.meshes[gltf.data.meshes[pynode.mesh].blender_name[pynode.skin]]
|
||||
|
||||
if instance is False:
|
||||
if pynode.name:
|
||||
name = pynode.name
|
||||
gltf.log.info("Blender create Mesh node " + 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)
|
||||
gltf.log.info("Blender create Mesh node")
|
||||
|
||||
obj = bpy.data.objects.new(name, mesh)
|
||||
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)
|
||||
mesh = BlenderMesh.create(gltf, pynode.mesh, pynode.skin)
|
||||
|
||||
# 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)
|
||||
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
|
||||
|
||||
if instance == False:
|
||||
BlenderMesh.set_mesh(gltf, gltf.data.meshes[pynode.mesh], mesh, obj)
|
||||
mesh_name = gltf.data.meshes[pynode.mesh].name
|
||||
if not name and mesh_name:
|
||||
name = mesh_name
|
||||
|
||||
if pynode.children:
|
||||
for child_idx in pynode.children:
|
||||
BlenderNode.create(gltf, child_idx, node_idx)
|
||||
obj = bpy.data.objects.new(name, mesh)
|
||||
|
||||
return
|
||||
if instance == False:
|
||||
BlenderMesh.set_mesh(gltf, gltf.data.meshes[pynode.mesh], 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)
|
||||
|
||||
if pynode.children:
|
||||
for child_idx in pynode.children:
|
||||
BlenderNode.create(gltf, child_idx, node_idx)
|
||||
|
||||
return
|
||||
|
||||
if pynode.is_joint:
|
||||
if pynode.name:
|
||||
gltf.log.info("Blender create Bone 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
|
||||
|
||||
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)
|
||||
|
||||
if pynode.children:
|
||||
for child_idx in pynode.children:
|
||||
BlenderNode.create(gltf, child_idx, node_idx)
|
||||
|
||||
return
|
||||
|
||||
# No mesh, no camera, no light. For now, create empty #TODO
|
||||
|
||||
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)
|
||||
|
||||
@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
|
||||
|
||||
|
|
|
@ -13,10 +13,9 @@
|
|||
# limitations under the License.
|
||||
|
||||
import bpy
|
||||
from mathutils import Vector
|
||||
from mathutils import Vector, Matrix
|
||||
|
||||
from .gltf2_blender_material import BlenderMaterial
|
||||
from ..com.gltf2_blender_conversion import loc_gltf_to_blender
|
||||
from ...io.imp.gltf2_io_binary import BinaryData
|
||||
from ...io.com.gltf2_io_color_management import color_linear_to_srgb
|
||||
from ...io.com import gltf2_io_debug
|
||||
|
@ -37,7 +36,7 @@ class BlenderPrimitive():
|
|||
return bme_layers[name]
|
||||
|
||||
@staticmethod
|
||||
def add_primitive_to_bmesh(gltf, bme, pymesh, pyprimitive, material_index):
|
||||
def add_primitive_to_bmesh(gltf, bme, pymesh, pyprimitive, skin_idx, material_index):
|
||||
attributes = pyprimitive.attributes
|
||||
|
||||
if 'POSITION' not in attributes:
|
||||
|
@ -57,6 +56,62 @@ class BlenderPrimitive():
|
|||
bme_edges = bme.edges
|
||||
bme_faces = bme.faces
|
||||
|
||||
# Gather up the joints/weights (multiple sets allow >4 influences)
|
||||
joint_sets = []
|
||||
weight_sets = []
|
||||
set_num = 0
|
||||
while 'JOINTS_%d' % set_num in attributes and 'WEIGHTS_%d' % set_num in attributes:
|
||||
joint_data = BinaryData.get_data_from_accessor(gltf, attributes['JOINTS_%d' % set_num], cache=True)
|
||||
weight_data = BinaryData.get_data_from_accessor(gltf, attributes['WEIGHTS_%d' % set_num], cache=True)
|
||||
|
||||
joint_sets.append(joint_data)
|
||||
weight_sets.append(weight_data)
|
||||
|
||||
set_num += 1
|
||||
|
||||
# For skinned meshes, we will need to calculate the position of the
|
||||
# verts in the bind pose, ie. the pose the edit bones are in.
|
||||
if skin_idx is not None:
|
||||
pyskin = gltf.data.skins[skin_idx]
|
||||
if pyskin.inverse_bind_matrices is not None:
|
||||
inv_binds = BinaryData.get_data_from_accessor(gltf, pyskin.inverse_bind_matrices)
|
||||
inv_binds = [gltf.matrix_gltf_to_blender(m) for m in inv_binds]
|
||||
else:
|
||||
inv_binds = [Matrix.Identity(4) for i in range(len(pyskin.joints))]
|
||||
arma_mats = [gltf.vnodes[joint].bone_arma_mat for joint in pyskin.joints]
|
||||
joint_mats = [arma_mat @ inv_bind for arma_mat, inv_bind in zip(arma_mats, inv_binds)]
|
||||
|
||||
def skin_vert(pos, pidx):
|
||||
out = Vector((0, 0, 0))
|
||||
# Spec says weights should already sum to 1 but some models
|
||||
# don't do it (ex. CesiumMan), so normalize.
|
||||
weight_sum = 0
|
||||
for joint_set, weight_set in zip(joint_sets, weight_sets):
|
||||
for j in range(0, 4):
|
||||
weight = weight_set[pidx][j]
|
||||
if weight != 0.0:
|
||||
weight_sum += weight
|
||||
joint = joint_set[pidx][j]
|
||||
out += weight * (joint_mats[joint] @ pos)
|
||||
out /= weight_sum
|
||||
return out
|
||||
|
||||
def skin_normal(norm, pidx):
|
||||
# TODO: not sure this is right
|
||||
norm = Vector([norm[0], norm[1], norm[2], 0])
|
||||
out = Vector((0, 0, 0, 0))
|
||||
weight_sum = 0
|
||||
for joint_set, weight_set in zip(joint_sets, weight_sets):
|
||||
for j in range(0, 4):
|
||||
weight = weight_set[pidx][j]
|
||||
if weight != 0.0:
|
||||
weight_sum += weight
|
||||
joint = joint_set[pidx][j]
|
||||
out += weight * (joint_mats[joint] @ norm)
|
||||
out /= weight_sum
|
||||
out = out.to_3d().normalized()
|
||||
return out
|
||||
|
||||
# Every vertex has an index into the primitive's attribute arrays and a
|
||||
# *different* index into the BMesh's list of verts. Call the first one the
|
||||
# pidx and the second the bidx. Need to keep them straight!
|
||||
|
@ -74,7 +129,11 @@ class BlenderPrimitive():
|
|||
used_pidxs = list(used_pidxs)
|
||||
used_pidxs.sort()
|
||||
for pidx in used_pidxs:
|
||||
bme_verts.new(positions[pidx])
|
||||
pos = gltf.loc_gltf_to_blender(positions[pidx])
|
||||
if skin_idx is not None:
|
||||
pos = skin_vert(pos, pidx)
|
||||
|
||||
bme_verts.new(pos)
|
||||
vert_idxs.append((bidx, pidx))
|
||||
pidx_to_bidx[pidx] = bidx
|
||||
bidx += 1
|
||||
|
@ -114,8 +173,13 @@ class BlenderPrimitive():
|
|||
if 'NORMAL' in attributes:
|
||||
normals = BinaryData.get_data_from_accessor(gltf, attributes['NORMAL'], cache=True)
|
||||
|
||||
for bidx, pidx in vert_idxs:
|
||||
bme_verts[bidx].normal = normals[pidx]
|
||||
if skin_idx is None:
|
||||
for bidx, pidx in vert_idxs:
|
||||
bme_verts[bidx].normal = gltf.normal_gltf_to_blender(normals[pidx])
|
||||
else:
|
||||
for bidx, pidx in vert_idxs:
|
||||
normal = gltf.normal_gltf_to_blender(normals[pidx])
|
||||
bme_verts[bidx].normal = skin_normal(normal, pidx)
|
||||
|
||||
# Set vertex colors. Add them in the order COLOR_0, COLOR_1, etc.
|
||||
set_num = 0
|
||||
|
@ -176,19 +240,7 @@ class BlenderPrimitive():
|
|||
|
||||
set_num += 1
|
||||
|
||||
# Set joints/weights for skinning (multiple sets allow > 4 influences)
|
||||
joint_sets = []
|
||||
weight_sets = []
|
||||
set_num = 0
|
||||
while 'JOINTS_%d' % set_num in attributes and 'WEIGHTS_%d' % set_num in attributes:
|
||||
joint_data = BinaryData.get_data_from_accessor(gltf, attributes['JOINTS_%d' % set_num], cache=True)
|
||||
weight_data = BinaryData.get_data_from_accessor(gltf, attributes['WEIGHTS_%d' % set_num], cache=True)
|
||||
|
||||
joint_sets.append(joint_data)
|
||||
weight_sets.append(weight_data)
|
||||
|
||||
set_num += 1
|
||||
|
||||
# Set joints/weights for skinning
|
||||
if joint_sets:
|
||||
layer = BlenderPrimitive.get_layer(bme.verts.layers.deform, 'Vertex Weights')
|
||||
|
||||
|
@ -210,11 +262,19 @@ class BlenderPrimitive():
|
|||
|
||||
morph_positions = BinaryData.get_data_from_accessor(gltf, target['POSITION'], cache=True)
|
||||
|
||||
for bidx, pidx in vert_idxs:
|
||||
bme_verts[bidx][layer] = (
|
||||
Vector(positions[pidx]) +
|
||||
Vector(morph_positions[pidx])
|
||||
)
|
||||
if skin_idx is None:
|
||||
for bidx, pidx in vert_idxs:
|
||||
bme_verts[bidx][layer] = (
|
||||
gltf.loc_gltf_to_blender(positions[pidx]) +
|
||||
gltf.loc_gltf_to_blender(morph_positions[pidx])
|
||||
)
|
||||
else:
|
||||
for bidx, pidx in vert_idxs:
|
||||
pos = (
|
||||
gltf.loc_gltf_to_blender(positions[pidx]) +
|
||||
gltf.loc_gltf_to_blender(morph_positions[pidx])
|
||||
)
|
||||
bme_verts[bidx][layer] = skin_vert(pos, pidx)
|
||||
|
||||
@staticmethod
|
||||
def edges_and_faces(mode, indices):
|
||||
|
|
|
@ -19,6 +19,7 @@ from .gltf2_blender_node import BlenderNode
|
|||
from .gltf2_blender_skin import BlenderSkin
|
||||
from .gltf2_blender_animation import BlenderAnimation
|
||||
from .gltf2_blender_animation_utils import simulate_stash
|
||||
from .gltf2_blender_vnode import VNode, compute_vnodes
|
||||
|
||||
|
||||
class BlenderScene():
|
||||
|
@ -27,168 +28,67 @@ class BlenderScene():
|
|||
raise RuntimeError("%s should not be instantiated" % cls)
|
||||
|
||||
@staticmethod
|
||||
def create(gltf, scene_idx):
|
||||
def create(gltf):
|
||||
"""Scene creation."""
|
||||
gltf.blender_active_collection = None
|
||||
if scene_idx is not None:
|
||||
pyscene = gltf.data.scenes[scene_idx]
|
||||
list_nodes = pyscene.nodes
|
||||
scene = bpy.context.scene
|
||||
gltf.blender_scene = scene.name
|
||||
if bpy.context.collection.name in bpy.data.collections: # avoid master collection
|
||||
gltf.blender_active_collection = bpy.context.collection.name
|
||||
if scene.render.engine not in ['CYCLES', 'BLENDER_EEVEE']:
|
||||
scene.render.engine = "BLENDER_EEVEE"
|
||||
|
||||
# Create a new scene only if not already exists in .blend file
|
||||
# TODO : put in current scene instead ?
|
||||
if pyscene.name not in [scene.name for scene in bpy.data.scenes]:
|
||||
# TODO: There is a bug in 2.8 alpha that break CLEAR_KEEP_TRANSFORM
|
||||
# if we are creating a new scene
|
||||
scene = bpy.context.scene
|
||||
if bpy.context.collection.name in bpy.data.collections: # avoid master collection
|
||||
gltf.blender_active_collection = bpy.context.collection.name
|
||||
if scene.render.engine not in ['CYCLES', 'BLENDER_EEVEE']:
|
||||
scene.render.engine = "BLENDER_EEVEE"
|
||||
compute_vnodes(gltf)
|
||||
|
||||
gltf.blender_scene = scene.name
|
||||
else:
|
||||
gltf.blender_scene = pyscene.name
|
||||
|
||||
# Switch to newly created main scene
|
||||
bpy.context.window.scene = bpy.data.scenes[gltf.blender_scene]
|
||||
if bpy.context.collection.name in bpy.data.collections: # avoid master collection
|
||||
gltf.blender_active_collection = bpy.context.collection.name
|
||||
|
||||
else:
|
||||
# No scene in glTF file, create all objects in current scene
|
||||
scene = bpy.context.scene
|
||||
if scene.render.engine not in ['CYCLES', 'BLENDER_EEVEE']:
|
||||
scene.render.engine = "BLENDER_EEVEE"
|
||||
if bpy.context.collection.name in bpy.data.collections: # avoid master collection
|
||||
gltf.blender_active_collection = bpy.context.collection.name
|
||||
gltf.blender_scene = scene.name
|
||||
list_nodes = BlenderScene.get_root_nodes(gltf)
|
||||
|
||||
if bpy.app.debug_value != 100:
|
||||
# Create Yup2Zup empty
|
||||
obj_rotation = bpy.data.objects.new("Yup2Zup", None)
|
||||
obj_rotation.rotation_mode = 'QUATERNION'
|
||||
obj_rotation.rotation_quaternion = Quaternion((sqrt(2) / 2, sqrt(2) / 2, 0.0, 0.0))
|
||||
|
||||
if gltf.blender_active_collection is not None:
|
||||
bpy.data.collections[gltf.blender_active_collection].objects.link(obj_rotation)
|
||||
else:
|
||||
bpy.data.scenes[gltf.blender_scene].collection.objects.link(obj_rotation)
|
||||
|
||||
if list_nodes is not None:
|
||||
for node_idx in list_nodes:
|
||||
BlenderNode.create(gltf, node_idx, None) # None => No parent
|
||||
gltf.display_current_node = 0 # for debugging
|
||||
BlenderNode.create_vnode(gltf, 'root')
|
||||
|
||||
# Now that all mesh / bones are created, create vertex groups on mesh
|
||||
if gltf.data.skins:
|
||||
for skin_id, skin in enumerate(gltf.data.skins):
|
||||
if hasattr(skin, "node_ids"):
|
||||
BlenderSkin.create_vertex_groups(gltf, skin_id)
|
||||
BlenderSkin.create_vertex_groups(gltf)
|
||||
BlenderSkin.create_armature_modifiers(gltf)
|
||||
|
||||
for skin_id, skin in enumerate(gltf.data.skins):
|
||||
if hasattr(skin, "node_ids"):
|
||||
BlenderSkin.create_armature_modifiers(gltf, skin_id)
|
||||
BlenderScene.create_animations(gltf)
|
||||
|
||||
if bpy.context.mode != 'OBJECT':
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
BlenderScene.set_active_object(gltf)
|
||||
|
||||
@staticmethod
|
||||
def create_animations(gltf):
|
||||
"""Create animations."""
|
||||
if gltf.data.animations:
|
||||
for anim_idx, anim in enumerate(gltf.data.animations):
|
||||
# Blender armature name -> action all its bones should use
|
||||
gltf.arma_cache = {}
|
||||
# Caches the action for each object (keyed by object name)
|
||||
gltf.action_cache = {}
|
||||
# Things we need to stash when we're done.
|
||||
gltf.needs_stash = []
|
||||
|
||||
if list_nodes is not None:
|
||||
for node_idx in list_nodes:
|
||||
BlenderAnimation.anim(gltf, anim_idx, node_idx)
|
||||
BlenderAnimation.anim(gltf, anim_idx, 'root')
|
||||
|
||||
for (obj, anim_name, action) in gltf.needs_stash:
|
||||
simulate_stash(obj, anim_name, action)
|
||||
|
||||
# Restore first animation
|
||||
anim_name = gltf.data.animations[0].track_name
|
||||
for node_idx in list_nodes:
|
||||
BlenderAnimation.restore_animation(gltf, node_idx, anim_name)
|
||||
|
||||
if bpy.app.debug_value != 100:
|
||||
# Parent root node to rotation object
|
||||
if list_nodes is not None:
|
||||
exclude_nodes = []
|
||||
for node_idx in list_nodes:
|
||||
if gltf.data.nodes[node_idx].is_joint:
|
||||
# Do not change parent if root node is already parented (can be the case for skinned mesh)
|
||||
if not bpy.data.objects[gltf.data.nodes[node_idx].blender_armature_name].parent:
|
||||
bpy.data.objects[gltf.data.nodes[node_idx].blender_armature_name].parent = obj_rotation
|
||||
else:
|
||||
exclude_nodes.append(node_idx)
|
||||
else:
|
||||
# Do not change parent if root node is already parented (can be the case for skinned mesh)
|
||||
if not bpy.data.objects[gltf.data.nodes[node_idx].blender_object].parent:
|
||||
bpy.data.objects[gltf.data.nodes[node_idx].blender_object].parent = obj_rotation
|
||||
else:
|
||||
exclude_nodes.append(node_idx)
|
||||
|
||||
if gltf.animation_object is False:
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# Avoid rotation bug if collection is hidden or disabled
|
||||
if gltf.blender_active_collection is not None:
|
||||
gltf.collection_hide_viewport = bpy.data.collections[gltf.blender_active_collection].hide_viewport
|
||||
bpy.data.collections[gltf.blender_active_collection].hide_viewport = False
|
||||
# TODO for visibility ... but seems not exposed on bpy for now
|
||||
|
||||
for node_idx in list_nodes:
|
||||
|
||||
if node_idx in exclude_nodes:
|
||||
continue # for root node that are parented by the process
|
||||
# for example skinned meshes
|
||||
|
||||
for obj_ in bpy.context.scene.objects:
|
||||
obj_.select_set(False)
|
||||
if gltf.data.nodes[node_idx].is_joint:
|
||||
bpy.data.objects[gltf.data.nodes[node_idx].blender_armature_name].select_set(True)
|
||||
bpy.context.view_layer.objects.active = bpy.data.objects[gltf.data.nodes[node_idx].blender_armature_name]
|
||||
|
||||
else:
|
||||
bpy.data.objects[gltf.data.nodes[node_idx].blender_object].select_set(True)
|
||||
bpy.context.view_layer.objects.active = bpy.data.objects[gltf.data.nodes[node_idx].blender_object]
|
||||
|
||||
bpy.ops.object.parent_clear(type='CLEAR_KEEP_TRANSFORM')
|
||||
|
||||
# remove object
|
||||
#bpy.context.scene.collection.objects.unlink(obj_rotation)
|
||||
bpy.data.objects.remove(obj_rotation)
|
||||
|
||||
# Restore collection hidden / disabled values
|
||||
if gltf.blender_active_collection is not None:
|
||||
bpy.data.collections[gltf.blender_active_collection].hide_viewport = gltf.collection_hide_viewport
|
||||
# TODO restore visibility when expose in bpy
|
||||
|
||||
# Make first root object the new active one
|
||||
if list_nodes is not None:
|
||||
if gltf.data.nodes[list_nodes[0]].blender_object:
|
||||
bl_name = gltf.data.nodes[list_nodes[0]].blender_object
|
||||
else:
|
||||
bl_name = gltf.data.nodes[list_nodes[0]].blender_armature_name
|
||||
bpy.context.view_layer.objects.active = bpy.data.objects[bl_name]
|
||||
BlenderAnimation.restore_animation(gltf, 'root', anim_name)
|
||||
|
||||
@staticmethod
|
||||
def get_root_nodes(gltf):
|
||||
if gltf.data.nodes is None:
|
||||
return None
|
||||
def set_active_object(gltf):
|
||||
"""Make the first root object from the default glTF scene active.
|
||||
If no default scene, use the first scene, or just any root object.
|
||||
"""
|
||||
if gltf.data.scenes:
|
||||
pyscene = gltf.data.scenes[gltf.data.scene or 0]
|
||||
vnode = gltf.vnodes[pyscene.nodes[0]]
|
||||
if gltf.vnodes[vnode.parent].type != VNode.DummyRoot:
|
||||
vnode = gltf.vnodes[vnode.parent]
|
||||
|
||||
parents = {}
|
||||
for idx, node in enumerate(gltf.data.nodes):
|
||||
pynode = gltf.data.nodes[idx]
|
||||
if pynode.children:
|
||||
for child_idx in pynode.children:
|
||||
parents[child_idx] = idx
|
||||
else:
|
||||
vnode = gltf.vnodes['root']
|
||||
if vnode.type == VNode.DummyRoot:
|
||||
if not vnode.children:
|
||||
return # no nodes
|
||||
vnode = gltf.vnodes[vnode.children[0]]
|
||||
|
||||
roots = []
|
||||
for idx, node in enumerate(gltf.data.nodes):
|
||||
if idx not in parents.keys():
|
||||
roots.append(idx)
|
||||
|
||||
return roots
|
||||
bpy.context.view_layer.objects.active = vnode.blender_object
|
||||
|
||||
|
|
|
@ -14,10 +14,6 @@
|
|||
|
||||
|
||||
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
|
||||
from ..com.gltf2_blender_extras import set_extras
|
||||
|
||||
class BlenderSkin():
|
||||
"""Blender Skinning / Armature."""
|
||||
|
@ -25,154 +21,36 @@ class BlenderSkin():
|
|||
raise RuntimeError("%s should not be instantiated" % cls)
|
||||
|
||||
@staticmethod
|
||||
def create_armature(gltf, skin_id, parent):
|
||||
"""Armature creation."""
|
||||
pyskin = gltf.data.skins[skin_id]
|
||||
def create_vertex_groups(gltf):
|
||||
"""Create vertex groups for all skinned meshes."""
|
||||
for vnode in gltf.vnodes.values():
|
||||
if vnode.mesh_node_idx is None:
|
||||
continue
|
||||
pynode = gltf.data.nodes[vnode.mesh_node_idx]
|
||||
if pynode.skin is None:
|
||||
continue
|
||||
pyskin = gltf.data.skins[pynode.skin]
|
||||
|
||||
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)
|
||||
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)
|
||||
|
||||
pyskin.blender_armature_name = obj.name
|
||||
if parent is not None:
|
||||
obj.parent = bpy.data.objects[gltf.data.nodes[parent].blender_object]
|
||||
obj = vnode.blender_object
|
||||
for node_idx in pyskin.joints:
|
||||
bone = gltf.vnodes[node_idx]
|
||||
obj.vertex_groups.new(name=bone.blender_bone_name)
|
||||
|
||||
@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]
|
||||
def create_armature_modifiers(gltf):
|
||||
"""Create Armature modifiers for all skinned meshes."""
|
||||
for vnode in gltf.vnodes.values():
|
||||
if vnode.mesh_node_idx is None:
|
||||
continue
|
||||
pynode = gltf.data.nodes[vnode.mesh_node_idx]
|
||||
if pynode.skin is None:
|
||||
continue
|
||||
pyskin = gltf.data.skins[pynode.skin]
|
||||
|
||||
obj = bpy.data.objects[pyskin.blender_armature_name]
|
||||
first_bone = gltf.vnodes[pyskin.joints[0]]
|
||||
arma = gltf.vnodes[first_bone.bone_arma]
|
||||
|
||||
# Set bone bind_pose by inverting bindpose matrix
|
||||
if node_id in pyskin.joints:
|
||||
index_in_skel = pyskin.joints.index(node_id)
|
||||
if pyskin.inverse_bind_matrices is not None:
|
||||
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:
|
||||
pynode.blender_bone_matrix = Matrix() # 4x4 identity matrix
|
||||
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 & scale
|
||||
obj.pose.bones[pynode.blender_bone_name].rotation_quaternion = \
|
||||
(pynode.blender_bone_matrix.inverted() @ parent_mat @
|
||||
matrix_gltf_to_blender(pynode.transform)).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 / obj.matrix_world.to_scale()[1], 0.0)) # Needed to keep bone alive
|
||||
# Custom prop on edit bone
|
||||
set_extras(bone, pynode.extras)
|
||||
|
||||
# set bind and pose transforms
|
||||
BlenderSkin.set_bone_transforms(gltf, skin_id, bone, node_id, parent)
|
||||
bpy.ops.object.mode_set(mode="OBJECT")
|
||||
# Custom prop on pose bone
|
||||
if pynode.blender_bone_name in obj.pose.bones:
|
||||
set_extras(obj.pose.bones[pynode.blender_bone_name], pynode.extras)
|
||||
|
||||
@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 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]
|
||||
obj = vnode.blender_object
|
||||
mod = obj.modifiers.new(name="Armature", type="ARMATURE")
|
||||
mod.object = arma.blender_object
|
||||
|
||||
|
|
|
@ -0,0 +1,324 @@
|
|||
# Copyright 2018-2019 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, Quaternion, Matrix
|
||||
|
||||
def compute_vnodes(gltf):
|
||||
"""Computes the tree of virtual nodes.
|
||||
Copies the glTF nodes into a tree of VNodes, then performs a series of
|
||||
passes to transform it into a form that we can import into Blender.
|
||||
"""
|
||||
init_vnodes(gltf)
|
||||
mark_bones_and_armas(gltf)
|
||||
move_skinned_meshes(gltf)
|
||||
fixup_multitype_nodes(gltf)
|
||||
correct_cameras_and_lights(gltf)
|
||||
calc_bone_matrices(gltf)
|
||||
|
||||
|
||||
class VNode:
|
||||
"""A "virtual" node.
|
||||
These are what eventually get turned into nodes
|
||||
in the Blender scene.
|
||||
"""
|
||||
# Types
|
||||
Object = 0
|
||||
Bone = 1
|
||||
DummyRoot = 2
|
||||
|
||||
def __init__(self):
|
||||
self.name = ''
|
||||
self.children = []
|
||||
self.parent = None
|
||||
self.type = VNode.Object
|
||||
self.is_arma = False
|
||||
self.trs = (
|
||||
Vector((0, 0, 0)),
|
||||
Quaternion((1, 0, 0, 0)),
|
||||
Vector((1, 1, 1)),
|
||||
)
|
||||
# Indices of the glTF node where the mesh, etc. came from.
|
||||
# (They can get moved around.)
|
||||
self.mesh_node_idx = None
|
||||
self.camera_node_idx = None
|
||||
self.light_node_idx = None
|
||||
|
||||
|
||||
def init_vnodes(gltf):
|
||||
# Map of all VNodes. The keys are arbitrary IDs.
|
||||
# Nodes coming from glTF use the index into gltf.data.nodes for an ID.
|
||||
gltf.vnodes = {}
|
||||
|
||||
for i, pynode in enumerate(gltf.data.nodes or []):
|
||||
vnode = VNode()
|
||||
gltf.vnodes[i] = vnode
|
||||
vnode.name = pynode.name or 'Node_%d' % i
|
||||
vnode.children = list(pynode.children or [])
|
||||
vnode.trs = get_node_trs(gltf, pynode)
|
||||
if pynode.mesh is not None:
|
||||
vnode.mesh_node_idx = i
|
||||
if pynode.camera is not None:
|
||||
vnode.camera_node_idx = i
|
||||
if 'KHR_lights_punctual' in (pynode.extensions or {}):
|
||||
vnode.light_node_idx = i
|
||||
|
||||
for id in gltf.vnodes:
|
||||
for child in gltf.vnodes[id].children:
|
||||
assert gltf.vnodes[child].parent is None
|
||||
gltf.vnodes[child].parent = id
|
||||
|
||||
# Inserting a root node will simplify things.
|
||||
roots = [id for id in gltf.vnodes if gltf.vnodes[id].parent is None]
|
||||
gltf.vnodes['root'] = VNode()
|
||||
gltf.vnodes['root'].type = VNode.DummyRoot
|
||||
gltf.vnodes['root'].name = 'Root'
|
||||
gltf.vnodes['root'].children = roots
|
||||
for root in roots:
|
||||
gltf.vnodes[root].parent = 'root'
|
||||
|
||||
def get_node_trs(gltf, pynode):
|
||||
if pynode.matrix is not None:
|
||||
m = gltf.matrix_gltf_to_blender(pynode.matrix)
|
||||
return m.decompose()
|
||||
|
||||
t = gltf.loc_gltf_to_blender(pynode.translation or [0, 0, 0])
|
||||
r = gltf.quaternion_gltf_to_blender(pynode.rotation or [0, 0, 0, 1])
|
||||
s = gltf.scale_gltf_to_blender(pynode.scale or [1, 1, 1])
|
||||
return t, r, s
|
||||
|
||||
|
||||
def mark_bones_and_armas(gltf):
|
||||
"""
|
||||
Mark nodes as armatures so that every node that is used as joint is a
|
||||
descendant of an armature. Mark everything between an armature and a
|
||||
joint as a bone.
|
||||
"""
|
||||
for skin in gltf.data.skins or []:
|
||||
descendants = list(skin.joints)
|
||||
if skin.skeleton is not None:
|
||||
descendants.append(skin.skeleton)
|
||||
arma_id = deepest_common_ancestor(gltf, descendants)
|
||||
|
||||
if arma_id in skin.joints:
|
||||
arma_id = gltf.vnodes[arma_id].parent
|
||||
|
||||
if gltf.vnodes[arma_id].type != VNode.Bone:
|
||||
gltf.vnodes[arma_id].type = VNode.Object
|
||||
gltf.vnodes[arma_id].is_arma = True
|
||||
gltf.vnodes[arma_id].arma_name = skin.name or 'Armature'
|
||||
|
||||
for joint in skin.joints:
|
||||
while joint != arma_id:
|
||||
gltf.vnodes[joint].type = VNode.Bone
|
||||
gltf.vnodes[joint].is_arma = False
|
||||
joint = gltf.vnodes[joint].parent
|
||||
|
||||
# Mark the armature each bone is a descendant of.
|
||||
|
||||
def visit(vnode_id, cur_arma): # Depth-first walk
|
||||
vnode = gltf.vnodes[vnode_id]
|
||||
|
||||
if vnode.is_arma:
|
||||
cur_arma = vnode_id
|
||||
elif vnode.type == VNode.Bone:
|
||||
vnode.bone_arma = cur_arma
|
||||
else:
|
||||
cur_arma = None
|
||||
|
||||
for child in vnode.children:
|
||||
visit(child, cur_arma)
|
||||
|
||||
visit('root', cur_arma=None)
|
||||
|
||||
def deepest_common_ancestor(gltf, vnode_ids):
|
||||
"""Find the deepest (improper) ancestor of a set of vnodes."""
|
||||
path_to_ancestor = [] # path to deepest ancestor so far
|
||||
for vnode_id in vnode_ids:
|
||||
path = path_from_root(gltf, vnode_id)
|
||||
if not path_to_ancestor:
|
||||
path_to_ancestor = path
|
||||
else:
|
||||
path_to_ancestor = longest_common_prefix(path, path_to_ancestor)
|
||||
return path_to_ancestor[-1]
|
||||
|
||||
def path_from_root(gltf, vnode_id):
|
||||
"""Returns the ids of all vnodes from the root to vnode_id."""
|
||||
path = []
|
||||
while vnode_id is not None:
|
||||
path.append(vnode_id)
|
||||
vnode_id = gltf.vnodes[vnode_id].parent
|
||||
path.reverse()
|
||||
return path
|
||||
|
||||
def longest_common_prefix(list1, list2):
|
||||
i = 0
|
||||
while i != min(len(list1), len(list2)):
|
||||
if list1[i] != list2[i]:
|
||||
break
|
||||
i += 1
|
||||
return list1[:i]
|
||||
|
||||
|
||||
def move_skinned_meshes(gltf):
|
||||
"""
|
||||
In glTF, where in the node hierarchy a skinned mesh is instantiated has
|
||||
no effect on its world space position: only the world transforms of the
|
||||
joints in its skin affect it.
|
||||
|
||||
To do this in Blender:
|
||||
* Move a skinned mesh to become a child of the armature that affects it
|
||||
* When we do mesh creation, we will also need to put all the verts in
|
||||
their rest pose (ie. the pose the edit bones are in)
|
||||
"""
|
||||
# TODO: this leaves behind empty "husk" nodes where the skinned meshes
|
||||
# used to be, which is ugly.
|
||||
ids = list(gltf.vnodes.keys())
|
||||
for id in ids:
|
||||
vnode = gltf.vnodes[id]
|
||||
|
||||
if vnode.mesh_node_idx is None:
|
||||
continue
|
||||
|
||||
mesh = gltf.data.nodes[vnode.mesh_node_idx].mesh
|
||||
skin = gltf.data.nodes[vnode.mesh_node_idx].skin
|
||||
if skin is None:
|
||||
continue
|
||||
|
||||
pyskin = gltf.data.skins[skin]
|
||||
arma = gltf.vnodes[pyskin.joints[0]].bone_arma
|
||||
|
||||
new_id = str(id) + '.skinned'
|
||||
gltf.vnodes[new_id] = VNode()
|
||||
gltf.vnodes[new_id].name = gltf.data.meshes[mesh].name or 'Mesh_%d' % mesh
|
||||
gltf.vnodes[new_id].parent = arma
|
||||
gltf.vnodes[arma].children.append(new_id)
|
||||
|
||||
gltf.vnodes[new_id].mesh_node_idx = vnode.mesh_node_idx
|
||||
vnode.mesh_node_idx = None
|
||||
|
||||
|
||||
def fixup_multitype_nodes(gltf):
|
||||
"""
|
||||
Blender only lets each object have one of: an armature, a mesh, a
|
||||
camera, a light. Also bones cannot have any of these either. Find any
|
||||
nodes like this and move the mesh/camera/light onto new children.
|
||||
"""
|
||||
ids = list(gltf.vnodes.keys())
|
||||
for id in ids:
|
||||
vnode = gltf.vnodes[id]
|
||||
|
||||
needs_move = False
|
||||
|
||||
if vnode.is_arma or vnode.type == VNode.Bone:
|
||||
needs_move = True
|
||||
|
||||
if vnode.mesh_node_idx is not None:
|
||||
if needs_move:
|
||||
new_id = str(id) + '.mesh'
|
||||
gltf.vnodes[new_id] = VNode()
|
||||
gltf.vnodes[new_id].name = vnode.name + ' Mesh'
|
||||
gltf.vnodes[new_id].mesh_node_idx = vnode.mesh_node_idx
|
||||
gltf.vnodes[new_id].parent = id
|
||||
vnode.children.append(new_id)
|
||||
vnode.mesh_node_idx = None
|
||||
needs_move = True
|
||||
|
||||
if vnode.camera_node_idx is not None:
|
||||
if needs_move:
|
||||
new_id = str(id) + '.camera'
|
||||
gltf.vnodes[new_id] = VNode()
|
||||
gltf.vnodes[new_id].name = vnode.name + ' Camera'
|
||||
gltf.vnodes[new_id].camera_node_idx = vnode.camera_node_idx
|
||||
gltf.vnodes[new_id].parent = id
|
||||
vnode.children.append(new_id)
|
||||
vnode.camera_node_idx = None
|
||||
needs_move = True
|
||||
|
||||
if vnode.light_node_idx is not None:
|
||||
if needs_move:
|
||||
new_id = str(id) + '.light'
|
||||
gltf.vnodes[new_id] = VNode()
|
||||
gltf.vnodes[new_id].name = vnode.name + ' Light'
|
||||
gltf.vnodes[new_id].light_node_idx = vnode.light_node_idx
|
||||
gltf.vnodes[new_id].parent = id
|
||||
vnode.children.append(new_id)
|
||||
vnode.light_node_idx = None
|
||||
needs_move = True
|
||||
|
||||
|
||||
def correct_cameras_and_lights(gltf):
|
||||
"""
|
||||
Depending on the coordinate change, lights and cameras might need to be
|
||||
rotated to match Blender conventions for which axes they point along.
|
||||
"""
|
||||
if gltf.camera_correction is None:
|
||||
return
|
||||
|
||||
trs = (Vector((0, 0, 0)), gltf.camera_correction, Vector((1, 1, 1)))
|
||||
|
||||
ids = list(gltf.vnodes.keys())
|
||||
for id in ids:
|
||||
vnode = gltf.vnodes[id]
|
||||
|
||||
# Move the camera/light onto a new child and set its rotation
|
||||
# TODO: "hard apply" the rotation without creating a new node
|
||||
# (like we'll need to do for bones)
|
||||
|
||||
if vnode.camera_node_idx is not None:
|
||||
new_id = str(id) + '.camera-correction'
|
||||
gltf.vnodes[new_id] = VNode()
|
||||
gltf.vnodes[new_id].name = vnode.name + ' Correction'
|
||||
gltf.vnodes[new_id].trs = trs
|
||||
gltf.vnodes[new_id].camera_node_idx = vnode.camera_node_idx
|
||||
gltf.vnodes[new_id].parent = id
|
||||
vnode.children.append(new_id)
|
||||
vnode.camera_node_idx = None
|
||||
|
||||
if vnode.light_node_idx is not None:
|
||||
new_id = str(id) + '.light-correction'
|
||||
gltf.vnodes[new_id] = VNode()
|
||||
gltf.vnodes[new_id].name = vnode.name + ' Correction'
|
||||
gltf.vnodes[new_id].trs = trs
|
||||
gltf.vnodes[new_id].light_node_idx = vnode.light_node_idx
|
||||
gltf.vnodes[new_id].parent = id
|
||||
vnode.children.append(new_id)
|
||||
vnode.light_node_idx = None
|
||||
|
||||
|
||||
def calc_bone_matrices(gltf):
|
||||
"""
|
||||
Calculate bone_arma_mat, the transformation from bone space to armature
|
||||
space for the edit bone, for each bone.
|
||||
"""
|
||||
def visit(vnode_id): # Depth-first walk
|
||||
vnode = gltf.vnodes[vnode_id]
|
||||
if vnode.type == VNode.Bone:
|
||||
if gltf.vnodes[vnode.parent].type == VNode.Bone:
|
||||
parent_arma_mat = gltf.vnodes[vnode.parent].bone_arma_mat
|
||||
else:
|
||||
parent_arma_mat = Matrix.Identity(4)
|
||||
|
||||
t, r, _ = vnode.trs
|
||||
local_to_parent = Matrix.Translation(t) @ Quaternion(r).to_matrix().to_4x4()
|
||||
vnode.bone_arma_mat = parent_arma_mat @ local_to_parent
|
||||
|
||||
for child in vnode.children:
|
||||
visit(child)
|
||||
|
||||
visit('root')
|
||||
|
||||
|
||||
# TODO: add pass to rotate/resize bones so they look pretty
|
||||
|
|
@ -1,68 +0,0 @@
|
|||
# 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.
|
||||
|
||||
|
||||
class TRS:
|
||||
|
||||
def __new__(cls, *args, **kwargs):
|
||||
raise RuntimeError("{} should not be instantiated".format(cls.__name__))
|
||||
|
||||
@staticmethod
|
||||
def scale_to_matrix(scale):
|
||||
# column major !
|
||||
return [scale[0], 0, 0, 0,
|
||||
0, scale[1], 0, 0,
|
||||
0, 0, scale[2], 0,
|
||||
0, 0, 0, 1]
|
||||
|
||||
@staticmethod
|
||||
def quaternion_to_matrix(q):
|
||||
x, y, z, w = q
|
||||
# TODO : is q normalized ? --> if not, multiply by 1/(w*w + x*x + y*y + z*z)
|
||||
# column major !
|
||||
return [
|
||||
1 - 2 * y * y - 2 * z * z, 2 * x * y + 2 * w * z, 2 * x * z - 2 * w * y, 0,
|
||||
2 * x * y - 2 * w * z, 1 - 2 * x * x - 2 * z * z, 2 * y * z + 2 * w * x, 0,
|
||||
2 * x * z + 2 * y * w, 2 * y * z - 2 * w * x, 1 - 2 * x * x - 2 * y * y, 0,
|
||||
0, 0, 0, 1]
|
||||
|
||||
@staticmethod
|
||||
def matrix_multiply(m, n):
|
||||
# column major !
|
||||
|
||||
return [
|
||||
m[0] * n[0] + m[4] * n[1] + m[8] * n[2] + m[12] * n[3],
|
||||
m[1] * n[0] + m[5] * n[1] + m[9] * n[2] + m[13] * n[3],
|
||||
m[2] * n[0] + m[6] * n[1] + m[10] * n[2] + m[14] * n[3],
|
||||
m[3] * n[0] + m[7] * n[1] + m[11] * n[2] + m[15] * n[3],
|
||||
m[0] * n[4] + m[4] * n[5] + m[8] * n[6] + m[12] * n[7],
|
||||
m[1] * n[4] + m[5] * n[5] + m[9] * n[6] + m[13] * n[7],
|
||||
m[2] * n[4] + m[6] * n[5] + m[10] * n[6] + m[14] * n[7],
|
||||
m[3] * n[4] + m[7] * n[5] + m[11] * n[6] + m[15] * n[7],
|
||||
m[0] * n[8] + m[4] * n[9] + m[8] * n[10] + m[12] * n[11],
|
||||
m[1] * n[8] + m[5] * n[9] + m[9] * n[10] + m[13] * n[11],
|
||||
m[2] * n[8] + m[6] * n[9] + m[10] * n[10] + m[14] * n[11],
|
||||
m[3] * n[8] + m[7] * n[9] + m[11] * n[10] + m[15] * n[11],
|
||||
m[0] * n[12] + m[4] * n[13] + m[8] * n[14] + m[12] * n[15],
|
||||
m[1] * n[12] + m[5] * n[13] + m[9] * n[14] + m[13] * n[15],
|
||||
m[2] * n[12] + m[6] * n[13] + m[10] * n[14] + m[14] * n[15],
|
||||
m[3] * n[12] + m[7] * n[13] + m[11] * n[14] + m[15] * n[15],
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def translation_to_matrix(translation):
|
||||
# column major !
|
||||
return [1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0,
|
||||
translation[0], translation[1], translation[2], 1.0]
|
||||
|
|
@ -174,17 +174,6 @@ class glTFImporter():
|
|||
self.content = None
|
||||
return success, txt
|
||||
|
||||
def is_node_joint(self, node_idx):
|
||||
"""Check if node is a joint."""
|
||||
if not self.data.skins: # if no skin in gltf file
|
||||
return False, None
|
||||
|
||||
for skin_idx, skin in enumerate(self.data.skins):
|
||||
if node_idx in skin.joints:
|
||||
return True, skin_idx
|
||||
|
||||
return False, None
|
||||
|
||||
def load_buffer(self, buffer_idx):
|
||||
"""Load buffer."""
|
||||
buffer = self.data.buffers[buffer_idx]
|
||||
|
|
Loading…
Reference in New Issue