├── README.md ├── .gitignore ├── lmt ├── Lmt.py └── Cstruct.py └── __init__.py /README.md: -------------------------------------------------------------------------------- 1 | # MHW-LMT-Loader 2 | Import Monster Hunter: Word animation files into blender 3 | 4 | ## Install 5 | Download zip file and extract in blender addons folder 6 | 7 | ## Instructions 8 | - Load a model with [Mod3 Importer](https://github.com/AsteriskAmpersand/Mod3-MHW-Importer)'s **latest version** 9 | - Choose 'Animation Armature' in the 'Import Skeleton' field 10 | 11 | - Go to File -> Import -> MHW Lmt (.lmt) 12 | - Choose your file 13 | - Change the animation id field is needed 14 | - By default this will load all animations in the lmt (can take several minutes, blender *will* freeze) 15 | - Each animation is loaded as an action in blender 16 | 17 | 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | testdata/ 3 | 4 | # Byte-compiled / optimized / DLL files 5 | __pycache__/ 6 | *.py[cod] 7 | *$py.class 8 | 9 | # C extensions 10 | *.so 11 | 12 | # Distribution / packaging 13 | .Python 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | downloads/ 18 | eggs/ 19 | .eggs/ 20 | lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | wheels/ 26 | pip-wheel-metadata/ 27 | share/python-wheels/ 28 | *.egg-info/ 29 | .installed.cfg 30 | *.egg 31 | MANIFEST 32 | 33 | # PyInstaller 34 | # Usually these files are written by a python script from a template 35 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 36 | *.manifest 37 | *.spec 38 | 39 | # Installer logs 40 | pip-log.txt 41 | pip-delete-this-directory.txt 42 | 43 | # Unit test / coverage reports 44 | htmlcov/ 45 | .tox/ 46 | .nox/ 47 | .coverage 48 | .coverage.* 49 | .cache 50 | nosetests.xml 51 | coverage.xml 52 | *.cover 53 | .hypothesis/ 54 | .pytest_cache/ 55 | 56 | # Translations 57 | *.mo 58 | *.pot 59 | 60 | # Django stuff: 61 | *.log 62 | local_settings.py 63 | db.sqlite3 64 | db.sqlite3-journal 65 | 66 | # Flask stuff: 67 | instance/ 68 | .webassets-cache 69 | 70 | # Scrapy stuff: 71 | .scrapy 72 | 73 | # Sphinx documentation 74 | docs/_build/ 75 | 76 | # PyBuilder 77 | target/ 78 | 79 | # Jupyter Notebook 80 | .ipynb_checkpoints 81 | 82 | # IPython 83 | profile_default/ 84 | ipython_config.py 85 | 86 | # pyenv 87 | .python-version 88 | 89 | # pipenv 90 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 91 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 92 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 93 | # install all needed dependencies. 94 | #Pipfile.lock 95 | 96 | # celery beat schedule file 97 | celerybeat-schedule 98 | 99 | # SageMath parsed files 100 | *.sage.py 101 | 102 | # Environments 103 | .env 104 | .venv 105 | env/ 106 | venv/ 107 | ENV/ 108 | env.bak/ 109 | venv.bak/ 110 | 111 | # Spyder project settings 112 | .spyderproject 113 | .spyproject 114 | 115 | # Rope project settings 116 | .ropeproject 117 | 118 | # mkdocs documentation 119 | /site 120 | 121 | # mypy 122 | .mypy_cache/ 123 | .dmypy.json 124 | dmypy.json 125 | 126 | # Pyre type checker 127 | .pyre/ 128 | -------------------------------------------------------------------------------- /lmt/Lmt.py: -------------------------------------------------------------------------------- 1 | import json 2 | import struct 3 | from . import Cstruct as CS 4 | from io import BytesIO 5 | from collections import OrderedDict 6 | 7 | def readAt(data, offset, class_def): 8 | tell = data.tell() 9 | data.seek(offset) 10 | ret = class_def(data) 11 | data.seek(tell) 12 | return ret 13 | 14 | def align(num, amount): 15 | return (num + (amount - 1)) & ~(amount - 1) 16 | 17 | def pad(array, amount): 18 | padAmount = align(len(array), amount) - len(array) 19 | return array + b'\0' * padAmount 20 | 21 | class LMT(CS.PyCStruct): 22 | fields = OrderedDict([ 23 | ("magic", "byte[4]"), 24 | ("version", "short"), 25 | ("entry_count", "short"), 26 | ("unkn", "byte[8]"), 27 | ]) 28 | 29 | def __init__(self, data, **kwargs): 30 | self.full_data = readAt(data, data.tell(), lambda d: d.read()) 31 | super().__init__(data, **kwargs) 32 | self.animation_offsets = [] 33 | for i in range(self.entry_count): 34 | self.animation_offsets += [struct.unpack('Q', data.read(8))[0]] 35 | 36 | def get_animation(self, id): 37 | if self.animation_offsets[id] == 0: 38 | return None 39 | return readAt(BytesIO(self.full_data), self.animation_offsets[id], AnimationBlock) 40 | 41 | def override_animation(self, id, animation): 42 | offset = len(self.full_data) 43 | offset = align(offset, 16) 44 | self.animation_offsets[id] = offset 45 | animation.update_offsets(offset) 46 | self.full_data = pad(self.full_data, 16) + animation.serialize() 47 | 48 | def serialize(self): 49 | ret = super().serialize() 50 | for offset in self.animation_offsets: 51 | ret += struct.pack('Q', offset) 52 | ret += self.full_data[len(ret):] 53 | return ret 54 | 55 | class AnimationBlock(CS.PyCStruct): 56 | fields = OrderedDict([ 57 | ("bone_paths_offset", "uint64"), 58 | ("bone_path_count", "int"), 59 | ("frame_count", "int"), 60 | ("loop_frame", "int"), 61 | ("unkn", "int[17]"), 62 | ("events_offset", "uint64") 63 | ]) 64 | 65 | def __init__(self, data = None, **kw): 66 | super().__init__(data, **kw) 67 | if not data: return 68 | 69 | self.bone_paths = [] 70 | for i in range(self.bone_path_count): 71 | self.bone_paths += [readAt(data, self.bone_paths_offset + len(BonePath()) * i, BonePath)] 72 | 73 | if self.events_offset: 74 | self.events = readAt(data, self.events_offset, Events) 75 | 76 | def update_offsets(self, offset): 77 | offset += len(self) 78 | offset = self.update_data_offsets(offset) 79 | return offset 80 | 81 | def update_data_offsets(self, offset): 82 | self.bone_paths_offset = offset 83 | self.bone_path_count = len(self.bone_paths) 84 | offset += self.bone_path_count * len(BonePath()) 85 | for path in self.bone_paths: 86 | if path.bounds: 87 | path.bounds_offset = offset 88 | offset += len(BonePath.Bounds()) 89 | else: 90 | path.bounds_offset = 0 91 | 92 | for i, path in enumerate(self.bone_paths): 93 | path.buffer_size = len(path.buffer) 94 | if path.buffer_size: 95 | path.buffer_offset = offset 96 | else: 97 | path.buffer_offset = 0 98 | offset = align(offset + path.buffer_size, 4) 99 | 100 | offset = align(offset, 16) 101 | self.events_offset = offset 102 | offset = self.events.update_offsets(offset) 103 | return offset 104 | 105 | def serialize(self, offset = None): 106 | if offset is not None: 107 | self.update_offsets(offset) 108 | ret = self.serialize_block() 109 | ret += self.serialize_data() 110 | return ret 111 | 112 | def serialize_block(self): 113 | return super().serialize() 114 | 115 | def serialize_data(self): 116 | ret = b'' 117 | for path in self.bone_paths: 118 | ret += path.serialize() 119 | 120 | for path in self.bone_paths: 121 | if path.bounds: 122 | ret += path.bounds.serialize() 123 | 124 | for path in self.bone_paths: 125 | ret += path.buffer 126 | ret = pad(ret, 4) 127 | 128 | ret = pad(ret, 16) 129 | ret += self.events.serialize() 130 | return ret 131 | 132 | 133 | class BonePath(CS.PyCStruct): 134 | class Bounds(CS.PyCStruct): 135 | fields = OrderedDict([ 136 | ("mult", "float[4]"), 137 | ("add", "float[4]") 138 | ]) 139 | 140 | fields = OrderedDict([ 141 | ("buffer_type", "ubyte"), 142 | ("usage", "ubyte"), 143 | ("joint_type", "ubyte"), 144 | ("unkn", "ubyte"), 145 | ("bone_id", "int"), 146 | ("weight", "float"), 147 | ("buffer_size", "int"), 148 | ("buffer_offset", "int64"), 149 | ("reference_frame", "float[4]"), 150 | ("bounds_offset", "int64"), 151 | ]) 152 | 153 | def __init__(self, data = None, **kw): 154 | super().__init__(data, **kw) 155 | if data: 156 | self.buffer = b'' 157 | if self.buffer_size and self.buffer_offset: 158 | self.buffer = readAt(data, self.buffer_offset, lambda d: d.read(self.buffer_size)) 159 | self.bounds = None 160 | if self.bounds_offset: 161 | self.bounds = readAt(data, self.bounds_offset, self.Bounds) 162 | 163 | 164 | 165 | class Events(CS.PyCStruct): 166 | class EventParameter(CS.PyCStruct): 167 | fields = OrderedDict([ 168 | ("offset", "uint64"), 169 | ("count", "uint64"), 170 | ("type", "ubyte[8]") 171 | ]) 172 | 173 | class Data(CS.PyCStruct): 174 | fields = OrderedDict([ 175 | ("values", "ubyte[20]") 176 | ]) 177 | 178 | fields = OrderedDict([ 179 | ("events_offset", "uint64"), 180 | ("event_count", "uint64"), 181 | ("unkn", "int[8]") 182 | ]) 183 | 184 | 185 | def __init__(self, data = None, **kw): 186 | super().__init__(data, **kw) 187 | if not data: 188 | return 189 | 190 | self.events = [] 191 | for i in range(self.event_count): 192 | self.events += [readAt(data, self.events_offset + i * len(self.EventParameter()), self.EventParameter)] 193 | 194 | for event in self.events: 195 | event.parameters = [] 196 | for i in range(event.count): 197 | parameter = readAt(data, event.offset + i * len(self.EventParameter()), self.EventParameter) 198 | parameter.buffer = [] 199 | for j in range(parameter.count): 200 | parameter.buffer += [readAt(data, parameter.offset + j * len(self.Data()), self.Data)] 201 | event.parameters += [parameter] 202 | 203 | def update_offsets(self, offset): 204 | offset += len(self) 205 | self.events_offset = offset 206 | self.event_count = len(self.events) 207 | 208 | offset += len(self.EventParameter()) * self.event_count 209 | offset = align(offset, 16) 210 | 211 | for event in self.events: 212 | event.count = len(event.parameters) 213 | event.offset = offset 214 | offset += event.count * len(self.EventParameter()) 215 | offset = align(offset, 16) 216 | 217 | for event in self.events: 218 | for parameter in event.parameters: 219 | parameter.count = len(parameter.buffer) 220 | parameter.offset = offset 221 | offset += parameter.count * len(self.Data()) 222 | offset = align(offset, 16) 223 | 224 | return offset 225 | 226 | def serialize(self): 227 | ret = super().serialize() 228 | 229 | for event in self.events: 230 | ret += event.serialize() 231 | ret = pad(ret, 16) 232 | 233 | 234 | for event in self.events: 235 | for parameter in event.parameters: 236 | ret += parameter.serialize() 237 | ret = pad(ret, 16) 238 | 239 | for event in self.events: 240 | for parameter in event.parameters: 241 | for data in parameter.buffer: 242 | ret += data.serialize() 243 | ret = pad(ret, 16) 244 | 245 | return ret -------------------------------------------------------------------------------- /lmt/Cstruct.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Mon Jan 28 13:38:38 2019 4 | 5 | @author: AsteriskAmpersand 6 | """ 7 | import struct 8 | from collections import OrderedDict 9 | from binascii import hexlify 10 | from io import BytesIO 11 | 12 | def chunks(l, n): 13 | """Yield successive n-sized chunks from l.""" 14 | for i in range(0, len(l), n): 15 | yield l[i:i + n] 16 | 17 | def HalfToFloat(h): 18 | s = int((h >> 15) & 0x00000001) # sign 19 | e = int((h >> 10) & 0x0000001f) # exponent 20 | f = int(h & 0x000003ff) # fraction 21 | if s==0 and e==0 and f==0: 22 | return 0 23 | return (-1)**s*2**(e-15)*(f/(2**10)+1) 24 | 25 | def minifloatDeserialize(x): 26 | v = struct.unpack('H', x) 27 | return HalfToFloat(v[0]) 28 | # x = HalfToFloat(v[0]) 29 | # # hack to coerce int to float 30 | # bstring = struct.pack('I',x) 31 | # f=struct.unpack('f', bstring) 32 | # return f[0] 33 | 34 | def minifloatSerialize(x): 35 | F16_EXPONENT_BITS = 0x1F 36 | F16_EXPONENT_SHIFT = 10 37 | F16_EXPONENT_BIAS = 15 38 | F16_MANTISSA_BITS = 0x3ff 39 | F16_MANTISSA_SHIFT = (23 - F16_EXPONENT_SHIFT) 40 | F16_MAX_EXPONENT = (F16_EXPONENT_BITS << F16_EXPONENT_SHIFT) 41 | a = struct.pack('>f',x) 42 | b = hexlify(a) 43 | f32 = int(b,16) 44 | f16 = 0 45 | sign = (f32 >> 16) & 0x8000 46 | exponent = ((f32 >> 23) & 0xff) - 127 47 | mantissa = f32 & 0x007fffff 48 | if exponent == 128: 49 | f16 = sign | F16_MAX_EXPONENT 50 | if mantissa: 51 | f16 |= (mantissa & F16_MANTISSA_BITS) 52 | elif exponent > 15:#hack 53 | f16 = sign | F16_MAX_EXPONENT 54 | elif exponent >= -15:#hack 55 | exponent += F16_EXPONENT_BIAS 56 | mantissa >>= F16_MANTISSA_SHIFT 57 | f16 = (sign | exponent << F16_EXPONENT_SHIFT | mantissa) 58 | else: 59 | f16 = sign 60 | return struct.pack('H',f16) 61 | 62 | class StructManager(): 63 | structures = {} 64 | def register(self, classType): 65 | if not issubclass(classType,PyCStruct): 66 | raise ValueError("Class is not derived from PyCStruct") 67 | className, classObject = classType.__name__, classType 68 | self.structures[className] = classObject 69 | return True 70 | def __contains__(self, typeStr): 71 | return typeStr in self.structures 72 | def __getitem__(self, field): 73 | struct = self.structures[field] 74 | return {"size":len(struct()),"serializer":(lambda x: x.serialize()), "deserializer":(lambda x: struct().marshall())} 75 | 76 | class Cstruct(): 77 | deserializer = lambda y: {'deserializer':lambda x: struct.unpack(y,x)[0], 'serializer': lambda x: struct.pack(y,x)} 78 | CTypes = {"byte": {'size':1,**deserializer('b')}, 79 | "int8": {'size':1,**deserializer('b')}, 80 | "ubyte": {'size':1,**deserializer('B')}, 81 | "uint8": {'size':1,**deserializer('B')}, 82 | "short": {'size':2,**deserializer('h')}, 83 | "int16": {'size':2,**deserializer('h')}, 84 | "ushort": {'size':2,**deserializer('H')}, 85 | "uint16": {'size':2,**deserializer('H')}, 86 | "long": {'size':4,**deserializer('i')}, 87 | "int32": {'size':4,**deserializer('i')}, 88 | "int": {'size':4,**deserializer('i')}, 89 | "ulong": {'size':4,**deserializer('I')}, 90 | "uint32": {'size':4,**deserializer('I')}, 91 | "uint": {'size':4,**deserializer('I')}, 92 | "quad": {'size':8,**deserializer('q')}, 93 | "int64": {'size':8,**deserializer('q')}, 94 | "uquad": {'size':8,**deserializer('Q')}, 95 | "uint64": {'size':8,**deserializer('Q')}, 96 | "hfloat": {'size':2,'deserializer':minifloatDeserialize, 'serializer':minifloatSerialize}, 97 | "float": {'size':4,**deserializer('f')}, 98 | "double": {'size':8,**deserializer('d')}, 99 | "char": {'size':1,**deserializer('c')}, 100 | "bool": {'size':1,**deserializer('b')}, 101 | } 102 | StructTypes = StructManager() 103 | 104 | @staticmethod 105 | def register(classType): 106 | Cstruct.StructTypes.register(classType) 107 | 108 | @staticmethod 109 | def isStructType(typeStr): 110 | return typeStr in Cstruct.StructTypes 111 | 112 | @staticmethod 113 | def isArrayType(typeStr): 114 | return '[' in typeStr and (typeStr[:typeStr.index('[')] in Cstruct.CTypes or typeStr[:typeStr.index('[')] in Cstruct.StructTypes) 115 | 116 | @staticmethod 117 | def arrayType(typeStr): 118 | base = typeStr[:typeStr.index('[')] 119 | size = typeStr[typeStr.index('[')+1:typeStr.index(']')] 120 | baseTypeCall = Cstruct.CTypes if base in Cstruct.CTypes else Cstruct.StructTypes 121 | 122 | intSize = int(size) 123 | return { 124 | 'size': intSize*baseTypeCall[base]['size'], 125 | 'deserializer': lambda x: [baseTypeCall[base]['deserializer'](chunk) for chunk in chunks(x,baseTypeCall[base]['size']) ], 126 | 'serializer': lambda x: b''.join(map(baseTypeCall[base]['serializer'],x)) 127 | } if base != "char" else { 128 | 'size': intSize*baseTypeCall[base]['size'], 129 | 'deserializer': lambda x: ''.join([( baseTypeCall[base]['deserializer'](chunk) ).decode("ascii") for chunk in chunks(x,baseTypeCall[base]['size']) ]), 130 | 'serializer': lambda x: x.encode('ascii').ljust(intSize, b'\x00') 131 | } 132 | 133 | @staticmethod 134 | def structType(typeStr): 135 | return Cstruct.StructTypes[typeStr] 136 | 137 | @staticmethod 138 | def size(): 139 | return sum([self.struct[element]['size'] for element in self.struct]) 140 | 141 | def __init__(self, fields): 142 | self.struct = OrderedDict() 143 | self.initialized = True 144 | for name in fields: 145 | if fields[name] in Cstruct.CTypes: 146 | self.struct[name]=Cstruct.CTypes[fields[name]] 147 | elif Cstruct.isArrayType(fields[name]): 148 | self.struct[name]=Cstruct.arrayType(fields[name]) 149 | elif Cstruct.isStructType(fields[name]): 150 | self.struct[name]=Cstruct.structType(fields[name]) 151 | else: 152 | raise ValueError("%s Type is not C Struct class compatible."%fields[name]) 153 | 154 | def __len__(self): 155 | return sum([self.struct[element]['size'] for element in self.struct]) 156 | 157 | def marshall(self, data): 158 | return {varName:typeOperator['deserializer'](data.read(typeOperator['size'])) for varName, typeOperator in self.struct.items()} 159 | 160 | def serialize(self, data): 161 | return b''.join([typeOperator['serializer'](data[varName]) for varName, typeOperator in self.struct.items()]) 162 | 163 | class RegisteredClass(type): 164 | def __new__(cls, clsname, superclasses, attributedict): 165 | newclass = type.__new__(cls, clsname, superclasses, attributedict) 166 | # condition to prevent base class registration 167 | if superclasses: 168 | Cstruct.register(newclass) 169 | return newclass 170 | 171 | class PyCStruct(metaclass = RegisteredClass): 172 | def __init__(self, data = None, **kwargs): 173 | self.CStruct = Cstruct(self.fields) 174 | if data != None: 175 | self.marshall(data) 176 | elif kwargs: 177 | fieldskeys = set(self.fields.keys()) 178 | entrykeys=set(kwargs.keys()) 179 | if fieldskeys == entrykeys: 180 | [self.__setattr__(attr, value) for attr, value in kwargs.items()] 181 | else: 182 | if fieldskeys > entrykeys: 183 | raise AttributeError("Missing fields to Initialize") 184 | if fieldskeys < entrykeys: 185 | raise AttributeError("Excessive Fields passed") 186 | raise AttributeError("Field Mismatch") 187 | 188 | def __len__(self): 189 | return len(self.CStruct) 190 | 191 | def marshall(self,data): 192 | [self.__setattr__(attr, value) for attr, value in self.CStruct.marshall(data).items()] 193 | return self 194 | 195 | def serialize(self): 196 | return self.CStruct.serialize({key:self.__getattribute__(key) for key in self.fields}) 197 | 198 | def __eq__(self, other): 199 | return all([self.__getattribute__(key)==other.__getattribute__(key) for key in self.fields]) 200 | 201 | defaultProperties = {} 202 | requiredProperties = {} 203 | def construct(self,data): 204 | for field in self.fields: 205 | if field in data: 206 | self.__setattr__(field,data[field]) 207 | elif field in self.defaultProperties: 208 | self.__setattr__(field,self.defaultProperties[field]) 209 | elif field in self.requiredProperties: 210 | raise KeyError("Required Property missing in supplied data") 211 | else: 212 | self.__setattr__(field,None) 213 | 214 | def verify(self): 215 | for attr in self.fields: 216 | if self.__getattribute__(attr) is None: 217 | raise AssertionError("Attribute %s is not initialized."%attr) 218 | 219 | class Mod3Container(): 220 | def __init__(self, Mod3Class, containeeCount = 0): 221 | self.mod3Array = [Mod3Class() for _ in range(containeeCount)] 222 | 223 | def marshall(self, data): 224 | [x.marshall(data) for x in self.mod3Array] 225 | 226 | def construct(self, data): 227 | if len(data) != len(self.mod3Array): 228 | raise AssertionError("Cannot construct container with different amounts of data") 229 | [x.construct(d) for x,d in zip(self.mod3Array,data)] 230 | 231 | def serialize(self): 232 | return b''.join([element.serialize() for element in self.mod3Array]) 233 | 234 | def __iter__(self): 235 | return self.mod3Array.__iter__() 236 | 237 | def __getitem__(self, ix): 238 | return self.mod3Array.__getitem__(ix) 239 | 240 | def __len__(self): 241 | if self.mod3Array: 242 | return len(self.mod3Array)*len(self.mod3Array[0]) 243 | return 0 244 | 245 | def append(self, ele): 246 | self.mod3Array.append(ele) 247 | 248 | def pop(self,ix): 249 | self.mod3Array.pop(ix) 250 | 251 | def Count(self): 252 | return len(self.mod3Array) 253 | 254 | def verify(self): 255 | [x.verify() for x in self.mod3Array] 256 | 257 | def FileClass(capClass): 258 | class dummyClass(): 259 | def __init__(self, path): 260 | with open(path,"rb") as data: 261 | datum = BytesIO(data.read()) 262 | self.data = capClass().marshall(datum) 263 | return dummyClass -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import bpy_extras 3 | import struct 4 | from bpy.props import StringProperty, BoolProperty, EnumProperty 5 | from mathutils import Vector, Quaternion, Matrix 6 | from io import BytesIO 7 | from .lmt.Lmt import LMT, AnimationBlock 8 | 9 | bl_info = {"name": "LMT Importer", "category": "Animation"} 10 | 11 | def lerp3(value, bounds): 12 | if (bounds is None): 13 | return value 14 | value = Vector(value[:]) 15 | value = Vector(x * y for x, y in zip(value, bounds.mult[0:3])) 16 | return Vector(value + Vector(bounds.add[0:3])) 17 | 18 | def lerpq(value, bounds): 19 | ret = Quaternion() 20 | mult = Vector(bounds.mult) 21 | add = Vector(bounds.add) 22 | ret.x = add.x + value.x * mult.x 23 | ret.y = add.y + value.y * mult.y 24 | ret.z = add.z + value.z * mult.z 25 | ret.w = add.w + value.w * mult.w 26 | return ret 27 | 28 | def recompose(trs, rot, scl): 29 | return ( 30 | Matrix.Translation(trs) * rot.to_matrix().to_4x4() 31 | * Matrix.Scale(scl[0],4,(1, 0, 0)) 32 | * Matrix.Scale(scl[1],4,(0, 1, 0)) 33 | * Matrix.Scale(scl[2],4,(0, 0, 1)) 34 | ) 35 | 36 | def structread(io, format): 37 | return list(struct.unpack(format, io.read(struct.calcsize(format)))) 38 | 39 | class QuantizedVals: 40 | def __init__(self, array, bit_per_elem = 8): 41 | self.array = [int(x) for x in array] 42 | self.bit_per_elem = bit_per_elem 43 | self.total_bits = len(array) * bit_per_elem 44 | self.elem_bits = bit_per_elem 45 | 46 | def skipbits(self, bitcount): 47 | while bitcount > 0: 48 | count = min(bitcount, self.elem_bits) 49 | self.elem_bits -= count 50 | bitcount -= count 51 | if self.elem_bits == 0: 52 | self.array = self.array[1:] 53 | self.elem_bits = self.bit_per_elem 54 | else: 55 | self.array[0] = self.array[0] >> count 56 | self.total_bits -= bitcount 57 | 58 | def loadbits(self, bitcount, scale = 0): 59 | val = 0 60 | bits_left = bitcount 61 | current_elem = 0 62 | current_bitcount = min(self.elem_bits, bits_left) 63 | while bits_left > 0: 64 | val = val << current_bitcount 65 | val = val | (self.array[current_elem] & ((1 << current_bitcount) - 1)) 66 | bits_left -= current_bitcount 67 | current_elem += 1 68 | current_bitcount = min(self.bit_per_elem, bits_left) 69 | 70 | 71 | maxval = ((1 << bitcount) - 1) 72 | if scale == 1: 73 | val = val / maxval 74 | elif scale == -1: 75 | if val > (maxval >> 1): 76 | val -= maxval 77 | val = val / (maxval >> 1) 78 | return val 79 | 80 | def takebits(self, bitcount, scale = 0): 81 | ret = self.loadbits(bitcount, scale) 82 | self.skipbits(bitcount) 83 | return ret 84 | 85 | class Key: 86 | #if type == 1: 87 | def baseFloatVectorKey(self, io, bounds): 88 | self.value = Vector(structread(io, "fff")) 89 | self.frame = 1 90 | #elif type == 2 or type == 3 or type == 9: 91 | def floatVectorKey(self, io, bounds): 92 | self.value = Vector(structread(io, "fff")) 93 | self.frame = structread(io, "I")[0] 94 | #elif type == 4: 95 | def shortVectorKey(self, io, bounds): 96 | self.value = Vector((x / 65535) for x in structread(io, "HHH")) 97 | self.frame = structread(io, "H")[0] 98 | self.value = lerp3(self.value, bounds) 99 | 100 | #elif type == 5: 101 | def byteVectorKey(self, io, bounds): 102 | self.value = Vector((x / 255) for x in structread(io, "BBB")) 103 | self.frame = structread(io, "B")[0] 104 | self.value = lerp3(self.value, bounds) 105 | 106 | #elif type == 6: 107 | def bits14QuaternionKey(self, io, bounds): 108 | values = QuantizedVals(structread(io, "Q"), 64) 109 | self.value = Quaternion((1, 0, 0, 0)) 110 | self.value.w = values.takebits(14, -1) * 2 111 | self.value.z = values.takebits(14, -1) * 2 112 | self.value.y = values.takebits(14, -1) * 2 113 | self.value.x = values.takebits(14, -1) * 2 114 | self.frame = values.takebits(8) 115 | 116 | #elif type == 7: 117 | def bits7QuaternionKey(self, io, bounds): 118 | values = QuantizedVals(structread(io, "I"), 32) 119 | self.value = Quaternion((1, 0, 0, 0)) 120 | self.value.w = values.takebits(7, 1) 121 | self.value.z = values.takebits(7, 1) 122 | self.value.y = values.takebits(7, 1) 123 | self.value.x = values.takebits(7, 1) 124 | self.frame = values.takebits(4) 125 | self.value = lerpq(self.value, bounds) 126 | 127 | #elif type == 11: 128 | def XWQuaternionKey(self, io, bounds): 129 | value = QuantizedVals(structread(io, "I"), 32) 130 | self.value = Quaternion((1, 0, 0, 0)) 131 | if bounds is not None: 132 | self.value.x = value.takebits(14, 1) 133 | self.value.w = value.takebits(14, 1) 134 | self.value = lerpq(self.value, bounds) 135 | else: 136 | self.value.w = value.takebits(14) / 0xFFF 137 | self.value.x = value.takebits(14) 138 | if (self.value.x > 0x1fff != 0): 139 | self.value.x = -(0x1fff - self.value.x) 140 | self.value.x /= 0x8ff 141 | self.frame = value.takebits(4) 142 | #elif type == 12: 143 | def YWQuaternionKey(self, io, bounds): 144 | value = QuantizedVals(structread(io, "I"), 32) 145 | self.value = Quaternion((1, 0, 0, 0)) 146 | if bounds is not None: 147 | self.value.y = value.takebits(14, 1) 148 | self.value.w = value.takebits(14, 1) 149 | self.value = lerpq(self.value, bounds) 150 | else: 151 | self.value.w = value.takebits(14) / 0xFFF 152 | self.value.y = value.takebits(14) 153 | if (self.value.y > 0x1fff != 0): 154 | self.value.y = -(0x1fff - self.value.y) 155 | self.value.y /= 0x8ff 156 | self.frame = value.takebits(4) 157 | #elif type == 13: 158 | def ZWQuaternionKey(self, io, bounds): 159 | value = QuantizedVals(structread(io, "I"), 32) 160 | self.value = Quaternion((1, 0, 0, 0)) 161 | if bounds is not None: 162 | self.value.z = value.takebits(14, 1) 163 | self.value.w = value.takebits(14, 1) 164 | self.value = lerpq(self.value, bounds) 165 | else: 166 | self.value.w = value.takebits(14) / 0xFFF 167 | self.value.z = value.takebits(14) 168 | if (self.value.z > 0x1fff != 0): 169 | self.value.z = -(0x1fff - self.value.z) 170 | self.value.z /= 0x8ff 171 | self.frame = value.takebits(4) 172 | #elif type == 14: 173 | def bits11QuaternionKey(self, io, bounds): 174 | values = QuantizedVals(structread(io, "HHH"), 16) 175 | self.value = Quaternion((1, 0, 0, 0)) 176 | self.value.x = values.takebits(11, 1) 177 | self.value.y = values.takebits(11, 1) 178 | self.value.z = values.takebits(11, 1) 179 | self.value.w = values.takebits(11, 1) 180 | self.frame = values.takebits(4) 181 | self.value = lerpq(self.value, bounds) 182 | #15 183 | def bits9QuaternionKey(self, io, bounds): 184 | values = QuantizedVals(io.read(5)) 185 | self.value = Quaternion((1, 0, 0, 0)) 186 | self.value.x = values.takebits(9, 1) 187 | self.value.y = values.takebits(9, 1) 188 | self.value.z = values.takebits(9, 1) 189 | self.value.w = values.takebits(9, 1) 190 | self.frame = values.takebits(4) 191 | self.value = lerpq(self.value, bounds) 192 | 193 | def __init__(self, io, type, bounds): 194 | [ 195 | None, 196 | self.baseFloatVectorKey, 197 | self.floatVectorKey, 198 | self.floatVectorKey, 199 | self.shortVectorKey, 200 | self.byteVectorKey, 201 | self.bits14QuaternionKey, 202 | self.bits7QuaternionKey, 203 | None, 204 | self.floatVectorKey, 205 | None, 206 | self.XWQuaternionKey, 207 | self.YWQuaternionKey, 208 | self.ZWQuaternionKey, 209 | self.bits11QuaternionKey, 210 | self.bits9QuaternionKey 211 | ][type](io, bounds) 212 | 213 | 214 | class BaseKey: 215 | def __init__(self, ref, usage): 216 | if usage == 0 or usage == 3: 217 | self.value = Quaternion([ref[3]] + ref[0:3]) 218 | else: 219 | self.value = Vector(ref[0:3]) 220 | self.frame = 0 221 | 222 | class KeyFrameList: 223 | def __init__(self, bone_path): 224 | key_frame_list = [] 225 | io = BytesIO(bone_path.buffer) 226 | while io.tell() < len(bone_path.buffer): 227 | key_frame_list += [Key(io, bone_path.buffer_type, bone_path.bounds)] 228 | if not key_frame_list: 229 | key_frame_list += [BaseKey(bone_path.reference_frame, bone_path.usage)] 230 | self.bone_id = bone_path.bone_id 231 | self.usage = bone_path.usage 232 | self.keys = key_frame_list 233 | 234 | 235 | class Animation: 236 | def __init__(self, animation_block : AnimationBlock, armature_obj): 237 | self.block = animation_block 238 | self.key_frames = [KeyFrameList(b) for b in animation_block.bone_paths] 239 | 240 | self.bone_map = {-1: armature_obj.pose.bones[0]} 241 | for bone, edit_bone in zip(armature_obj.pose.bones, armature_obj.data.edit_bones): 242 | if "boneFunction" in edit_bone: 243 | bone["boneFunction"] = edit_bone["boneFunction"] 244 | self.bone_map[edit_bone["boneFunction"]] = bone 245 | 246 | def apply_animation(self, armature_obj): 247 | for key_frame_list in self.key_frames: 248 | if key_frame_list.bone_id not in self.bone_map: 249 | continue 250 | bone = self.bone_map[key_frame_list.bone_id] 251 | frameId = 0 252 | 253 | local_bone_matrix = bone.matrix 254 | if bone.parent: 255 | local_bone_matrix = armature_obj.convert_space(bone.parent, bone.matrix, 'POSE', 'LOCAL') 256 | 257 | if key_frame_list.usage == 0: 258 | for key in key_frame_list.keys: 259 | trs, rot, scl = local_bone_matrix.decompose() 260 | rot = key.value 261 | nmatrix = recompose(trs, rot, scl) 262 | bone.matrix = armature_obj.convert_space(bone.parent, nmatrix, 'LOCAL', 'POSE') 263 | bone.keyframe_insert('rotation_quaternion', frame=frameId) 264 | frameId += key.frame 265 | if key_frame_list.usage == 1: 266 | for key in key_frame_list.keys: 267 | trs, rot, scl = local_bone_matrix.decompose() 268 | trs = key.value 269 | nmatrix = recompose(trs, rot, scl) 270 | bone.matrix = armature_obj.convert_space(bone.parent, nmatrix, 'LOCAL', 'POSE') 271 | bone.keyframe_insert('location', frame=frameId) 272 | frameId += key.frame 273 | if key_frame_list.usage == 3: 274 | for key in key_frame_list.keys: 275 | trs, rot, scl = bone.matrix.decompose() 276 | rot = key.value 277 | bone.matrix = recompose(trs, rot, scl) 278 | bone.keyframe_insert('rotation_quaternion', frame=frameId) 279 | frameId += key.frame 280 | if key_frame_list.usage == 4: 281 | for key in key_frame_list.keys: 282 | trs, rot, scl = bone.matrix.decompose() 283 | trs = key.value 284 | bone.matrix = recompose(trs, rot, scl) 285 | bone.keyframe_insert('location', frame=frameId) 286 | frameId += key.frame 287 | 288 | class LmtImportOperator(bpy.types.Operator, bpy_extras.io_utils.ImportHelper): 289 | bl_idname = "custom_import.import_lmt" 290 | bl_label = "Import LMT Animation" 291 | bl_options = {'REGISTER', 'PRESET', 'UNDO'} 292 | 293 | filename_ext = ".lmt" 294 | filter_glob = StringProperty(default="*.lmt", options={'HIDDEN'}, maxlen=255) 295 | animation_id = StringProperty(default="*", maxlen=20, name="Animation ID", description="'*' to load all animations, otherwise one number") 296 | 297 | def execute(self, context): 298 | lmt = LMT(open(self.properties.filepath, 'rb')) 299 | for obj in context.scene.objects: 300 | if (obj.type == 'ARMATURE'): 301 | armature_obj = obj 302 | break 303 | if not armature_obj: 304 | return 305 | context.scene.objects.active = armature_obj 306 | bpy.ops.object.mode_set(mode='EDIT') 307 | 308 | 309 | armature_obj.animation_data_create() 310 | ids_to_extract = range(lmt.entry_count) 311 | if self.animation_id != "*": 312 | ids_to_extract = [int(self.animation_id)] 313 | for id in ids_to_extract: 314 | animation = lmt.get_animation(id) 315 | if animation: 316 | print('\rLoading animation %03d / %03d' % (id, lmt.entry_count), end='') 317 | animation = Animation(animation, armature_obj) 318 | for bone in armature_obj.pose.bones: 319 | bone.matrix_basis = Matrix() 320 | armature_obj.animation_data.action = bpy.data.actions.new("Animation %03d" % id) 321 | animation.apply_animation(armature_obj) 322 | 323 | bpy.ops.object.mode_set(mode='POSE') 324 | return {'FINISHED'} 325 | 326 | 327 | def menu_func_import(self, context): 328 | self.layout.operator(LmtImportOperator.bl_idname, text="MHW LMT (.lmt)") 329 | 330 | def register(): 331 | bpy.utils.register_class(LmtImportOperator) 332 | bpy.types.INFO_MT_file_import.append(menu_func_import) 333 | 334 | 335 | def unregister(): 336 | bpy.utils.unregister_class(LmtImportOperator) 337 | bpy.types.INFO_MT_file_import.remove(menu_func_import) 338 | --------------------------------------------------------------------------------