1 # SPDX-License-Identifier: Apache-2.0
2 # Copyright 2018-2021 The glTF-Blender-IO authors.
5 from mathutils
import Vector
7 from ...io
.imp
.gltf2_io_binary
import BinaryData
8 from .gltf2_blender_animation_utils
import make_fcurve
9 from .gltf2_blender_vnode
import VNode
10 from io_scene_gltf2
.io
.imp
.gltf2_io_user_extensions
import import_user_extensions
13 class BlenderNodeAnim():
14 """Blender Object Animation."""
15 def __new__(cls
, *args
, **kwargs
):
16 raise RuntimeError("%s should not be instantiated" % cls
)
19 def anim(gltf
, anim_idx
, node_idx
):
20 """Manage animation targeting a node's TRS."""
21 animation
= gltf
.data
.animations
[anim_idx
]
22 node
= gltf
.data
.nodes
[node_idx
]
23 if anim_idx
not in node
.animations
.keys():
26 for channel_idx
in node
.animations
[anim_idx
]:
27 channel
= animation
.channels
[channel_idx
]
28 if channel
.target
.path
not in ['translation', 'rotation', 'scale']:
31 BlenderNodeAnim
.do_channel(gltf
, anim_idx
, node_idx
, channel
)
34 def do_channel(gltf
, anim_idx
, node_idx
, channel
):
35 animation
= gltf
.data
.animations
[anim_idx
]
36 vnode
= gltf
.vnodes
[node_idx
]
37 path
= channel
.target
.path
39 import_user_extensions('gather_import_animation_channel_before_hook', gltf
, animation
, vnode
, path
, channel
)
41 action
= BlenderNodeAnim
.get_or_create_action(gltf
, node_idx
, animation
.track_name
)
43 keys
= BinaryData
.get_data_from_accessor(gltf
, animation
.samplers
[channel
.sampler
].input)
44 values
= BinaryData
.get_data_from_accessor(gltf
, animation
.samplers
[channel
.sampler
].output
)
46 if animation
.samplers
[channel
.sampler
].interpolation
== "CUBICSPLINE":
47 # TODO manage tangent?
50 # Convert the curve from glTF to Blender.
52 if path
== "translation":
53 blender_path
= "location"
54 group_name
= "Location"
56 values
= [gltf
.loc_gltf_to_blender(vals
) for vals
in values
]
57 values
= vnode
.base_locs_to_final_locs(values
)
59 elif path
== "rotation":
60 blender_path
= "rotation_quaternion"
61 group_name
= "Rotation"
63 values
= [gltf
.quaternion_gltf_to_blender(vals
) for vals
in values
]
64 values
= vnode
.base_rots_to_final_rots(values
)
67 blender_path
= "scale"
70 values
= [gltf
.scale_gltf_to_blender(vals
) for vals
in values
]
71 values
= vnode
.base_scales_to_final_scales(values
)
73 # Objects parented to a bone are translated to the bone tip by default.
74 # Correct for this by translating backwards from the tip to the root.
75 if vnode
.type == VNode
.Object
and path
== "translation":
76 if vnode
.parent
is not None and gltf
.vnodes
[vnode
.parent
].type == VNode
.Bone
:
77 bone_length
= gltf
.vnodes
[vnode
.parent
].bone_length
78 off
= Vector((0, -bone_length
, 0))
79 values
= [vals
+ off
for vals
in values
]
81 if vnode
.type == VNode
.Bone
:
82 # Need to animate the pose bone when the node is a bone.
83 group_name
= vnode
.blender_bone_name
84 blender_path
= 'pose.bones["%s"].%s' % (
85 bpy
.utils
.escape_identifier(vnode
.blender_bone_name
),
89 # We have the final TRS of the bone in values. We need to give
90 # the TRS of the pose bone though, which is relative to the edit
93 # Final = EditBone * PoseBone
95 # Final = Trans[ft] Rot[fr] Scale[fs]
96 # EditBone = Trans[et] Rot[er]
97 # PoseBone = Trans[pt] Rot[pr] Scale[ps]
99 # Solving for PoseBone gives
101 # pt = Rot[er^{-1}] (ft - et)
105 if path
== 'translation':
106 edit_trans
, edit_rot
= vnode
.editbone_trans
, vnode
.editbone_rot
107 edit_rot_inv
= edit_rot
.conjugated()
109 edit_rot_inv
@ (trans
- edit_trans
)
113 elif path
== 'rotation':
114 edit_rot
= vnode
.editbone_rot
115 edit_rot_inv
= edit_rot
.conjugated()
121 elif path
== 'scale':
122 pass # no change needed
124 # To ensure rotations always take the shortest path, we flip
125 # adjacent antipodal quaternions.
126 if path
== 'rotation':
127 for i
in range(1, len(values
)):
128 if values
[i
].dot(values
[i
-1]) < 0:
129 values
[i
] = -values
[i
]
131 fps
= bpy
.context
.scene
.render
.fps
133 coords
= [0] * (2 * len(keys
))
134 coords
[::2] = (key
[0] * fps
for key
in keys
)
136 for i
in range(0, num_components
):
137 coords
[1::2] = (vals
[i
] for vals
in values
)
141 data_path
=blender_path
,
143 group_name
=group_name
,
144 interpolation
=animation
.samplers
[channel
.sampler
].interpolation
,
147 import_user_extensions('gather_import_animation_channel_after_hook', gltf
, animation
, vnode
, path
, channel
, action
)
150 def get_or_create_action(gltf
, node_idx
, anim_name
):
151 vnode
= gltf
.vnodes
[node_idx
]
153 if vnode
.type == VNode
.Bone
:
154 # For bones, the action goes on the armature.
155 vnode
= gltf
.vnodes
[vnode
.bone_arma
]
157 obj
= vnode
.blender_object
159 action
= gltf
.action_cache
.get(obj
.name
)
161 name
= anim_name
+ "_" + obj
.name
162 action
= bpy
.data
.actions
.new(name
)
163 action
.id_root
= 'OBJECT'
164 gltf
.needs_stash
.append((obj
, action
))
165 gltf
.action_cache
[obj
.name
] = action