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 if export_settings
['gltf_selected'] is True and blender_object
.type != "ARMATURE":
67 channels
= __gather_channels_baked(obj_uuid
, export_settings
)
68 if channels
is not None:
69 animation
= gltf2_io
.Animation(
71 extensions
=None, # as other animations
72 extras
=None, # Because there is no animation to get extras from
73 name
=blender_object
.name
, # Use object name as animation name
77 __link_samplers(animation
, export_settings
)
78 if animation
is not None:
79 animations
.append(animation
)
80 elif export_settings
['gltf_selected'] is True and blender_object
.type == "ARMATURE":
81 # We need to bake all bones. Because some bone can have some constraints linking to
82 # some other armature bones, for example
88 if blender_object
.animation_data
and blender_object
.animation_data
.action
:
89 current_action
= blender_object
.animation_data
.action
90 # Remove any solo (starred) NLA track. Restored after export
92 if blender_object
.animation_data
:
93 for track
in blender_object
.animation_data
.nla_tracks
:
99 # Remove any tweak mode. Restore after export
100 if blender_object
.animation_data
:
101 restore_tweak_mode
= blender_object
.animation_data
.use_tweak_mode
103 # Remove use of NLA. Restore after export
104 if blender_object
.animation_data
:
105 current_use_nla
= blender_object
.animation_data
.use_nla
106 blender_object
.animation_data
.use_nla
= False
108 # Export all collected actions.
109 for blender_action
, track_name
, on_type
in blender_actions
:
111 # Set action as active, to be able to bake if needed
112 if on_type
== "OBJECT": # Not for shapekeys!
113 if blender_object
.animation_data
.action
is None \
114 or (blender_object
.animation_data
.action
.name
!= blender_action
.name
):
115 if blender_object
.animation_data
.is_property_readonly('action'):
116 blender_object
.animation_data
.use_tweak_mode
= False
118 blender_object
.animation_data
.action
= blender_action
120 error
= "Action is readonly. Please check NLA editor"
121 print_console("WARNING", "Animation '{}' could not be exported. Cause: {}".format(blender_action
.name
, error
))
124 # No need to set active shapekeys animations, this is needed for bone baking
126 animation
= __gather_animation(obj_uuid
, blender_action
, export_settings
)
127 if animation
is not None:
128 animations
.append(animation
)
130 # Store data for merging animation later
131 if track_name
is not None: # Do not take into account animation not in NLA
132 # Do not take into account default NLA track names
133 if not (track_name
.startswith("NlaTrack") or track_name
.startswith("[Action Stash]")):
134 if track_name
not in tracks
.keys():
135 tracks
[track_name
] = []
136 tracks
[track_name
].append(offset
+ len(animations
)-1) # Store index of animation in animations
138 # Restore action status
139 # TODO: do this in a finally
140 if blender_object
.animation_data
:
141 if blender_object
.animation_data
.action
is not None:
142 if current_action
is None:
143 # remove last exported action
144 blender_object
.animation_data
.action
= None
145 elif blender_object
.animation_data
.action
.name
!= current_action
.name
:
146 # Restore action that was active at start of exporting
147 blender_object
.animation_data
.action
= current_action
148 if solo_track
is not None:
149 solo_track
.is_solo
= True
150 blender_object
.animation_data
.use_tweak_mode
= restore_tweak_mode
151 blender_object
.animation_data
.use_nla
= current_use_nla
153 return animations
, tracks
156 def __gather_animation( obj_uuid
: int,
157 blender_action
: bpy
.types
.Action
,
159 ) -> typing
.Optional
[gltf2_io
.Animation
]:
161 blender_object
= export_settings
['vtree'].nodes
[obj_uuid
].blender_object
163 if not __filter_animation(blender_action
, blender_object
, export_settings
):
166 name
= __gather_name(blender_action
, blender_object
, export_settings
)
168 animation
= gltf2_io
.Animation(
169 channels
=__gather_channels(obj_uuid
, blender_action
, export_settings
),
170 extensions
=__gather_extensions(blender_action
, blender_object
, export_settings
),
171 extras
=__gather_extras(blender_action
, blender_object
, export_settings
),
173 samplers
=__gather_samplers(obj_uuid
, blender_action
, export_settings
)
175 except RuntimeError as error
:
176 print_console("WARNING", "Animation '{}' could not be exported. Cause: {}".format(name
, error
))
179 export_user_extensions('pre_gather_animation_hook', export_settings
, animation
, blender_action
, blender_object
)
181 if not animation
.channels
:
184 # To allow reuse of samplers in one animation,
185 __link_samplers(animation
, export_settings
)
187 export_user_extensions('gather_animation_hook', export_settings
, animation
, blender_action
, blender_object
)
192 def __filter_animation(blender_action
: bpy
.types
.Action
,
193 blender_object
: bpy
.types
.Object
,
196 if blender_action
.users
== 0:
202 def __gather_channels(obj_uuid
: int,
203 blender_action
: bpy
.types
.Action
,
205 ) -> typing
.List
[gltf2_io
.AnimationChannel
]:
206 return gltf2_blender_gather_animation_channels
.gather_animation_channels(
207 obj_uuid
, blender_action
, export_settings
)
210 def __gather_extensions(blender_action
: bpy
.types
.Action
,
211 blender_object
: bpy
.types
.Object
,
217 def __gather_extras(blender_action
: bpy
.types
.Action
,
218 blender_object
: bpy
.types
.Object
,
222 if export_settings
['gltf_extras']:
223 return generate_extras(blender_action
)
227 def __gather_name(blender_action
: bpy
.types
.Action
,
228 blender_object
: bpy
.types
.Object
,
230 ) -> typing
.Optional
[str]:
231 return blender_action
.name
234 def __gather_samplers(obj_uuid
: str,
235 blender_action
: bpy
.types
.Action
,
237 ) -> typing
.List
[gltf2_io
.AnimationSampler
]:
238 # We need to gather the samplers after gathering all channels --> populate this list in __link_samplers
242 def __link_samplers(animation
: gltf2_io
.Animation
, export_settings
):
244 Move animation samplers to their own list and store their indices at their previous locations.
246 After gathering, samplers are stored in the channels properties of the animation and need to be moved
247 to their own list while storing an index into this list at the position where they previously were.
248 This behaviour is similar to that of the glTFExporter that traverses all nodes
250 :param export_settings:
253 # TODO: move this to some util module and update gltf2 exporter also
254 T
= typing
.TypeVar('T')
256 def __append_unique_and_get_index(l
: typing
.List
[T
], item
: T
):
264 for i
, channel
in enumerate(animation
.channels
):
265 animation
.channels
[i
].sampler
= __append_unique_and_get_index(animation
.samplers
, channel
.sampler
)
268 def __get_blender_actions(blender_object
: bpy
.types
.Object
,
270 ) -> typing
.List
[typing
.Tuple
[bpy
.types
.Action
, str, str]]:
275 export_user_extensions('pre_gather_actions_hook', export_settings
, blender_object
)
277 if blender_object
.animation_data
is not None:
278 # Collect active action.
279 if blender_object
.animation_data
.action
is not None:
280 blender_actions
.append(blender_object
.animation_data
.action
)
281 blender_tracks
[blender_object
.animation_data
.action
.name
] = None
282 action_on_type
[blender_object
.animation_data
.action
.name
] = "OBJECT"
284 # Collect associated strips from NLA tracks.
285 if export_settings
['gltf_nla_strips'] is True:
286 for track
in blender_object
.animation_data
.nla_tracks
:
287 # Multi-strip tracks do not export correctly yet (they need to be baked),
288 # so skip them for now and only write single-strip tracks.
289 non_muted_strips
= [strip
for strip
in track
.strips
if strip
.action
is not None and strip
.mute
is False]
290 if track
.strips
is None or len(non_muted_strips
) != 1:
292 for strip
in non_muted_strips
:
293 blender_actions
.append(strip
.action
)
294 blender_tracks
[strip
.action
.name
] = track
.name
# Always set after possible active action -> None will be overwrite
295 action_on_type
[strip
.action
.name
] = "OBJECT"
297 if blender_object
.type == "MESH" \
298 and blender_object
.data
is not None \
299 and blender_object
.data
.shape_keys
is not None \
300 and blender_object
.data
.shape_keys
.animation_data
is not None:
302 if blender_object
.data
.shape_keys
.animation_data
.action
is not None:
303 blender_actions
.append(blender_object
.data
.shape_keys
.animation_data
.action
)
304 blender_tracks
[blender_object
.data
.shape_keys
.animation_data
.action
.name
] = None
305 action_on_type
[blender_object
.data
.shape_keys
.animation_data
.action
.name
] = "SHAPEKEY"
307 if export_settings
['gltf_nla_strips'] is True:
308 for track
in blender_object
.data
.shape_keys
.animation_data
.nla_tracks
:
309 # Multi-strip tracks do not export correctly yet (they need to be baked),
310 # so skip them for now and only write single-strip tracks.
311 non_muted_strips
= [strip
for strip
in track
.strips
if strip
.action
is not None and strip
.mute
is False]
312 if track
.strips
is None or len(non_muted_strips
) != 1:
314 for strip
in non_muted_strips
:
315 blender_actions
.append(strip
.action
)
316 blender_tracks
[strip
.action
.name
] = track
.name
# Always set after possible active action -> None will be overwrite
317 action_on_type
[strip
.action
.name
] = "SHAPEKEY"
319 # If there are only 1 armature, include all animations, even if not in NLA
320 if blender_object
.type == "ARMATURE":
321 if len(export_settings
['vtree'].get_all_node_of_type(VExportNode
.ARMATURE
)) == 1:
322 # Keep all actions on objects (no Shapekey animation)
323 for act
in [a
for a
in bpy
.data
.actions
if a
.id_root
== "OBJECT"]:
324 # We need to check this is an armature action
325 # Checking that at least 1 bone is animated
326 if not __is_armature_action(act
):
328 # Check if this action is already taken into account
329 if act
.name
in blender_tracks
.keys():
331 blender_actions
.append(act
)
332 blender_tracks
[act
.name
] = None
333 action_on_type
[act
.name
] = "OBJECT"
335 export_user_extensions('gather_actions_hook', export_settings
, blender_object
, blender_actions
, blender_tracks
, action_on_type
)
337 # Remove duplicate actions.
338 blender_actions
= list(set(blender_actions
))
339 # sort animations alphabetically (case insensitive) so they have a defined order and match Blender's Action list
340 blender_actions
.sort(key
= lambda a
: a
.name
.lower())
342 return [(blender_action
, blender_tracks
[blender_action
.name
], action_on_type
[blender_action
.name
]) for blender_action
in blender_actions
]
345 def __is_armature_action(blender_action
) -> bool:
346 for fcurve
in blender_action
.fcurves
:
347 if is_bone_anim_channel(fcurve
.data_path
):