Skip to content
Snippets Groups Projects
Commit 5dba7a81 authored by Julien Duroure's avatar Julien Duroure
Browse files

glTF importer: Huge speedup performance for animation import

Using foreach_set instead of individual keyframing
Thanks to Scurest for the PR :)
parent 682fffba
Branches
Tags
No related merge requests found
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
# #
import os import os
import time
import bpy import bpy
from bpy_extras.io_utils import ImportHelper, ExportHelper from bpy_extras.io_utils import ImportHelper, ExportHelper
from bpy.types import Operator, AddonPreferences from bpy.types import Operator, AddonPreferences
...@@ -482,8 +483,10 @@ class ImportGLTF2(Operator, ImportHelper): ...@@ -482,8 +483,10 @@ class ImportGLTF2(Operator, ImportHelper):
self.report({'ERROR'}, txt) self.report({'ERROR'}, txt)
return {'CANCELLED'} return {'CANCELLED'}
self.gltf_importer.log.critical("Data are loaded, start creating Blender stuff") self.gltf_importer.log.critical("Data are loaded, start creating Blender stuff")
start_time = time.time()
BlenderGlTF.create(self.gltf_importer) BlenderGlTF.create(self.gltf_importer)
self.gltf_importer.log.critical("glTF import is now finished") elapsed_s = "{:.2f}s".format(time.time() - start_time)
self.gltf_importer.log.critical("glTF import finished in " + elapsed_s)
self.gltf_importer.log.removeHandler(self.gltf_importer.log_handler) self.gltf_importer.log.removeHandler(self.gltf_importer.log_handler)
return {'FINISHED'} return {'FINISHED'}
......
...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
import json
import bpy import bpy
from mathutils import Matrix from mathutils import Matrix
...@@ -39,38 +40,46 @@ class BlenderBoneAnim(): ...@@ -39,38 +40,46 @@ class BlenderBoneAnim():
@staticmethod @staticmethod
def parse_translation_channel(gltf, node, obj, bone, channel, animation): def parse_translation_channel(gltf, node, obj, bone, channel, animation):
"""Manage Location animation.""" """Manage Location animation."""
fps = bpy.context.scene.render.fps blender_path = "pose.bones[" + json.dumps(bone.name) + "].location"
blender_path = "location" group_name = "location"
keys = BinaryData.get_data_from_accessor(gltf, animation.samplers[channel.sampler].input) keys = BinaryData.get_data_from_accessor(gltf, animation.samplers[channel.sampler].input)
values = BinaryData.get_data_from_accessor(gltf, animation.samplers[channel.sampler].output) 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() \ inv_bind_matrix = node.blender_bone_matrix.to_quaternion().to_matrix().to_4x4().inverted() \
@ Matrix.Translation(node.blender_bone_matrix.to_translation()).inverted() @ Matrix.Translation(node.blender_bone_matrix.to_translation()).inverted()
for idx, key in enumerate(keys): if animation.samplers[channel.sampler].interpolation == "CUBICSPLINE":
if animation.samplers[channel.sampler].interpolation == "CUBICSPLINE": # TODO manage tangent?
# TODO manage tangent? translation_keyframes = (
translation_keyframe = loc_gltf_to_blender(values[idx * 3 + 1]) loc_gltf_to_blender(values[idx * 3 + 1])
else: for idx in range(0, len(keys))
translation_keyframe = loc_gltf_to_blender(values[idx]) )
if node.parent is None: 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() parent_mat = Matrix()
else: else:
if not gltf.data.nodes[node.parent].is_joint: parent_mat = gltf.data.nodes[node.parent].blender_bone_matrix
parent_mat = Matrix()
else: # Pose is in object (armature) space and it's value if the offset from the bind pose
parent_mat = gltf.data.nodes[node.parent].blender_bone_matrix # (which is also in object space)
# Scale is not taken into account
# Pose is in object (armature) space and it's value if the offset from the bind pose final_translations = [
# (which is also in object space) inv_bind_matrix @ (parent_mat @ Matrix.Translation(translation_keyframe)).to_translation()
# Scale is not taken into account for translation_keyframe in translation_keyframes
final_trans = (parent_mat @ Matrix.Translation(translation_keyframe)).to_translation() ]
bone.location = inv_bind_matrix @ final_trans
bone.keyframe_insert(blender_path, frame=key[0] * fps, group="location") BlenderBoneAnim.fill_fcurves(
obj.animation_data.action,
for fcurve in [curve for curve in obj.animation_data.action.fcurves if curve.group.name == "location"]: keys,
for kf in fcurve.keyframe_points: final_translations,
BlenderBoneAnim.set_interpolation(animation.samplers[channel.sampler].interpolation, kf) group_name,
blender_path,
animation.samplers[channel.sampler].interpolation
)
@staticmethod @staticmethod
def parse_rotation_channel(gltf, node, obj, bone, channel, animation): def parse_rotation_channel(gltf, node, obj, bone, channel, animation):
...@@ -82,73 +91,120 @@ class BlenderBoneAnim(): ...@@ -82,73 +91,120 @@ class BlenderBoneAnim():
# Converting to euler and then back to quaternion is a dirty fix preventing this issue in animation, until a # Converting to euler and then back to quaternion is a dirty fix preventing this issue in animation, until a
# better solution is found # better solution is found
# This fix is skipped when parent matrix is identity # This fix is skipped when parent matrix is identity
fps = bpy.context.scene.render.fps blender_path = "pose.bones[" + json.dumps(bone.name) + "].rotation_quaternion"
blender_path = "rotation_quaternion" group_name = "rotation"
keys = BinaryData.get_data_from_accessor(gltf, animation.samplers[channel.sampler].input) keys = BinaryData.get_data_from_accessor(gltf, animation.samplers[channel.sampler].input)
values = BinaryData.get_data_from_accessor(gltf, animation.samplers[channel.sampler].output) values = BinaryData.get_data_from_accessor(gltf, animation.samplers[channel.sampler].output)
bind_rotation = node.blender_bone_matrix.to_quaternion() bind_rotation = node.blender_bone_matrix.to_quaternion()
for idx, key in enumerate(keys): if animation.samplers[channel.sampler].interpolation == "CUBICSPLINE":
if animation.samplers[channel.sampler].interpolation == "CUBICSPLINE": # TODO manage tangent?
# TODO manage tangent? quat_keyframes = (
quat_keyframe = quaternion_gltf_to_blender(values[idx * 3 + 1]) 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)
if not node.parent:
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: else:
quat_keyframe = quaternion_gltf_to_blender(values[idx]) parent_mat = gltf.data.nodes[node.parent].blender_bone_matrix
if not node.parent:
bone.rotation_quaternion = bind_rotation.inverted() @ quat_keyframe if parent_mat != parent_mat.inverted():
final_rots = [
bind_rotation.rotation_difference(
(parent_mat @ quat_keyframe.to_matrix().to_4x4()).to_quaternion()
).to_euler().to_quaternion()
for quat_keyframe in quat_keyframes
]
else: else:
if not gltf.data.nodes[node.parent].is_joint: final_rots = [
parent_mat = Matrix() bind_rotation.rotation_difference(quat_keyframe).to_euler().to_quaternion()
else: for quat_keyframe in quat_keyframes
parent_mat = gltf.data.nodes[node.parent].blender_bone_matrix ]
if parent_mat != parent_mat.inverted(): BlenderBoneAnim.fill_fcurves(
final_rot = (parent_mat @ quat_keyframe.to_matrix().to_4x4()).to_quaternion() obj.animation_data.action,
bone.rotation_quaternion = bind_rotation.rotation_difference(final_rot).to_euler().to_quaternion() keys,
else: final_rots,
bone.rotation_quaternion = \ group_name,
bind_rotation.rotation_difference(quat_keyframe).to_euler().to_quaternion() blender_path,
animation.samplers[channel.sampler].interpolation
bone.keyframe_insert(blender_path, frame=key[0] * fps, group='rotation') )
for fcurve in [curve for curve in obj.animation_data.action.fcurves if curve.group.name == "rotation"]:
for kf in fcurve.keyframe_points:
BlenderBoneAnim.set_interpolation(animation.samplers[channel.sampler].interpolation, kf)
@staticmethod @staticmethod
def parse_scale_channel(gltf, node, obj, bone, channel, animation): def parse_scale_channel(gltf, node, obj, bone, channel, animation):
"""Manage scaling animation.""" """Manage scaling animation."""
fps = bpy.context.scene.render.fps blender_path = "pose.bones[" + json.dumps(bone.name) + "].scale"
blender_path = "scale" group_name = "scale"
keys = BinaryData.get_data_from_accessor(gltf, animation.samplers[channel.sampler].input) keys = BinaryData.get_data_from_accessor(gltf, animation.samplers[channel.sampler].input)
values = BinaryData.get_data_from_accessor(gltf, animation.samplers[channel.sampler].output) values = BinaryData.get_data_from_accessor(gltf, animation.samplers[channel.sampler].output)
bind_scale = scale_to_matrix(node.blender_bone_matrix.to_scale()) bind_scale = scale_to_matrix(node.blender_bone_matrix.to_scale())
for idx, key in enumerate(keys): if animation.samplers[channel.sampler].interpolation == "CUBICSPLINE":
if animation.samplers[channel.sampler].interpolation == "CUBICSPLINE": # TODO manage tangent?
# TODO manage tangent? scale_mats = (
scale_mat = scale_to_matrix(loc_gltf_to_blender(values[idx * 3 + 1])) scale_to_matrix(loc_gltf_to_blender(values[idx * 3 + 1]))
else: for idx in range(0, len(keys))
scale_mat = scale_to_matrix(loc_gltf_to_blender(values[idx])) )
if not node.parent: else:
bone.scale = (bind_scale.inverted() @ scale_mat).to_scale() scale_mats = (scale_to_matrix(loc_gltf_to_blender(vals)) for vals in values)
if not node.parent:
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: else:
if not gltf.data.nodes[node.parent].is_joint: parent_mat = gltf.data.nodes[node.parent].blender_bone_matrix
parent_mat = Matrix()
else: final_scales = [
parent_mat = gltf.data.nodes[node.parent].blender_bone_matrix (bind_scale.inverted() @ scale_to_matrix(parent_mat.to_scale()) @ scale_mat).to_scale()
for scale_mat in scale_mats
]
BlenderBoneAnim.fill_fcurves(
obj.animation_data.action,
keys,
final_scales,
group_name,
blender_path,
animation.samplers[channel.sampler].interpolation
)
@staticmethod
def fill_fcurves(action, keys, values, group_name, blender_path, interpolation):
"""Create FCurves from the keyframe-value pairs (one per component)."""
fps = bpy.context.scene.render.fps
coords = [0] * (2 * len(keys))
coords[::2] = (key[0] * fps for key in keys)
if group_name not in action.groups:
action.groups.new(group_name)
group = action.groups[group_name]
bone.scale = ( for i in range(0, len(values[0])):
bind_scale.inverted() @ scale_to_matrix(parent_mat.to_scale()) @ scale_mat fcurve = action.fcurves.new(data_path=blender_path, index=i)
).to_scale() fcurve.group = group
bone.keyframe_insert(blender_path, frame=key[0] * fps, group='scale') fcurve.keyframe_points.add(len(keys))
coords[1::2] = (vals[i] for vals in values)
fcurve.keyframe_points.foreach_set('co', coords)
for fcurve in [curve for curve in obj.animation_data.action.fcurves if curve.group.name == "scale"]: # Setting interpolation
for kf in fcurve.keyframe_points: for kf in fcurve.keyframe_points:
BlenderBoneAnim.set_interpolation(animation.samplers[channel.sampler].interpolation, kf) BlenderBoneAnim.set_interpolation(interpolation, kf)
@staticmethod @staticmethod
def anim(gltf, anim_idx, node_idx): def anim(gltf, anim_idx, node_idx):
......
...@@ -76,58 +76,52 @@ class BlenderNodeAnim(): ...@@ -76,58 +76,52 @@ class BlenderNodeAnim():
# We can't remove Yup2Zup oject # We can't remove Yup2Zup oject
gltf.animation_object = True 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))]
if channel.target.path == "translation": if channel.target.path == "translation":
blender_path = "location" blender_path = "location"
for idx, key in enumerate(keys): group_name = "location"
if animation.samplers[channel.sampler].interpolation == "CUBICSPLINE": num_components = 3
# TODO manage tangent? values = [loc_gltf_to_blender(vals) for vals in values]
obj.location = Vector(loc_gltf_to_blender(list(values[idx * 3 + 1])))
else:
obj.location = Vector(loc_gltf_to_blender(list(values[idx])))
obj.keyframe_insert(blender_path, frame=key[0] * fps, group='location')
# Setting interpolation
for fcurve in [curve for curve in obj.animation_data.action.fcurves
if curve.group.name == "location"]:
for kf in fcurve.keyframe_points:
BlenderNodeAnim.set_interpolation(animation.samplers[channel.sampler].interpolation, kf)
elif channel.target.path == "rotation": elif channel.target.path == "rotation":
blender_path = "rotation_quaternion" blender_path = "rotation_quaternion"
for idx, key in enumerate(keys): group_name = "rotation"
if animation.samplers[channel.sampler].interpolation == "CUBICSPLINE": num_components = 4
# TODO manage tangent? if node.correction_needed is True:
vals = values[idx * 3 + 1] values = [
else: (quaternion_gltf_to_blender(vals).to_matrix().to_4x4() @ correction_rotation()).to_quaternion()
vals = values[idx] for vals in values
]
else:
values = [quaternion_gltf_to_blender(vals) for vals in values]
if node.correction_needed is True: elif channel.target.path == "scale":
obj.rotation_quaternion = (quaternion_gltf_to_blender(vals).to_matrix().to_4x4() @ correction_rotation()).to_quaternion() blender_path = "scale"
else: group_name = "scale"
obj.rotation_quaternion = quaternion_gltf_to_blender(vals) num_components = 3
values = [scale_gltf_to_blender(vals) for vals in values]
obj.keyframe_insert(blender_path, frame=key[0] * fps, group='rotation') coords = [0] * (2 * len(keys))
coords[::2] = (key[0] * fps for key in keys)
# Setting interpolation if group_name not in action.groups:
for fcurve in [curve for curve in obj.animation_data.action.fcurves action.groups.new(group_name)
if curve.group.name == "rotation"]: group = action.groups[group_name]
for kf in fcurve.keyframe_points:
BlenderNodeAnim.set_interpolation(animation.samplers[channel.sampler].interpolation, kf)
elif channel.target.path == "scale": for i in range(0, num_components):
blender_path = "scale" fcurve = action.fcurves.new(data_path=blender_path, index=i)
for idx, key in enumerate(keys): fcurve.group = group
# TODO manage tangent?
if animation.samplers[channel.sampler].interpolation == "CUBICSPLINE": fcurve.keyframe_points.add(len(keys))
obj.scale = Vector(scale_gltf_to_blender(list(values[idx * 3 + 1]))) coords[1::2] = (vals[i] for vals in values)
else: fcurve.keyframe_points.foreach_set('co', coords)
obj.scale = Vector(scale_gltf_to_blender(list(values[idx])))
obj.keyframe_insert(blender_path, frame=key[0] * fps, group='scale')
# Setting interpolation # Setting interpolation
for fcurve in [curve for curve in obj.animation_data.action.fcurves if curve.group.name == "scale"]: for kf in fcurve.keyframe_points:
for kf in fcurve.keyframe_points: BlenderNodeAnim.set_interpolation(animation.samplers[channel.sampler].interpolation, kf)
BlenderNodeAnim.set_interpolation(animation.samplers[channel.sampler].interpolation, kf)
elif channel.target.path == 'weights': elif channel.target.path == 'weights':
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment