1 # SPDX-License-Identifier: Apache-2.0
2 # Copyright 2018-2021 The glTF-Blender-IO authors.
7 from io_scene_gltf2
.io
.com
import gltf2_io
8 from io_scene_gltf2
.blender
.exp
import gltf2_blender_gather_animation_channels
9 from io_scene_gltf2
.io
.com
.gltf2_io_debug
import print_console
10 from ..com
.gltf2_blender_extras
import generate_extras
11 from io_scene_gltf2
.io
.exp
.gltf2_io_user_extensions
import export_user_extensions
12 from io_scene_gltf2
.blender
.exp
.gltf2_blender_gather_tree
import VExportNode
13 from ..com
.gltf2_blender_data_path
import is_bone_anim_channel
16 def __gather_channels_baked(obj_uuid
, export_settings
):
19 # If no animation in file, no need to bake
20 if len(bpy
.data
.actions
) == 0:
23 start_frame
= min([v
[0] for v
in [a
.frame_range
for a
in bpy
.data
.actions
]])
24 end_frame
= max([v
[1] for v
in [a
.frame_range
for a
in bpy
.data
.actions
]])
26 for p
in ["location", "rotation_quaternion", "scale"]:
27 channel
= gltf2_blender_gather_animation_channels
.gather_animation_channel(
36 obj_uuid
, # Use obj uuid as action name for caching
38 False #If Object is not animated, don't keep animation for this channel
40 if channel
is not None:
41 channels
.append(channel
)
43 return channels
if len(channels
) > 0 else None
45 def gather_animations( obj_uuid
: int,
46 tracks
: typing
.Dict
[str, typing
.List
[int]],
48 export_settings
) -> typing
.Tuple
[typing
.List
[gltf2_io
.Animation
], typing
.Dict
[str, typing
.List
[int]]]:
50 Gather all animations which contribute to the objects property, and corresponding track names
52 :param blender_object: The blender object which is animated
53 :param export_settings:
54 :return: A list of glTF2 animations and tracks
58 blender_object
= export_settings
['vtree'].nodes
[obj_uuid
].blender_object
60 # Collect all 'actions' affecting this object. There is a direct mapping between blender actions and glTF animations
61 blender_actions
= __get_blender_actions(blender_object
, export_settings
)
63 if len([a
for a
in blender_actions
if a
[2] == "OBJECT"]) == 0:
64 # No TRS animation are found for this object.
65 # But we need to bake, in case we export selection
66 # (Only when force sampling is ON)
67 # If force sampling is OFF, can lead to inconsistent export anyway
68 if export_settings
['gltf_selected'] is True and blender_object
.type != "ARMATURE" and export_settings
['gltf_force_sampling'] is True:
69 channels
= __gather_channels_baked(obj_uuid
, export_settings
)
70 if channels
is not None:
71 animation
= gltf2_io
.Animation(
73 extensions
=None, # as other animations
74 extras
=None, # Because there is no animation to get extras from
75 name
=blender_object
.name
, # Use object name as animation name
79 __link_samplers(animation
, export_settings
)
80 if animation
is not None:
81 animations
.append(animation
)
82 elif export_settings
['gltf_selected'] is True and blender_object
.type == "ARMATURE":
83 # We need to bake all bones. Because some bone can have some constraints linking to
84 # some other armature bones, for example
90 if blender_object
.animation_data
and blender_object
.animation_data
.action
:
91 current_action
= blender_object
.animation_data
.action
92 # Remove any solo (starred) NLA track. Restored after export
94 if blender_object
.animation_data
:
95 for track
in blender_object
.animation_data
.nla_tracks
:
101 # Remove any tweak mode. Restore after export
102 if blender_object
.animation_data
:
103 restore_tweak_mode
= blender_object
.animation_data
.use_tweak_mode
105 # Remove use of NLA. Restore after export
106 if blender_object
.animation_data
:
107 current_use_nla
= blender_object
.animation_data
.use_nla
108 blender_object
.animation_data
.use_nla
= False
110 # Export all collected actions.
111 for blender_action
, track_name
, on_type
in blender_actions
:
113 # Set action as active, to be able to bake if needed
114 if on_type
== "OBJECT": # Not for shapekeys!
115 if blender_object
.animation_data
.action
is None \
116 or (blender_object
.animation_data
.action
.name
!= blender_action
.name
):
117 if blender_object
.animation_data
.is_property_readonly('action'):
118 blender_object
.animation_data
.use_tweak_mode
= False
120 blender_object
.animation_data
.action
= blender_action
122 error
= "Action is readonly. Please check NLA editor"
123 print_console("WARNING", "Animation '{}' could not be exported. Cause: {}".format(blender_action
.name
, error
))
126 # No need to set active shapekeys animations, this is needed for bone baking
128 animation
= __gather_animation(obj_uuid
, blender_action
, export_settings
)
129 if animation
is not None:
130 animations
.append(animation
)
132 # Store data for merging animation later
133 if track_name
is not None: # Do not take into account animation not in NLA
134 # Do not take into account default NLA track names
135 if not (track_name
.startswith("NlaTrack") or track_name
.startswith("[Action Stash]")):
136 if track_name
not in tracks
.keys():
137 tracks
[track_name
] = []
138 tracks
[track_name
].append(offset
+ len(animations
)-1) # Store index of animation in animations
140 # Restore action status
141 # TODO: do this in a finally
142 if blender_object
.animation_data
:
143 if blender_object
.animation_data
.action
is not None:
144 if current_action
is None:
145 # remove last exported action
146 blender_object
.animation_data
.action
= None
147 elif blender_object
.animation_data
.action
.name
!= current_action
.name
:
148 # Restore action that was active at start of exporting
149 blender_object
.animation_data
.action
= current_action
150 if solo_track
is not None:
151 solo_track
.is_solo
= True
152 blender_object
.animation_data
.use_tweak_mode
= restore_tweak_mode
153 blender_object
.animation_data
.use_nla
= current_use_nla
155 return animations
, tracks
158 def __gather_animation( obj_uuid
: int,
159 blender_action
: bpy
.types
.Action
,
161 ) -> typing
.Optional
[gltf2_io
.Animation
]:
163 blender_object
= export_settings
['vtree'].nodes
[obj_uuid
].blender_object
165 if not __filter_animation(blender_action
, blender_object
, export_settings
):
168 name
= __gather_name(blender_action
, blender_object
, export_settings
)
170 animation
= gltf2_io
.Animation(
171 channels
=__gather_channels(obj_uuid
, blender_action
, export_settings
),
172 extensions
=__gather_extensions(blender_action
, blender_object
, export_settings
),
173 extras
=__gather_extras(blender_action
, blender_object
, export_settings
),
175 samplers
=__gather_samplers(obj_uuid
, blender_action
, export_settings
)
177 except RuntimeError as error
:
178 print_console("WARNING", "Animation '{}' could not be exported. Cause: {}".format(name
, error
))
181 export_user_extensions('pre_gather_animation_hook', export_settings
, animation
, blender_action
, blender_object
)
183 if not animation
.channels
:
186 # To allow reuse of samplers in one animation,
187 __link_samplers(animation
, export_settings
)
189 export_user_extensions('gather_animation_hook', export_settings
, animation
, blender_action
, blender_object
)
194 def __filter_animation(blender_action
: bpy
.types
.Action
,
195 blender_object
: bpy
.types
.Object
,
198 if blender_action
.users
== 0:
204 def __gather_channels(obj_uuid
: int,
205 blender_action
: bpy
.types
.Action
,
207 ) -> typing
.List
[gltf2_io
.AnimationChannel
]:
208 return gltf2_blender_gather_animation_channels
.gather_animation_channels(
209 obj_uuid
, blender_action
, export_settings
)
212 def __gather_extensions(blender_action
: bpy
.types
.Action
,
213 blender_object
: bpy
.types
.Object
,
219 def __gather_extras(blender_action
: bpy
.types
.Action
,
220 blender_object
: bpy
.types
.Object
,
224 if export_settings
['gltf_extras']:
225 return generate_extras(blender_action
)
229 def __gather_name(blender_action
: bpy
.types
.Action
,
230 blender_object
: bpy
.types
.Object
,
232 ) -> typing
.Optional
[str]:
233 return blender_action
.name
236 def __gather_samplers(obj_uuid
: str,
237 blender_action
: bpy
.types
.Action
,
239 ) -> typing
.List
[gltf2_io
.AnimationSampler
]:
240 # We need to gather the samplers after gathering all channels --> populate this list in __link_samplers
244 def __link_samplers(animation
: gltf2_io
.Animation
, export_settings
):
246 Move animation samplers to their own list and store their indices at their previous locations.
248 After gathering, samplers are stored in the channels properties of the animation and need to be moved
249 to their own list while storing an index into this list at the position where they previously were.
250 This behaviour is similar to that of the glTFExporter that traverses all nodes
252 :param export_settings:
255 # TODO: move this to some util module and update gltf2 exporter also
256 T
= typing
.TypeVar('T')
258 def __append_unique_and_get_index(l
: typing
.List
[T
], item
: T
):
266 for i
, channel
in enumerate(animation
.channels
):
267 animation
.channels
[i
].sampler
= __append_unique_and_get_index(animation
.samplers
, channel
.sampler
)
270 def __get_blender_actions(blender_object
: bpy
.types
.Object
,
272 ) -> typing
.List
[typing
.Tuple
[bpy
.types
.Action
, str, str]]:
277 export_user_extensions('pre_gather_actions_hook', export_settings
, blender_object
)
279 if blender_object
.animation_data
is not None:
280 # Collect active action.
281 if blender_object
.animation_data
.action
is not None:
282 blender_actions
.append(blender_object
.animation_data
.action
)
283 blender_tracks
[blender_object
.animation_data
.action
.name
] = None
284 action_on_type
[blender_object
.animation_data
.action
.name
] = "OBJECT"
286 # Collect associated strips from NLA tracks.
287 if export_settings
['gltf_nla_strips'] is True:
288 for track
in blender_object
.animation_data
.nla_tracks
:
289 # Multi-strip tracks do not export correctly yet (they need to be baked),
290 # so skip them for now and only write single-strip tracks.
291 non_muted_strips
= [strip
for strip
in track
.strips
if strip
.action
is not None and strip
.mute
is False]
292 if track
.strips
is None or len(non_muted_strips
) != 1:
294 for strip
in non_muted_strips
:
295 blender_actions
.append(strip
.action
)
296 blender_tracks
[strip
.action
.name
] = track
.name
# Always set after possible active action -> None will be overwrite
297 action_on_type
[strip
.action
.name
] = "OBJECT"
299 if blender_object
.type == "MESH" \
300 and blender_object
.data
is not None \
301 and blender_object
.data
.shape_keys
is not None \
302 and blender_object
.data
.shape_keys
.animation_data
is not None:
304 if blender_object
.data
.shape_keys
.animation_data
.action
is not None:
305 blender_actions
.append(blender_object
.data
.shape_keys
.animation_data
.action
)
306 blender_tracks
[blender_object
.data
.shape_keys
.animation_data
.action
.name
] = None
307 action_on_type
[blender_object
.data
.shape_keys
.animation_data
.action
.name
] = "SHAPEKEY"
309 if export_settings
['gltf_nla_strips'] is True:
310 for track
in blender_object
.data
.shape_keys
.animation_data
.nla_tracks
:
311 # Multi-strip tracks do not export correctly yet (they need to be baked),
312 # so skip them for now and only write single-strip tracks.
313 non_muted_strips
= [strip
for strip
in track
.strips
if strip
.action
is not None and strip
.mute
is False]
314 if track
.strips
is None or len(non_muted_strips
) != 1:
316 for strip
in non_muted_strips
:
317 blender_actions
.append(strip
.action
)
318 blender_tracks
[strip
.action
.name
] = track
.name
# Always set after possible active action -> None will be overwrite
319 action_on_type
[strip
.action
.name
] = "SHAPEKEY"
321 # If there are only 1 armature, include all animations, even if not in NLA
322 if export_settings
['gltf_export_anim_single_armature'] is True:
323 if blender_object
.type == "ARMATURE":
324 if len(export_settings
['vtree'].get_all_node_of_type(VExportNode
.ARMATURE
)) == 1:
325 # Keep all actions on objects (no Shapekey animation)
326 for act
in [a
for a
in bpy
.data
.actions
if a
.id_root
== "OBJECT"]:
327 # We need to check this is an armature action
328 # Checking that at least 1 bone is animated
329 if not __is_armature_action(act
):
331 # Check if this action is already taken into account
332 if act
.name
in blender_tracks
.keys():
334 blender_actions
.append(act
)
335 blender_tracks
[act
.name
] = None
336 action_on_type
[act
.name
] = "OBJECT"
338 export_user_extensions('gather_actions_hook', export_settings
, blender_object
, blender_actions
, blender_tracks
, action_on_type
)
340 # Remove duplicate actions.
341 blender_actions
= list(set(blender_actions
))
342 # sort animations alphabetically (case insensitive) so they have a defined order and match Blender's Action list
343 blender_actions
.sort(key
= lambda a
: a
.name
.lower())
345 return [(blender_action
, blender_tracks
[blender_action
.name
], action_on_type
[blender_action
.name
]) for blender_action
in blender_actions
]
348 def __is_armature_action(blender_action
) -> bool:
349 for fcurve
in blender_action
.fcurves
:
350 if is_bone_anim_channel(fcurve
.data_path
):