From 385efc7d42de0ac552914653fd592ca9e50d0da0 Mon Sep 17 00:00:00 2001 From: jugol Date: Sun, 25 Aug 2019 17:23:24 -0400 Subject: [PATCH 1/4] Loading textures when possible / Added some logging --- io_import_pokemon_masters.py | 123 ++++++++++++++++++++++++++++++----- 1 file changed, 105 insertions(+), 18 deletions(-) diff --git a/io_import_pokemon_masters.py b/io_import_pokemon_masters.py index a86aa7e..02de051 100644 --- a/io_import_pokemon_masters.py +++ b/io_import_pokemon_masters.py @@ -25,8 +25,19 @@ CollectionProperty ) from bpy_extras.io_utils import ImportHelper - - +import fnmatch +import traceback + +# To find a file in a path (including subfolders). Thanks Nadia Alramli +# for the answer from some corner in StackOverflow a decade ago +def find_file(pattern, path): + result = [] + for root, dirs, files in os.walk(path): + for name in files: + if fnmatch.fnmatch(name, pattern): + result.append(os.path.join(root, name)) + return result + class PokeMastImport(bpy.types.Operator, ImportHelper): bl_idname = "import_scene.pokemonmasters" bl_label = "Import" @@ -43,12 +54,12 @@ class PokeMastImport(bpy.types.Operator, ImportHelper): def draw(self, context): layout = self.layout def execute(self, context): + print("=====\nLoading file {}".format(self.filepath)) + CurFile = open(self.filepath,"rb") - CurFile.seek(0x34) BoneDataOffset = CurFile.tell() + int.from_bytes(CurFile.read(4),byteorder='little') ArmatureObject = BuildSkeleton(CurFile,BoneDataOffset) - CurFile.seek(0x38) MaterialDataOffset = CurFile.tell() + int.from_bytes(CurFile.read(4),byteorder='little') ParseMaterials(CurFile,MaterialDataOffset) @@ -59,6 +70,7 @@ def execute(self, context): for x in range(MeshCount): MeshList.append(CurFile.tell() + int.from_bytes(CurFile.read(4),byteorder='little')) CurMeshOffset = CurFile.tell() + print("Loading meshes:") for x in MeshList: ReadMeshChunk(CurFile,x,ArmatureObject) @@ -79,14 +91,14 @@ def ReadMeshChunk(CurFile,StartAddr,ArmatureObject): ModelNameArea = CurFile.tell() + int.from_bytes(CurFile.read(4),byteorder='little') CurFile.seek(ModelNameArea) ModelNameLength = int.from_bytes(CurFile.read(4),byteorder='little') - ModelName = CurFile.read(ModelNameLength).decode('utf-8') - + ModelName = CurFile.read(ModelNameLength).decode('utf-8','replace') + #Get Material Name CurFile.seek(StartAddr+0x14) MaterialNameOffset = CurFile.tell() + int.from_bytes(CurFile.read(4),byteorder='little') CurFile.seek(MaterialNameOffset+8) MaterialNameSize = int.from_bytes(CurFile.read(4),byteorder='little') - MaterialNameText = CurFile.read(MaterialNameSize).decode('utf-8') + MaterialNameText = CurFile.read(MaterialNameSize).decode('utf-8','replace') CurFile.seek(StartAddr+0x58) WeightBoneNameTableStart = CurFile.tell() + int.from_bytes(CurFile.read(4),byteorder='little') @@ -177,7 +189,7 @@ def ReadMeshChunk(CurFile,StartAddr,ArmatureObject): WeightBoneNameOffset = CurFile.tell() + int.from_bytes(CurFile.read(4),byteorder='little') CurFile.seek(WeightBoneNameOffset) WeightBoneNameSize = int.from_bytes(CurFile.read(4),byteorder='little') - WeightBoneName = CurFile.read(WeightBoneNameSize).decode('utf-8') + WeightBoneName = CurFile.read(WeightBoneNameSize).decode('utf-8','replace') WeightBoneTable.append(WeightBoneName) #Build Mesh @@ -199,7 +211,7 @@ def ReadMeshChunk(CurFile,StartAddr,ArmatureObject): bm.faces.new((list[f[0]],list[f[1]],list[f[2]])) except: continue - + print('- {}: {} - {}'.format(ModelName,MaterialNameText,len(list))) bm.to_mesh(mesh) uv_layer = bm.loops.layers.uv.verify() @@ -253,9 +265,10 @@ def BuildSkeleton(CurFile,DataStart): BoneOffsetTable = [] for x in range(BoneCount): BoneOffsetTable.append(CurFile.tell()+int.from_bytes(CurFile.read(4),byteorder='little')) - - armature_data = bpy.data.armatures.new("Armature") - armature_obj = bpy.data.objects.new("Armature", armature_data) + + name = os.path.split(CurFile.name)[-1] + armature_data = bpy.data.armatures.new(name) + armature_obj = bpy.data.objects.new(name, armature_data) bpy.context.scene.objects.link(armature_obj) select_all(False) armature_obj.select = True @@ -270,7 +283,7 @@ def BuildSkeleton(CurFile,DataStart): NameOffset = CurFile.tell() + int.from_bytes(CurFile.read(4),byteorder='little') CurFile.seek(NameOffset) BoneNameLength = int.from_bytes(CurFile.read(4),byteorder='little') - BoneName = CurFile.read(BoneNameLength).decode('utf-8') + BoneName = CurFile.read(BoneNameLength).decode('utf-8','replace') CurFile.seek(x+8) BoneMatrix = mathutils.Matrix((struct.unpack('ffff', CurFile.read(4*4)),struct.unpack('ffff', CurFile.read(4*4)),struct.unpack('ffff', CurFile.read(4*4)),struct.unpack('ffff', CurFile.read(4*4)))) CurFile.seek(x+0x38) @@ -279,7 +292,7 @@ def BuildSkeleton(CurFile,DataStart): BoneParentOffset = CurFile.tell() + int.from_bytes(CurFile.read(4),byteorder='little') CurFile.seek(BoneParentOffset) BoneParentNameLength = int.from_bytes(CurFile.read(4),byteorder='little') - BoneParentName = CurFile.read(BoneParentNameLength).decode('utf-8') + BoneParentName = CurFile.read(BoneParentNameLength).decode('utf-8','replace') edit_bone = armature_obj.data.edit_bones.new(BoneName) edit_bone.use_connect = False @@ -316,8 +329,56 @@ def ParseMaterials(CurFile,DataStart): MatTable = [] CurFile.seek(DataStart+4) MaterialNameOffset = CurFile.tell() + int.from_bytes(CurFile.read(4),byteorder='little') + CurFile.seek(DataStart+12) + TextureCount = int.from_bytes(CurFile.read(4),byteorder='little') + TextureOffSetTable = [] + for x in range(TextureCount): + TextureOffSetTable.append(CurFile.tell() + int.from_bytes(CurFile.read(4), byteorder='little')) + Textures = {} + print("Loading {} texture{}:".format(TextureCount,'' if TextureCount == 1 else 's')) + for x in TextureOffSetTable: + CurFile.seek(x+4) + TexFileRefOffset = CurFile.tell() + int.from_bytes(CurFile.read(4),byteorder='little') + TexFileNameOffset = CurFile.tell() + int.from_bytes(CurFile.read(4),byteorder='little') + TexFileMapOffset = CurFile.tell() + int.from_bytes(CurFile.read(4),byteorder='little') + CurFile.seek(TexFileNameOffset) + TexFileNameSize = int.from_bytes(CurFile.read(4),byteorder='little') + TexFileName = CurFile.read(TexFileNameSize).decode('utf-8','replace') + + CurFile.seek(TexFileRefOffset) + TexFileRefSize = int.from_bytes(CurFile.read(4),byteorder='little') + TexFileRef = CurFile.read(TexFileRefSize).decode('utf-8','replace') + + CurFile.seek(TexFileMapOffset) + TexFileMapSize = int.from_bytes(CurFile.read(4),byteorder='little') + TexFileMap = CurFile.read(TexFileMapSize).decode('utf-8','replace') + Textures.update({TexFileRef:TexFileName}) + tex = bpy.data.textures.get(TexFileName) + if not tex: + tex = bpy.data.textures.new(name=TexFileName,type='IMAGE') + try: + filepath = os.path.split(os.path.realpath(CurFile.name)) + files = find_file(TexFileName, filepath[0]) + # Try finding the file by brute force + if not files: + files = find_file(TexFileName.replace(".tga",".ktx.tga"), filepath[0]) + if not files: + files = find_file(TexFileName.replace(".tga",".png"), filepath[0]) + if not files: + files = find_file(TexFileName.replace(".tga", ".ktx.png"), filepath[0]) + if not files: + files = find_file(TexFileName.replace(".tga", "*.png"), filepath[0]) + if files: + bpy.ops.image.open(filepath=files[0]) + filename = os.path.split(files[0])[-1] + tex.image = bpy.data.images.get(filename) + except: + print(traceback.format_exc()) + print("- {}: {} / {}".format(TexFileRef,TexFileName,TexFileMap)) + CurFile.seek(MaterialNameOffset) MaterialCount = int.from_bytes(CurFile.read(4),byteorder='little') + print("Loading {} material{}:".format(MaterialCount,'' if MaterialCount == 1 else 's')) MatOffsetTable = [] for x in range(MaterialCount): MatOffsetTable.append(CurFile.tell() + int.from_bytes(CurFile.read(4),byteorder='little')) @@ -326,13 +387,39 @@ def ParseMaterials(CurFile,DataStart): MaterialNameTextOffset = CurFile.tell() + int.from_bytes(CurFile.read(4),byteorder='little') CurFile.seek(MaterialNameTextOffset) MaterialNameTextSize = int.from_bytes(CurFile.read(4),byteorder='little') - MaterialNameText = CurFile.read(MaterialNameTextSize).decode('utf-8') - CurFile.seek(x+0x48) - MaterialFileReferenceSize = int.from_bytes(CurFile.read(4),byteorder='little') - MaterialFileReferenceName = CurFile.read(MaterialNameTextSize).decode('utf-8') + MaterialNameText = CurFile.read(MaterialNameTextSize).decode('utf-8','replace') + + CurFile.seek(x + 0x38) + flag = int.from_bytes(CurFile.read(4),byteorder='little') + if flag == 0x40000000: + CurFile.seek(x + 0x44) + else: + CurFile.seek(x + 0x40) + TexSlotCount = int.from_bytes(CurFile.read(4),byteorder='little') + TexSlotsOffset = [] + TexSlots = [] + print("- {}".format(MaterialNameText)) + + for y in range(TexSlotCount): + TexSlotsOffset.append(CurFile.tell() + int.from_bytes(CurFile.read(4),byteorder='little')) + for texslot in TexSlotsOffset: + CurFile.seek(texslot) + MaterialFileReferenceSize = int.from_bytes(CurFile.read(4),byteorder='little') + MaterialFileReferenceName = CurFile.read(MaterialFileReferenceSize).decode('utf-8','replace') + print('- Texture slot [{}]'.format(MaterialFileReferenceName)) + TexSlots.append(Textures.get(MaterialFileReferenceName)) + mat = bpy.data.materials.get(MaterialNameText) if mat == None: mat = bpy.data.materials.new(name=MaterialNameText) + mat.use_transparency=True + mat.alpha = 0.0 + for texture in TexSlots: + tex = bpy.data.textures.get(texture) + if tex: + slot = mat.texture_slots.add() + slot.use_map_alpha = True + slot.texture = tex MatTable.append(mat) return MatTable From 2c15bd7f26eb41c55f9cd010fa146fcddec97ba4 Mon Sep 17 00:00:00 2001 From: jugolm Date: Sun, 29 Sep 2019 00:48:10 -0300 Subject: [PATCH 2/4] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index e47a1f9..66c5809 100644 --- a/README.md +++ b/README.md @@ -2,3 +2,5 @@ Blender 2.79 import script for LMD files from Pokemon Masters Just place it in your Blender install's AddOn folder and enable it in the preferences. + +This fork adds some transparency sets and tries to detect and import textures. From add41a374bbe5cd8f9e1d62f8a8fde7f8c9ec9a7 Mon Sep 17 00:00:00 2001 From: jugol Date: Sun, 29 Sep 2019 17:46:15 -0300 Subject: [PATCH 3/4] For models added after 1.2 update (i.e. just Elesa) --- io_import_pokemon_masters.py | 74 ++++++++++++++++++++++++------------ 1 file changed, 49 insertions(+), 25 deletions(-) diff --git a/io_import_pokemon_masters.py b/io_import_pokemon_masters.py index 02de051..0ef7b04 100644 --- a/io_import_pokemon_masters.py +++ b/io_import_pokemon_masters.py @@ -1,7 +1,7 @@ bl_info = { "name": "Import Pokemon Masters Models", "author": "Turk", - "version": (1, 0, 2), + "version": (1, 1, 0), "blender": (2, 79, 0), "location": "File > Import-Export", "description": "A tool designed to import LMD files from the mobile game Pokemon Masters", @@ -50,9 +50,17 @@ class PokeMastImport(bpy.types.Operator, ImportHelper): ) filepath = StringProperty(subtype='FILE_PATH',) + version = EnumProperty(name="Version",items=(("1.0","1.0","1.0"),("1.2","1.2","1.2")),default="1.0") + removedoubles = BoolProperty(name="Remove Doubles") + files = CollectionProperty(type=bpy.types.PropertyGroup) def draw(self, context): layout = self.layout + layout.separator() + layout.prop(self,'version') + #layout.separator() + #layout.prop(self,'removedoubles') + def execute(self, context): print("=====\nLoading file {}".format(self.filepath)) @@ -72,7 +80,7 @@ def execute(self, context): CurMeshOffset = CurFile.tell() print("Loading meshes:") for x in MeshList: - ReadMeshChunk(CurFile,x,ArmatureObject) + ReadMeshChunk(CurFile,x,ArmatureObject,self.version,self.removedoubles) CurFile.close() ArmatureObject.rotation_euler = (1.5707963705062866,0,0) @@ -83,7 +91,7 @@ def invoke(self, context, event): return {'RUNNING_MODAL'} -def ReadMeshChunk(CurFile,StartAddr,ArmatureObject): +def ReadMeshChunk(CurFile,StartAddr,ArmatureObject,Version="1.0",RemoveDoubles=False): CurFile.seek(StartAddr+7) VertChunkSize = int.from_bytes(CurFile.read(1),byteorder='little') @@ -121,7 +129,6 @@ def ReadMeshChunk(CurFile,StartAddr,ArmatureObject): VertSize = int.from_bytes(CurFile.read(Size),byteorder='little') VertOffset = CurFile.tell() - #Read Vert Info Here VertTable = [] UVTable = [] @@ -132,27 +139,31 @@ def ReadMeshChunk(CurFile,StartAddr,ArmatureObject): for x in range(VertCount): TempVert = struct.unpack('fff', CurFile.read(4*3)) CurFile.seek(4,1) - if VertChunkSize == 0x24 or VertChunkSize == 0x28: - bHasColor = True - TempColor = struct.unpack('4B', CurFile.read(4)) - ColorData.append((TempColor[0]/255,TempColor[1]/255,TempColor[2]/255)) - AlphaData.append((TempColor[3]/255,TempColor[3]/255,TempColor[3]/255)) - elif VertChunkSize == 0x30: + if (VertChunkSize >= 0x24) & (Version == "1.0"): bHasColor = True TempColor = struct.unpack('4B', CurFile.read(4)) ColorData.append((TempColor[0]/255,TempColor[1]/255,TempColor[2]/255)) AlphaData.append((TempColor[3]/255,TempColor[3]/255,TempColor[3]/255)) - CurFile.seek(0xc,1) TempUV = (np.fromstring(CurFile.read(2), dtype='= 0x24) & (Version == "1.0"): + CurFile.seek(VertChunkSize - 0x24, 1) + VGBone = ( + int.from_bytes(CurFile.read(1),byteorder='little'), + int.from_bytes(CurFile.read(1),byteorder='little'), + int.from_bytes(CurFile.read(1),byteorder='little'), + int.from_bytes(CurFile.read(1),byteorder='little')) + if Version == "1.0": + VGWeight = ( + int.from_bytes(CurFile.read(2),byteorder='little')/65535, + int.from_bytes(CurFile.read(2),byteorder='little')/65535, + int.from_bytes(CurFile.read(2),byteorder='little')/65535, + int.from_bytes(CurFile.read(2),byteorder='little')/65535) + else: + VGWeight = struct.unpack('ffff', CurFile.read(4*4)) VGData.append((x,VGBone,VGWeight)) VertTable.append(TempVert) UVTable.append(TempUV) - if Size == 1: UnknownSize = 2 else: UnknownSize = 4 CurFile.seek(VertOffset+VertSize+Size+UnknownSize) @@ -184,6 +195,7 @@ def ReadMeshChunk(CurFile,StartAddr,ArmatureObject): WeightBoneTable = [] CurFile.seek(WeightBoneNameTableStart) WeightBoneCount = int.from_bytes(CurFile.read(4),byteorder='little') + for x in range(WeightBoneCount): CurFile.seek(WeightBoneNameTableStart + x*4 + 4) WeightBoneNameOffset = CurFile.tell() + int.from_bytes(CurFile.read(4),byteorder='little') @@ -191,6 +203,7 @@ def ReadMeshChunk(CurFile,StartAddr,ArmatureObject): WeightBoneNameSize = int.from_bytes(CurFile.read(4),byteorder='little') WeightBoneName = CurFile.read(WeightBoneNameSize).decode('utf-8','replace') WeightBoneTable.append(WeightBoneName) + print('{}: {}'.format(WeightBoneName,WeightBoneNameOffset)) #Build Mesh @@ -205,13 +218,14 @@ def ReadMeshChunk(CurFile,StartAddr,ArmatureObject): bm = bmesh.new() for v in VertTable: bm.verts.new((v[0],v[1],v[2])) - list = [v for v in bm.verts] + vlist = [v for v in bm.verts] for f in FaceTable: try: - bm.faces.new((list[f[0]],list[f[1]],list[f[2]])) + bm.faces.new((vlist[f[0]],vlist[f[1]],vlist[f[2]])) except: continue - print('- {}: {} - {}'.format(ModelName,MaterialNameText,len(list))) + print('- {}: {} - {}'.format(ModelName,MaterialNameText,len(vlist))) + bm.to_mesh(mesh) uv_layer = bm.loops.layers.uv.verify() @@ -225,7 +239,13 @@ def ReadMeshChunk(CurFile,StartAddr,ArmatureObject): except: continue bm.to_mesh(mesh) - + mesh.auto_smooth_angle = 1.2 + """if RemoveDoubles: + bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.00001) + bm.to_mesh(mesh) + mesh.validate(verbose=True) + mesh.update()""" + if bHasColor: color_layer = bm.loops.layers.color.new("Color") color_layerA = bm.loops.layers.color.new("Color_ALPHA") @@ -241,11 +261,15 @@ def ReadMeshChunk(CurFile,StartAddr,ArmatureObject): for x in VGData: for i in range(4): if x[2][i] != 0: - if obj.vertex_groups.find(WeightBoneTable[x[1][i]]) == -1: - TempVG = obj.vertex_groups.new(WeightBoneTable[x[1][i]]) - else: - TempVG = obj.vertex_groups[obj.vertex_groups.find(WeightBoneTable[x[1][i]])] - TempVG.add([x[0]],x[2][i]/65535,'ADD') + try: + if obj.vertex_groups.find(WeightBoneTable[x[1][i]]) == -1: + TempVG = obj.vertex_groups.new(WeightBoneTable[x[1][i]]) + else: + TempVG = obj.vertex_groups[obj.vertex_groups.find(WeightBoneTable[x[1][i]])] + TempVG.add([x[0]],x[2][i],'ADD') + except Exception as e: + print (" WEIGHT FAIL") + #add materials if obj.data.materials: obj.data.materials[0]=bpy.data.materials.get(MaterialNameText) From 42ba1d6adc88cc2d967ca7912bda4e49cd730af0 Mon Sep 17 00:00:00 2001 From: jugol Date: Wed, 6 Nov 2019 20:41:12 -0300 Subject: [PATCH 4/4] Credit where it's due --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 66c5809..cff5899 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,9 @@ # PokemonMasters -Blender 2.79 import script for LMD files from Pokemon Masters +Blender 2.79 import script for LMD files from Pokemon Masters - Based on Turk645's import script. Just place it in your Blender install's AddOn folder and enable it in the preferences. This fork adds some transparency sets and tries to detect and import textures. + +CHANGELONG +V1.1: Adds support to models from v1.2 onward - Unfortunately I was unable to figure out how to autodetect so you have to select it manually. \ No newline at end of file