1 # SPDX-FileCopyrightText: 2018-2023 The glTF-Blender-IO authors
3 # SPDX-License-Identifier: Apache-2.0
7 from ....io
.com
import gltf2_io
8 from ....io
.exp
.gltf2_io_user_extensions
import export_user_extensions
9 from ..gltf2_blender_gather_cache
import cached
10 from ..gltf2_blender_gather_tree
import VExportNode
11 from .gltf2_blender_gather_animation_utils
import merge_tracks_perform
, bake_animation
, add_slide_data
, reset_bone_matrix
, reset_sk_data
12 from .gltf2_blender_gather_drivers
import get_sk_drivers
13 from .sampled
.gltf2_blender_gather_animation_sampling_cache
import get_cache_data
15 def gather_tracks_animations(export_settings
):
20 vtree
= export_settings
['vtree']
21 for obj_uuid
in vtree
.get_all_objects():
23 # Do not manage not exported objects
24 if vtree
.nodes
[obj_uuid
].node
is None:
27 animations_
, merged_tracks
= gather_track_animations(obj_uuid
, merged_tracks
, len(animations
), export_settings
)
28 animations
+= animations_
30 new_animations
= merge_tracks_perform(merged_tracks
, animations
, export_settings
)
35 def gather_track_animations( obj_uuid
: int,
36 tracks
: typing
.Dict
[str, typing
.List
[int]],
38 export_settings
) -> typing
.Tuple
[typing
.List
[gltf2_io
.Animation
], typing
.Dict
[str, typing
.List
[int]]]:
42 blender_object
= export_settings
['vtree'].nodes
[obj_uuid
].blender_object
43 # Collect all tracks affecting this object.
44 blender_tracks
= __get_blender_tracks(obj_uuid
, export_settings
)
46 ####### Keep current situation
48 current_sk_action
= None
49 current_world_matrix
= None
50 current_use_nla
= None
51 current_use_nla_sk
= None
52 restore_track_mute
= {}
53 restore_track_mute
["OBJECT"] = {}
54 restore_track_mute
["SHAPEKEY"] = {}
56 if blender_object
.animation_data
:
57 current_action
= blender_object
.animation_data
.action
58 current_use_nla
= blender_object
.animation_data
.use_nla
59 restore_tweak_mode
= blender_object
.animation_data
.use_tweak_mode
60 current_world_matrix
= blender_object
.matrix_world
.copy()
63 if blender_object
.type == "MESH" \
64 and blender_object
.data
is not None \
65 and blender_object
.data
.shape_keys
is not None \
66 and blender_object
.data
.shape_keys
.animation_data
is not None:
67 current_sk_action
= blender_object
.data
.shape_keys
.animation_data
.action
68 current_use_nla_sk
= blender_object
.data
.shape_keys
.animation_data
.use_nla
70 ####### Prepare export for obj
72 if blender_object
.animation_data
:
73 blender_object
.animation_data
.action
= None
74 blender_object
.animation_data
.use_nla
= True
75 # Remove any solo (starred) NLA track. Restored after export
76 for track
in blender_object
.animation_data
.nla_tracks
:
83 if blender_object
.type == "MESH" \
84 and blender_object
.data
is not None \
85 and blender_object
.data
.shape_keys
is not None \
86 and blender_object
.data
.shape_keys
.animation_data
is not None:
87 # Remove any solo (starred) NLA track. Restored after export
88 for track
in blender_object
.data
.shape_keys
.animation_data
.nla_tracks
:
95 for track_group
in [b
[0] for b
in blender_tracks
if b
[2] == "OBJECT"]:
96 for track
in track_group
:
97 restore_track_mute
["OBJECT"][track
.idx
] = blender_object
.animation_data
.nla_tracks
[track
.idx
].mute
98 blender_object
.animation_data
.nla_tracks
[track
.idx
].mute
= True
99 for track_group
in [b
[0] for b
in blender_tracks
if b
[2] == "SHAPEKEY"]:
100 for track
in track_group
:
101 restore_track_mute
["SHAPEKEY"][track
.idx
] = blender_object
.data
.shape_keys
.animation_data
.nla_tracks
[track
.idx
].mute
102 blender_object
.data
.shape_keys
.animation_data
.nla_tracks
[track
.idx
].mute
= True
105 export_user_extensions('animation_track_switch_loop_hook', export_settings
, blender_object
, False)
109 # Export all collected actions.
110 for bl_tracks
, track_name
, on_type
in blender_tracks
:
111 prepare_tracks_range(obj_uuid
, bl_tracks
, track_name
, export_settings
)
113 if on_type
== "OBJECT":
115 for track
in bl_tracks
:
116 export_user_extensions('pre_animation_track_switch_hook', export_settings
, blender_object
, track
, track_name
, on_type
)
117 blender_object
.animation_data
.nla_tracks
[track
.idx
].mute
= False
118 export_user_extensions('post_animation_track_switch_hook', export_settings
, blender_object
, track
, track_name
, on_type
)
121 for track
in bl_tracks
:
122 export_user_extensions('pre_animation_track_switch_hook', export_settings
, blender_object
, track
, track_name
, on_type
)
123 blender_object
.data
.shape_keys
.animation_data
.nla_tracks
[track
.idx
].mute
= False
124 export_user_extensions('post_animation_track_switch_hook', export_settings
, blender_object
, track
, track_name
, on_type
)
126 reset_bone_matrix(blender_object
, export_settings
)
127 if on_type
== "SHAPEKEY":
128 reset_sk_data(blender_object
, blender_tracks
, export_settings
)
130 ##### Export animation
131 animation
= bake_animation(obj_uuid
, track_name
, export_settings
, mode
=on_type
)
132 get_cache_data
.reset_cache()
133 if animation
is not None:
134 animations
.append(animation
)
136 # Store data for merging animation later
137 # Do not take into account default NLA track names
138 if not (track_name
.startswith("NlaTrack") or track_name
.startswith("[Action Stash]")):
139 if track_name
not in tracks
.keys():
140 tracks
[track_name
] = []
141 tracks
[track_name
].append(offset
+ len(animations
)-1) # Store index of animation in animations
144 if on_type
== "OBJECT":
145 for track
in bl_tracks
:
146 blender_object
.animation_data
.nla_tracks
[track
.idx
].mute
= True
148 for track
in bl_tracks
:
149 blender_object
.data
.shape_keys
.animation_data
.nla_tracks
[track
.idx
].mute
= True
152 ############## Restoring
153 if current_action
is not None:
154 blender_object
.animation_data
.action
= current_action
155 if current_sk_action
is not None:
156 blender_object
.data
.shape_keys
.animation_data
.action
= current_sk_action
157 if solo_track
is not None:
158 solo_track
.is_solo
= True
159 if solo_track_sk
is not None:
160 solo_track_sk
.is_solo
= True
161 if blender_object
.animation_data
:
162 blender_object
.animation_data
.use_nla
= current_use_nla
163 blender_object
.animation_data
.use_tweak_mode
= restore_tweak_mode
164 for track_group
in [b
[0] for b
in blender_tracks
if b
[2] == "OBJECT"]:
165 for track
in track_group
:
166 blender_object
.animation_data
.nla_tracks
[track
.idx
].mute
= restore_track_mute
["OBJECT"][track
.idx
]
167 if blender_object
.type == "MESH" \
168 and blender_object
.data
is not None \
169 and blender_object
.data
.shape_keys
is not None \
170 and blender_object
.data
.shape_keys
.animation_data
is not None:
171 blender_object
.data
.shape_keys
.animation_data
.use_nla
= current_use_nla_sk
172 for track_group
in [b
[0] for b
in blender_tracks
if b
[2] == "SHAPEKEY"]:
173 for track
in track_group
:
174 blender_object
.data
.shape_keys
.animation_data
.nla_tracks
[track
.idx
].mute
= restore_track_mute
["SHAPEKEY"][track
.idx
]
176 blender_object
.matrix_world
= current_world_matrix
178 export_user_extensions('animation_track_switch_loop_hook', export_settings
, blender_object
, True)
180 return animations
, tracks
183 def __get_blender_tracks(obj_uuid
: str, export_settings
):
185 blender_object
= export_settings
['vtree'].nodes
[obj_uuid
].blender_object
186 export_user_extensions('pre_gather_tracks_hook', export_settings
, blender_object
)
188 tracks
, names
, types
= __get_nla_tracks_obj(obj_uuid
, export_settings
)
189 tracks_sk
, names_sk
, types_sk
= __get_nla_tracks_sk(obj_uuid
, export_settings
)
191 tracks
.extend(tracks_sk
)
192 names
.extend(names_sk
)
193 types
.extend(types_sk
)
195 # Use a class to get parameters, to be able to modify them
196 class GatherTrackHookParameters
:
197 def __init__(self
, blender_tracks
, blender_tracks_name
, track_on_type
):
198 self
.blender_tracks
= blender_tracks
199 self
.blender_tracks_name
= blender_tracks_name
200 self
.track_on_type
= track_on_type
202 gathertrackhookparams
= GatherTrackHookParameters(tracks
, names
, types
)
204 export_user_extensions('gather_tracks_hook', export_settings
, blender_object
, gathertrackhookparams
)
206 # Get params back from hooks
207 tracks
= gathertrackhookparams
.blender_tracks
208 names
= gathertrackhookparams
.blender_tracks_name
209 types
= gathertrackhookparams
.track_on_type
211 return list(zip(tracks
, names
, types
))
214 def __init__(self
, idx
, frame_start
, frame_end
, default_solo
, default_muted
):
216 self
.frame_start
= frame_start
217 self
.frame_end
= frame_end
218 self
.default_solo
= default_solo
219 self
.default_muted
= default_muted
221 def __get_nla_tracks_obj(obj_uuid
: str, export_settings
):
223 obj
= export_settings
['vtree'].nodes
[obj_uuid
].blender_object
225 if not obj
.animation_data
:
227 if len(obj
.animation_data
.nla_tracks
) == 0:
232 current_exported_tracks
= []
234 for idx_track
, track
in enumerate(obj
.animation_data
.nla_tracks
):
235 if len(track
.strips
) == 0:
238 stored_track
= NLATrack(
240 track
.strips
[0].frame_start
,
241 track
.strips
[-1].frame_end
,
246 # Keep tracks where some blending together
247 if any([strip
.blend_type
!= 'REPLACE' for strip
in track
.strips
]):
248 # There is some blending. Keeping with previous track
251 # The previous one(s) can go to the list, if any (not for first track)
252 if len(current_exported_tracks
) != 0:
253 exported_tracks
.append(current_exported_tracks
)
254 current_exported_tracks
= []
255 current_exported_tracks
.append(stored_track
)
257 # End of loop. Keep the last one(s)
258 exported_tracks
.append(current_exported_tracks
)
260 track_names
= [obj
.animation_data
.nla_tracks
[tracks_group
[0].idx
].name
for tracks_group
in exported_tracks
]
261 on_types
= ['OBJECT'] * len(track_names
)
262 return exported_tracks
, track_names
, on_types
265 def __get_nla_tracks_sk(obj_uuid
: str, export_settings
):
267 obj
= export_settings
['vtree'].nodes
[obj_uuid
].blender_object
269 if not obj
.type == "MESH":
273 if obj
.data
.shape_keys
is None:
275 if not obj
.data
.shape_keys
.animation_data
:
277 if len(obj
.data
.shape_keys
.animation_data
.nla_tracks
) == 0:
282 current_exported_tracks
= []
284 for idx_track
, track
in enumerate(obj
.data
.shape_keys
.animation_data
.nla_tracks
):
285 if len(track
.strips
) == 0:
288 stored_track
= NLATrack(
290 track
.strips
[0].frame_start
,
291 track
.strips
[-1].frame_end
,
296 # Keep tracks where some blending together
297 if any([strip
.blend_type
!= 'REPLACE' for strip
in track
.strips
]):
298 # There is some blending. Keeping with previous track
301 # The previous one(s) can go to the list, if any (not for first track)
302 if len(current_exported_tracks
) != 0:
303 exported_tracks
.append(current_exported_tracks
)
304 current_exported_tracks
= []
305 current_exported_tracks
.append(stored_track
)
307 # End of loop. Keep the last one(s)
308 exported_tracks
.append(current_exported_tracks
)
310 track_names
= [obj
.data
.shape_keys
.animation_data
.nla_tracks
[tracks_group
[0].idx
].name
for tracks_group
in exported_tracks
]
311 on_types
= ['SHAPEKEY'] * len(track_names
)
312 return exported_tracks
, track_names
, on_types
314 def prepare_tracks_range(obj_uuid
, tracks
, track_name
, export_settings
):
318 for idx
, btrack
in enumerate(tracks
):
319 frame_start
= btrack
.frame_start
if idx
== 0 else min(frame_start
, btrack
.frame_start
)
320 frame_end
= btrack
.frame_end
if idx
== 0 else max(frame_end
, btrack
.frame_end
)
322 # If some negative frame and crop -> set start at 0
323 if frame_start
< 0 and export_settings
['gltf_negative_frames'] == "CROP":
326 if export_settings
['gltf_frame_range'] is True:
327 frame_start
= max(bpy
.context
.scene
.frame_start
, frame_start
)
328 frame_end
= min(bpy
.context
.scene
.frame_end
, frame_end
)
330 export_settings
['ranges'][obj_uuid
] = {}
331 export_settings
['ranges'][obj_uuid
][track_name
] = {}
332 export_settings
['ranges'][obj_uuid
][track_name
]['start'] = int(frame_start
)
333 export_settings
['ranges'][obj_uuid
][track_name
]['end'] = int(frame_end
)
335 if export_settings
['gltf_negative_frames'] == "SLIDE":
336 if not (track_name
.startswith("NlaTrack") or track_name
.startswith("[Action Stash]")):
337 if track_name
not in track_slide
.keys() or (track_name
in track_slide
.keys() and frame_start
< track_slide
[track_name
]):
338 track_slide
.update({track_name
:frame_start
})
341 add_slide_data(frame_start
, obj_uuid
, track_name
, export_settings
)
344 if export_settings
['gltf_anim_slide_to_zero'] is True and frame_start
> 0:
345 if not (track_name
.startswith("NlaTrack") or track_name
.startswith("[Action Stash]")):
346 if track_name
not in track_slide
.keys() or (track_name
in track_slide
.keys() and frame_start
< track_slide
[track_name
]):
347 track_slide
.update({track_name
:frame_start
})
349 add_slide_data(frame_start
, obj_uuid
, track_name
, export_settings
)
353 if export_settings
['vtree'].nodes
[obj_uuid
].blender_type
== VExportNode
.ARMATURE
and export_settings
['gltf_morph_anim'] is True:
354 obj_drivers
= get_sk_drivers(obj_uuid
, export_settings
)
355 for obj_dr
in obj_drivers
:
356 if obj_dr
not in export_settings
['ranges']:
357 export_settings
['ranges'][obj_dr
] = {}
358 export_settings
['ranges'][obj_dr
][obj_uuid
+ "_" + track_name
] = {}
359 export_settings
['ranges'][obj_dr
][obj_uuid
+ "_" + track_name
]['start'] = frame_start
360 export_settings
['ranges'][obj_dr
][obj_uuid
+ "_" + track_name
]['end'] = frame_end
362 if (export_settings
['gltf_negative_frames'] == "SLIDE" \
363 or export_settings
['gltf_anim_slide_to_zero'] is True) \
364 and len(track_slide
) > 0:
366 if track_name
in track_slide
.keys():
367 if export_settings
['gltf_negative_frames'] == "SLIDE" and track_slide
[track_name
] < 0:
368 add_slide_data(track_slide
[track_name
], obj_uuid
, track_name
, export_settings
)
369 elif export_settings
['gltf_anim_slide_to_zero'] is True:
370 add_slide_data(track_slide
[track_name
], obj_uuid
, track_name
, export_settings
)