glTF exporter: Fix restore of track mute during track mode export
[blender-addons.git] / io_scene_gltf2 / blender / exp / animation / gltf2_blender_gather_tracks.py
blob40374528c12e64c6e0d4ca6e0b5eccd70fe6e834
1 # SPDX-FileCopyrightText: 2018-2023 The glTF-Blender-IO authors
3 # SPDX-License-Identifier: Apache-2.0
5 import bpy
6 import typing
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):
17 animations = []
18 merged_tracks = {}
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:
25 continue
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)
32 return new_animations
35 def gather_track_animations( obj_uuid: int,
36 tracks: typing.Dict[str, typing.List[int]],
37 offset: int,
38 export_settings) -> typing.Tuple[typing.List[gltf2_io.Animation], typing.Dict[str, typing.List[int]]]:
40 animations = []
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
47 current_action = None
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
71 solo_track = None
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:
77 if track.is_solo:
78 solo_track = track
79 track.is_solo = False
80 break
82 solo_track_sk = None
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:
89 if track.is_solo:
90 solo_track_sk = track
91 track.is_solo = False
92 break
94 # Mute all channels
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)
107 ######## Export
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":
114 # Enable tracks
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)
119 else:
120 # Enable tracks
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
143 # Restoring muting
144 if on_type == "OBJECT":
145 for track in bl_tracks:
146 blender_object.animation_data.nla_tracks[track.idx].mute = True
147 else:
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
182 @cached
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))
213 class NLATrack:
214 def __init__(self, idx, frame_start, frame_end, default_solo, default_muted):
215 self.idx = idx
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:
226 return [], [], []
227 if len(obj.animation_data.nla_tracks) == 0:
228 return [], [], []
230 exported_tracks = []
232 current_exported_tracks = []
234 for idx_track, track in enumerate(obj.animation_data.nla_tracks):
235 if len(track.strips) == 0:
236 continue
238 stored_track = NLATrack(
239 idx_track,
240 track.strips[0].frame_start,
241 track.strips[-1].frame_end,
242 track.is_solo,
243 track.mute
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
249 pass
250 else:
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":
270 return [], [], []
271 if obj.data is None:
272 return [], [], []
273 if obj.data.shape_keys is None:
274 return [], [], []
275 if not obj.data.shape_keys.animation_data:
276 return [], [], []
277 if len(obj.data.shape_keys.animation_data.nla_tracks) == 0:
278 return [], [], []
280 exported_tracks = []
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:
286 continue
288 stored_track = NLATrack(
289 idx_track,
290 track.strips[0].frame_start,
291 track.strips[-1].frame_end,
292 track.is_solo,
293 track.mute
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
299 pass
300 else:
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):
316 track_slide = {}
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":
324 frame_start = 0
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})
339 else:
340 if frame_start < 0:
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})
348 else:
349 add_slide_data(frame_start, obj_uuid, track_name, export_settings)
352 # For drivers
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)