| 
									
										
										
										
											2021-01-07 01:05:09 -05:00
										 |  |  | """
 | 
					
						
							|  |  |  | Script for reading zaabin/zaa files and applying the unmunged animation | 
					
						
							|  |  |  | to the currently selected armature. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | As regards decompress_curves, I should really make a separate AnimationSet | 
					
						
							|  |  |  | dataclass instead of returning a convoluted nested dict. | 
					
						
							|  |  |  | """
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-07 00:26:19 -05:00
										 |  |  | import os | 
					
						
							|  |  |  | import bpy | 
					
						
							|  |  |  | import re | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-07 13:45:50 -05:00
										 |  |  | from .chunked_file_reader import Reader | 
					
						
							| 
									
										
										
										
											2021-01-07 00:26:19 -05:00
										 |  |  | from .crc import * | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | from .msh_model import * | 
					
						
							|  |  |  | from .msh_model_utilities import * | 
					
						
							|  |  |  | from .msh_utilities import * | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | from typing import List, Set, Dict, Tuple | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-07 13:45:50 -05:00
										 |  |  | debug = False | 
					
						
							| 
									
										
										
										
											2021-01-07 00:26:19 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-07 13:45:50 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  |                                       #anims  #bones   #components #keyframes: index,value | 
					
						
							| 
									
										
										
										
											2021-01-07 00:26:19 -05:00
										 |  |  | def decompress_curves(input_file) -> Dict[int, Dict[int, List[ Dict[int,float]]]]: | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-07 13:45:50 -05:00
										 |  |  |     global debug | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-07 00:26:19 -05:00
										 |  |  |     decompressed_anims: Dict[int, Dict[int, List[ Dict[int,float]]]] = {} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-07 13:45:50 -05:00
										 |  |  |     with Reader(input_file, debug=debug) as head: | 
					
						
							| 
									
										
										
										
											2021-01-07 01:05:09 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  |         # Dont read SMNA as child, since it has a length field always set to 0... | 
					
						
							| 
									
										
										
										
											2021-01-07 00:26:19 -05:00
										 |  |  |         head.skip_until("SMNA") | 
					
						
							|  |  |  |         head.skip_bytes(20) | 
					
						
							|  |  |  |         num_anims = head.read_u16() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-07 13:45:50 -05:00
										 |  |  |         if debug: | 
					
						
							|  |  |  |             print("\nFile contains {} animations\n".format(num_anims)) | 
					
						
							| 
									
										
										
										
											2021-01-07 00:26:19 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  |         head.skip_bytes(2) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         anim_crcs = [] | 
					
						
							|  |  |  |         anim_metadata = {} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-11 14:02:17 -05:00
										 |  |  |         head.skip_until("MINA") | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-07 01:05:09 -05:00
										 |  |  |         # Read metadata (crc, num frames, num bones) for each anim | 
					
						
							| 
									
										
										
										
											2021-01-07 00:26:19 -05:00
										 |  |  |         with head.read_child() as mina: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             for i in range(num_anims): | 
					
						
							| 
									
										
										
										
											2021-01-10 12:25:03 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  |                 transBitFlags = mina.read_u32() | 
					
						
							|  |  |  |                 mina.skip_bytes(4) | 
					
						
							| 
									
										
										
										
											2021-01-07 00:26:19 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-07 01:05:09 -05:00
										 |  |  |                 anim_crc = mina.read_u32()  | 
					
						
							|  |  |  |                 anim_crcs.append(anim_crc) | 
					
						
							| 
									
										
										
										
											2021-01-07 00:26:19 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-10 12:25:03 -05:00
										 |  |  |                 anim_metadata[anim_crc] = { | 
					
						
							|  |  |  |                     "num_frames" : mina.read_u16(), | 
					
						
							|  |  |  |                     "num_bones" : mina.read_u16(), | 
					
						
							|  |  |  |                     "transBitFlags" : transBitFlags, | 
					
						
							|  |  |  |                 } | 
					
						
							| 
									
										
										
										
											2021-01-07 00:26:19 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-11 14:02:17 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  |         head.skip_until("TNJA") | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-07 01:05:09 -05:00
										 |  |  |         # Read TADA offsets and quantization parameters for each rot + loc component, for each bone, for each anim | 
					
						
							| 
									
										
										
										
											2021-01-07 00:26:19 -05:00
										 |  |  |         with head.read_child() as tnja: | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-07 01:05:09 -05:00
										 |  |  |             for i, anim_crc in enumerate(anim_crcs): | 
					
						
							| 
									
										
										
										
											2021-01-07 00:26:19 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  |                 bone_params = {} | 
					
						
							| 
									
										
										
										
											2021-01-10 12:25:03 -05:00
										 |  |  |                 bone_list = [] | 
					
						
							| 
									
										
										
										
											2021-01-07 00:26:19 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  |                 for _ in range(anim_metadata[anim_crc]["num_bones"]): | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-10 12:25:03 -05:00
										 |  |  |                     bone_crc = tnja.read_u32() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                     bone_list.append(bone_crc)               | 
					
						
							| 
									
										
										
										
											2021-01-07 00:26:19 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-07 01:05:09 -05:00
										 |  |  |                     bone_params[bone_crc] = { | 
					
						
							|  |  |  |                         "rot_offsets" : [tnja.read_u32() for _ in range(4)], # Offsets into TADA for rotation  | 
					
						
							|  |  |  |                         "loc_offsets" : [tnja.read_u32() for _ in range(3)], # and translation curves | 
					
						
							|  |  |  |                         "qparams"     : [tnja.read_f32() for _ in range(4)], # Translation quantization parameters, 3 biases, 1 multiplier | 
					
						
							|  |  |  |                     } | 
					
						
							| 
									
										
										
										
											2021-01-07 00:26:19 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  |                 anim_metadata[anim_crc]["bone_params"] = bone_params | 
					
						
							| 
									
										
										
										
											2021-01-10 12:25:03 -05:00
										 |  |  |                 anim_metadata[anim_crc]["bone_list"] = bone_list | 
					
						
							| 
									
										
										
										
											2021-01-07 00:26:19 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-11 14:02:17 -05:00
										 |  |  |         head.skip_until("TADA") | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-07 01:05:09 -05:00
										 |  |  |         # Decompress/dequantize frame data into discrete per-component curves | 
					
						
							| 
									
										
										
										
											2021-01-07 00:26:19 -05:00
										 |  |  |         with head.read_child() as tada: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             for anim_crc in anim_crcs: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 decompressed_anims[anim_crc] = {} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 num_frames = anim_metadata[anim_crc]["num_frames"] | 
					
						
							|  |  |  |                 num_bones = anim_metadata[anim_crc]["num_bones"] | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-10 12:25:03 -05:00
										 |  |  |                 transBitFlags = anim_metadata[anim_crc]["transBitFlags"] | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-07 13:45:50 -05:00
										 |  |  |                 if debug: | 
					
						
							|  |  |  |                     print("\n\tAnim hash: {} Num frames: {} Num joints: {}".format(hex(anim_crc), num_frames, num_bones)) | 
					
						
							| 
									
										
										
										
											2021-01-07 00:26:19 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-10 12:25:03 -05:00
										 |  |  |                 for bone_num, bone_crc in enumerate(anim_metadata[anim_crc]["bone_list"]): | 
					
						
							| 
									
										
										
										
											2021-01-07 00:26:19 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-07 01:05:09 -05:00
										 |  |  |                     bone_curves = [] | 
					
						
							| 
									
										
										
										
											2021-01-07 00:26:19 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-07 01:05:09 -05:00
										 |  |  |                     params_bone = anim_metadata[anim_crc]["bone_params"][bone_crc] | 
					
						
							| 
									
										
										
										
											2021-01-07 00:26:19 -05:00
										 |  |  |                      | 
					
						
							|  |  |  |                     offsets_list = params_bone["rot_offsets"] + params_bone["loc_offsets"] | 
					
						
							|  |  |  |                     qparams = params_bone["qparams"] | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-07 13:45:50 -05:00
										 |  |  |                     if debug: | 
					
						
							|  |  |  |                         print("\n\t\tBone #{} hash: {}".format(bone_num,hex(bone_crc))) | 
					
						
							|  |  |  |                         print("\n\t\tQParams: {}, {}, {}, {}".format(*qparams)) | 
					
						
							| 
									
										
										
										
											2021-01-07 00:26:19 -05:00
										 |  |  |                      | 
					
						
							| 
									
										
										
										
											2021-01-07 01:05:09 -05:00
										 |  |  |                     for o, start_offset in enumerate(offsets_list): | 
					
						
							|  |  |  |                          | 
					
						
							|  |  |  |                         # Init curve dict | 
					
						
							|  |  |  |                         curve : Dict[int,float] = {} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                         # Init accumulator | 
					
						
							|  |  |  |                         accumulator = 0.0 | 
					
						
							| 
									
										
										
										
											2021-01-07 00:26:19 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-07 01:05:09 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  |                         # 2047 = max val of signed 12 bit int, the (overwhelmingly) common compression amount. | 
					
						
							|  |  |  |                         # This is used for all rotation components in the file, with no offset | 
					
						
							| 
									
										
										
										
											2021-01-07 00:26:19 -05:00
										 |  |  |                         if o < 4: | 
					
						
							|  |  |  |                             mult = 1 / 2047 | 
					
						
							| 
									
										
										
										
											2021-01-07 01:05:09 -05:00
										 |  |  |                             bias = 0.0 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                         # Translations have specific quantization parameters; biases for each component and  | 
					
						
							|  |  |  |                         # a single multiplier for all three | 
					
						
							| 
									
										
										
										
											2021-01-07 00:26:19 -05:00
										 |  |  |                         else: | 
					
						
							| 
									
										
										
										
											2021-01-10 12:25:03 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-07 00:26:19 -05:00
										 |  |  |                             mult = qparams[-1] | 
					
						
							| 
									
										
										
										
											2021-01-07 01:05:09 -05:00
										 |  |  |                             bias = qparams[o - 4] | 
					
						
							| 
									
										
										
										
											2021-01-07 00:26:19 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-07 13:45:50 -05:00
										 |  |  |                             if debug: | 
					
						
							|  |  |  |                                 print("\n\t\t\tBias = {}, multiplier = {}".format(bias, mult)) | 
					
						
							| 
									
										
										
										
											2021-01-07 00:26:19 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-07 13:45:50 -05:00
										 |  |  |                         if debug: | 
					
						
							|  |  |  |                             print("\n\t\t\tOffset {}: {} ({}, {} remaining)".format(o,start_offset, tada.get_current_pos(), tada.how_much_left(tada.get_current_pos()))) | 
					
						
							| 
									
										
										
										
											2021-01-07 00:26:19 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-10 12:25:03 -05:00
										 |  |  |                         # Skip to start of compressed data for component, as specified in TNJA | 
					
						
							|  |  |  |                         tada.skip_bytes(start_offset) | 
					
						
							|  |  |  |                          | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-07 00:26:19 -05:00
										 |  |  |                         j = 0 | 
					
						
							| 
									
										
										
										
											2021-01-07 01:05:09 -05:00
										 |  |  |                         while (j < num_frames): | 
					
						
							|  |  |  |                             accumulator = bias + mult * tada.read_i16() | 
					
						
							|  |  |  |                             curve[j if j < num_frames else num_frames] = accumulator | 
					
						
							| 
									
										
										
										
											2021-01-07 00:26:19 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-07 13:45:50 -05:00
										 |  |  |                             if debug: | 
					
						
							|  |  |  |                                 print("\t\t\t\t{}: {}".format(j, accumulator)) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-07 00:26:19 -05:00
										 |  |  |                             j+=1 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-07 01:05:09 -05:00
										 |  |  |                             while (j < num_frames): | 
					
						
							| 
									
										
										
										
											2021-01-07 00:26:19 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  |                                 control = tada.read_i8() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-07 01:05:09 -05:00
										 |  |  |                                 # Reset the accumulator to next dequantized i16 | 
					
						
							|  |  |  |                                 if control == -0x7f: | 
					
						
							| 
									
										
										
										
											2022-01-07 13:45:50 -05:00
										 |  |  |                                     if debug: | 
					
						
							|  |  |  |                                         print("\t\t\t\tControl: READING NEXT FRAME") | 
					
						
							| 
									
										
										
										
											2021-01-07 01:05:09 -05:00
										 |  |  |                                     break | 
					
						
							| 
									
										
										
										
											2021-01-07 00:26:19 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-07 01:05:09 -05:00
										 |  |  |                                 # RLE: hold current accumulator for the next u8 frames  | 
					
						
							| 
									
										
										
										
											2021-01-07 00:26:19 -05:00
										 |  |  |                                 elif control == -0x80: | 
					
						
							|  |  |  |                                     num_skips = tada.read_u8() | 
					
						
							| 
									
										
										
										
											2022-01-07 13:45:50 -05:00
										 |  |  |                                     if debug: | 
					
						
							|  |  |  |                                         print("\t\t\t\tControl: HOLDING FOR {} FRAMES".format(num_skips)) | 
					
						
							| 
									
										
										
										
											2021-01-07 01:05:09 -05:00
										 |  |  |                                     j += num_skips | 
					
						
							| 
									
										
										
										
											2021-01-07 00:26:19 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-07 01:05:09 -05:00
										 |  |  |                                 # If not a special value, increment accumulator by the dequantized i8 | 
					
						
							|  |  |  |                                 # The bias is NOT applied here, only for accumulator resets | 
					
						
							| 
									
										
										
										
											2021-01-07 00:26:19 -05:00
										 |  |  |                                 else: | 
					
						
							| 
									
										
										
										
											2021-01-07 01:05:09 -05:00
										 |  |  |                                     accumulator += mult * float(control)  | 
					
						
							|  |  |  |                                     curve[j if j < num_frames else num_frames] = accumulator | 
					
						
							| 
									
										
										
										
											2022-01-07 13:45:50 -05:00
										 |  |  |                                     if debug: | 
					
						
							|  |  |  |                                         print("\t\t\t\t{}: {}".format(j, accumulator)) | 
					
						
							| 
									
										
										
										
											2021-01-07 00:26:19 -05:00
										 |  |  |                                     j+=1  | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-07 01:05:09 -05:00
										 |  |  |                         curve[num_frames - 1] = accumulator                           | 
					
						
							| 
									
										
										
										
											2021-01-07 00:26:19 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  |                         tada.reset_pos()  | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-07 01:05:09 -05:00
										 |  |  |                         bone_curves.append(curve) | 
					
						
							| 
									
										
										
										
											2021-01-07 00:26:19 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-07 01:05:09 -05:00
										 |  |  |                     decompressed_anims[anim_crc][bone_crc] = bone_curves | 
					
						
							| 
									
										
										
										
											2021-01-07 00:26:19 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  |     return decompressed_anims | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-07 01:05:09 -05:00
										 |  |  | '''
 | 
					
						
							|  |  |  | Gets the animation names from the supplied | 
					
						
							|  |  |  | .anims file. Handy since .zaabin files often | 
					
						
							|  |  |  | share a dir with a .anims file. | 
					
						
							|  |  |  | '''
 | 
					
						
							| 
									
										
										
										
											2021-01-07 00:26:19 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  | def read_anims_file(anims_file_path): | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if not os.path.exists(anims_file_path): | 
					
						
							| 
									
										
										
										
											2021-01-07 01:05:09 -05:00
										 |  |  |         return [] | 
					
						
							| 
									
										
										
										
											2021-01-07 00:26:19 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  |     with open(anims_file_path, 'r') as file: | 
					
						
							|  |  |  |         anims_text = file.read() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     splits = anims_text.split('"') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if len(splits) > 1: | 
					
						
							|  |  |  |         return splits[1:-1:2] | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-07 01:05:09 -05:00
										 |  |  |     return [] | 
					
						
							| 
									
										
										
										
											2021-01-07 00:26:19 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-07 01:05:09 -05:00
										 |  |  | '''
 | 
					
						
							|  |  |  | Unmunge the .zaa(bin) file and apply the resulting animation | 
					
						
							|  |  |  | to the currently selected armature object. | 
					
						
							| 
									
										
										
										
											2021-01-07 00:26:19 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-07 01:05:09 -05:00
										 |  |  | Contains some bloated code for calculating the world transforms of each bone, | 
					
						
							|  |  |  | for now this will work ONLY if the model was directly imported from a .msh file. | 
					
						
							|  |  |  | '''
 | 
					
						
							| 
									
										
										
										
											2021-01-07 00:26:19 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  | def extract_and_apply_munged_anim(input_file_path): | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-07 13:45:50 -05:00
										 |  |  |     global debug | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-07 00:26:19 -05:00
										 |  |  |     with open(input_file_path,"rb") as input_file: | 
					
						
							| 
									
										
										
										
											2021-01-07 01:05:09 -05:00
										 |  |  |         animation_set = decompress_curves(input_file) | 
					
						
							| 
									
										
										
										
											2021-01-07 00:26:19 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-07 01:05:09 -05:00
										 |  |  |     anim_names = [] | 
					
						
							| 
									
										
										
										
											2021-01-07 00:26:19 -05:00
										 |  |  |     if input_file_path.endswith(".zaabin"): | 
					
						
							|  |  |  |         anim_names = read_anims_file(input_file_path.replace(".zaabin", ".anims")) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     arma = bpy.context.view_layer.objects.active | 
					
						
							|  |  |  |     if arma.type != 'ARMATURE': | 
					
						
							|  |  |  |         raise Exception("Select an armature to attach the imported animation to!") | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     if arma.animation_data is not None: | 
					
						
							|  |  |  |         arma.animation_data_clear() | 
					
						
							|  |  |  |     arma.animation_data_create() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-07 01:05:09 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     When directly imported from .msh files, | 
					
						
							|  |  |  |     all skeleton models are saved as emptys, since | 
					
						
							|  |  |  |     some are excluded from the actual armature (effectors, roots, eg...). | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     bond_bind_poses contains matrices for converting the transform of  | 
					
						
							|  |  |  |     bones found in .msh/.zaabin files to ones that'll fit the extracted armature. | 
					
						
							|  |  |  |     This will be replaced with the eventual importer release. | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-07 13:45:50 -05:00
										 |  |  |     animated_bones = set() | 
					
						
							|  |  |  |     for anim_crc in animation_set: | 
					
						
							|  |  |  |         for bone_crc in animation_set[anim_crc]: | 
					
						
							|  |  |  |             animated_bones.add(bone_crc) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     bpy.context.view_layer.objects.active = arma | 
					
						
							|  |  |  |     bpy.ops.object.mode_set(mode='EDIT') | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-07 00:26:19 -05:00
										 |  |  |     bone_bind_poses = {} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-07 13:45:50 -05:00
										 |  |  |     for edit_bone in arma.data.edit_bones: | 
					
						
							|  |  |  |         if to_crc(edit_bone.name) not in animated_bones: | 
					
						
							|  |  |  |             continue | 
					
						
							| 
									
										
										
										
											2021-01-07 00:26:19 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-07 13:45:50 -05:00
										 |  |  |         curr_ancestor = edit_bone.parent | 
					
						
							|  |  |  |         while curr_ancestor is not None and to_crc(curr_ancestor.name) not in animated_bones: | 
					
						
							|  |  |  |             curr_ancestor = curr_ancestor.parent | 
					
						
							| 
									
										
										
										
											2021-01-07 00:26:19 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-07 13:45:50 -05:00
										 |  |  |         if curr_ancestor: | 
					
						
							|  |  |  |             bind_mat = curr_ancestor.matrix.inverted() @ edit_bone.matrix  | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             bind_mat = arma.matrix_local @ edit_bone.matrix | 
					
						
							| 
									
										
										
										
											2021-01-07 00:26:19 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-07 13:45:50 -05:00
										 |  |  |         bone_bind_poses[edit_bone.name] = bind_mat.inverted() | 
					
						
							| 
									
										
										
										
											2021-01-07 00:26:19 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-07 13:45:50 -05:00
										 |  |  |     bpy.ops.object.mode_set(mode='OBJECT') | 
					
						
							| 
									
										
										
										
											2021-01-07 00:26:19 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-07 13:45:50 -05:00
										 |  |  |     if debug: | 
					
						
							|  |  |  |         print("Extracting {} animations from {}:".format(len(animation_set), input_file_path)) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-07 01:05:09 -05:00
										 |  |  |     for anim_crc in animation_set: | 
					
						
							| 
									
										
										
										
											2021-01-07 00:26:19 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-07 01:25:09 -05:00
										 |  |  |         found_anim = [anim_name for anim_name in anim_names if to_crc(anim_name) == anim_crc] | 
					
						
							| 
									
										
										
										
											2021-01-07 01:05:09 -05:00
										 |  |  |         if found_anim: | 
					
						
							|  |  |  |             anim_str = found_anim[0] | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             anim_str = str(hex(anim_crc))  | 
					
						
							| 
									
										
										
										
											2021-01-07 00:26:19 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-07 13:45:50 -05:00
										 |  |  |         if debug: | 
					
						
							|  |  |  |             print("\tExtracting anim {}:".format(anim_str)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         #if anim_str in bpy.data.actions: | 
					
						
							|  |  |  |         #    bpy.data.actions[anim_str].use_fake_user = False | 
					
						
							|  |  |  |         #    bpy.data.actions.remove(bpy.data.actions[anim_str]) | 
					
						
							| 
									
										
										
										
											2021-01-07 00:26:19 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  |         action = bpy.data.actions.new(anim_str) | 
					
						
							|  |  |  |         action.use_fake_user = True | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-07 01:05:09 -05:00
										 |  |  |         animation = animation_set[anim_crc] | 
					
						
							| 
									
										
										
										
											2021-01-07 00:26:19 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-07 13:45:50 -05:00
										 |  |  |         bone_crcs_list = [bone_crc_ for bone_crc_ in animation] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         for bone_crc in sorted(bone_crcs_list): | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             bone_name = next((bone.name for bone in arma.pose.bones if to_crc(bone.name) == bone_crc), None) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             if bone_name is None: | 
					
						
							|  |  |  |                 continue | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             bone = arma.pose.bones[bone_name] | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-07 01:25:09 -05:00
										 |  |  |             bone_crc = to_crc(bone.name) | 
					
						
							| 
									
										
										
										
											2021-01-07 00:26:19 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-07 01:05:09 -05:00
										 |  |  |             if bone_crc not in animation: | 
					
						
							| 
									
										
										
										
											2021-01-07 00:26:19 -05:00
										 |  |  |                 continue; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             bind_mat = bone_bind_poses[bone.name] | 
					
						
							|  |  |  |             loc_data_path = "pose.bones[\"{}\"].location".format(bone.name)  | 
					
						
							|  |  |  |             rot_data_path = "pose.bones[\"{}\"].rotation_quaternion".format(bone.name)  | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-07 01:05:09 -05:00
										 |  |  |             bone_curves = animation[bone_crc] | 
					
						
							| 
									
										
										
										
											2021-01-07 00:26:19 -05:00
										 |  |  |             num_frames = max(bone_curves[0]) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-10 12:25:03 -05:00
										 |  |  |             has_translation = bone_curves[4] is not None | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-07 13:45:50 -05:00
										 |  |  |             if debug: | 
					
						
							|  |  |  |                 print("\t\tBone {} has {} frames: ".format(bone_name, num_frames)) | 
					
						
							| 
									
										
										
										
											2021-01-07 00:26:19 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  |             last_values = [1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             def get_quat(index): | 
					
						
							|  |  |  |                 nonlocal bone_curves, last_values | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 q = Quaternion() | 
					
						
							|  |  |  |                 valmap = [1,2,3,0] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 has_key = False | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 for i in range(4): | 
					
						
							|  |  |  |                     curve = bone_curves[i] | 
					
						
							|  |  |  |                     if index in curve: | 
					
						
							|  |  |  |                         has_key = True | 
					
						
							|  |  |  |                         last_values[i] = curve[index] | 
					
						
							|  |  |  |                     q[valmap[i]] = last_values[i] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 return q if has_key else None | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             def get_vec(index): | 
					
						
							|  |  |  |                 nonlocal bone_curves, last_values | 
					
						
							|  |  |  |                  | 
					
						
							|  |  |  |                 v = Vector() | 
					
						
							|  |  |  |                 has_key = False | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 for i in range(4,7): | 
					
						
							|  |  |  |                     curve = bone_curves[i] | 
					
						
							|  |  |  |                     if index in curve: | 
					
						
							|  |  |  |                         has_key = True | 
					
						
							|  |  |  |                         last_values[i] = curve[index] | 
					
						
							|  |  |  |                     v[i - 4] = last_values[i] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 return v if has_key else None | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             fcurve_rot_w = action.fcurves.new(rot_data_path, index=0, action_group=bone.name) | 
					
						
							|  |  |  |             fcurve_rot_x = action.fcurves.new(rot_data_path, index=1, action_group=bone.name) | 
					
						
							|  |  |  |             fcurve_rot_y = action.fcurves.new(rot_data_path, index=2, action_group=bone.name) | 
					
						
							|  |  |  |             fcurve_rot_z = action.fcurves.new(rot_data_path, index=3, action_group=bone.name) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-10 12:25:03 -05:00
										 |  |  |             if has_translation: | 
					
						
							|  |  |  |                 fcurve_loc_x = action.fcurves.new(loc_data_path, index=0, action_group=bone.name) | 
					
						
							|  |  |  |                 fcurve_loc_y = action.fcurves.new(loc_data_path, index=1, action_group=bone.name) | 
					
						
							|  |  |  |                 fcurve_loc_z = action.fcurves.new(loc_data_path, index=2, action_group=bone.name) | 
					
						
							| 
									
										
										
										
											2021-01-07 00:26:19 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  |             for frame in range(num_frames): | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 q = get_quat(frame) | 
					
						
							|  |  |  |                 if q is not None: | 
					
						
							| 
									
										
										
										
											2021-01-10 12:25:03 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-07 13:45:50 -05:00
										 |  |  |                     if debug: | 
					
						
							|  |  |  |                         print("\t\t\tRot key: ({}, {})".format(frame, quat_to_str(q))) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-07 01:05:09 -05:00
										 |  |  |                     # Very bloated, but works for now | 
					
						
							| 
									
										
										
										
											2021-01-07 00:26:19 -05:00
										 |  |  |                     q = (bind_mat @ convert_rotation_space(q).to_matrix().to_4x4()).to_quaternion() | 
					
						
							|  |  |  |                     fcurve_rot_w.keyframe_points.insert(frame,q.w) | 
					
						
							|  |  |  |                     fcurve_rot_x.keyframe_points.insert(frame,q.x) | 
					
						
							|  |  |  |                     fcurve_rot_y.keyframe_points.insert(frame,q.y) | 
					
						
							|  |  |  |                     fcurve_rot_z.keyframe_points.insert(frame,q.z) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-10 12:25:03 -05:00
										 |  |  |                 if has_translation: | 
					
						
							|  |  |  |                      | 
					
						
							|  |  |  |                     t = get_vec(frame) | 
					
						
							|  |  |  |                     if t is not None: | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-07 13:45:50 -05:00
										 |  |  |                         if debug: | 
					
						
							|  |  |  |                             print("\t\t\tPos key: ({}, {})".format(frame, vec_to_str(t))) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-10 12:25:03 -05:00
										 |  |  |                         t = (bind_mat @ Matrix.Translation(convert_vector_space(t))).translation | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                         fcurve_loc_x.keyframe_points.insert(frame,t.x) | 
					
						
							|  |  |  |                         fcurve_loc_y.keyframe_points.insert(frame,t.y) | 
					
						
							|  |  |  |                         fcurve_loc_z.keyframe_points.insert(frame,t.z) | 
					
						
							| 
									
										
										
										
											2021-01-07 00:26:19 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  |         arma.animation_data.action = action | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 |