Merge branch 'n2b'

This commit is contained in:
ArthurHeitmann
2022-06-27 06:02:38 +02:00
13 changed files with 2546 additions and 0 deletions
+159
View File
@@ -0,0 +1,159 @@
# https://github.com/Kerilk/bayonetta_tools/blob/master/binary_templates/Nier%20Automata%20col.bt
from .util import *
class Header:
def __init__(self, colFile):
self.id = colFile.read(4)
self.version = "%08x" % (to_uint(colFile.read(4)))
self.offsetNames = to_uint(colFile.read(4))
self.nameCount = to_uint(colFile.read(4))
self.offsetMeshes = to_uint(colFile.read(4))
self.meshCount = to_uint(colFile.read(4))
self.offsetBoneMap = to_uint(colFile.read(4))
self.boneMapCount = to_uint(colFile.read(4))
self.offsetBoneMap2 = to_uint(colFile.read(4))
self.boneMap2Count = to_uint(colFile.read(4))
self.offsetMeshMap = to_uint(colFile.read(4))
self.meshMapCount = to_uint(colFile.read(4))
self.offsetColTreeNodes = to_uint(colFile.read(4))
self.colTreeNodesCount = to_uint(colFile.read(4))
class NameGroups:
def __init__(self, colFile, header):
self.offsetNames = []
for i in range(header.nameCount):
self.offsetNames.append(to_uint(colFile.read(4)))
self.names = []
for offsetName in self.offsetNames:
colFile.seek(offsetName)
self.names.append(to_string(colFile.read(256)))
class Batch:
def __init__(self, colFile, batchType):
if batchType == 2:
self.boneIndex = to_int(colFile.read(4))
self.offsetVertices = to_uint(colFile.read(4))
self.vertexCount = to_uint(colFile.read(4))
self.offsetIndices = to_uint(colFile.read(4))
self.indexCount = to_uint(colFile.read(4))
returnPos = colFile.tell()
colFile.seek(self.offsetVertices)
self.vertices = []
self.vec4Vertices = []
for i in range(self.vertexCount):
x = to_float(colFile.read(4))
y = to_float(colFile.read(4))
z = to_float(colFile.read(4))
w = to_float(colFile.read(4))
self.vertices.append([x, y, z])
self.vec4Vertices.append([x, y, z, w])
colFile.seek(self.offsetIndices)
self.indices = []
self.rawIndices = []
for i in range(round(self.indexCount / 3)):
v0 = to_ushort(colFile.read(2))
v1 = to_ushort(colFile.read(2))
v2 = to_ushort(colFile.read(2))
self.rawIndices.append(v0)
self.rawIndices.append(v1)
self.rawIndices.append(v2)
self.indices.append([v2, v1, v0])
colFile.seek(returnPos)
elif batchType == 3:
self.offsetVertices = to_uint(colFile.read(4))
self.vertexCount = to_uint(colFile.read(4))
self.offsetIndices = to_uint(colFile.read(4))
self.indexCount = to_uint(colFile.read(4))
returnPos = colFile.tell()
else:
print("UNKNOWN BATCH TYPE!")
class Mesh:
def __init__(self, colFile):
self.collisionType = to_uint(colFile.read(1))
self.slidable = to_uint(colFile.read(1))
self.unknownByte = to_uint(colFile.read(1))
self.surfaceType = to_uint(colFile.read(1))
self.nameIndex = to_uint(colFile.read(4))
self.batchType = to_uint(colFile.read(4))
self.offsetBatches = to_uint(colFile.read(4))
self.batchCount = to_uint(colFile.read(4))
returnPos = colFile.tell()
colFile.seek(self.offsetBatches)
self.batches = []
for i in range(self.batchCount):
self.batches.append(Batch(colFile, self.batchType))
colFile.seek(returnPos)
class ColTreeNode:
def __init__(self, colFile):
self.p1 = [to_float(colFile.read(4)), to_float(colFile.read(4)), to_float(colFile.read(4))]
self.p2 = [to_float(colFile.read(4)), to_float(colFile.read(4)), to_float(colFile.read(4))]
self.left = to_int(colFile.read(4))
self.right = to_int(colFile.read(4))
self.offsetMeshIndices = to_uint(colFile.read(4))
self.meshIndexCount = to_uint(colFile.read(4))
self.meshIndices = []
if self.offsetMeshIndices != 0 and self.meshIndexCount != 0:
returnPos = colFile.tell()
colFile.seek(self.offsetMeshIndices)
for i in range(self.meshIndexCount):
self.meshIndices.append(to_uint(colFile.read(4)))
colFile.seek(returnPos)
class Col:
def __init__(self, colFile):
self.header = Header(colFile)
colFile.seek(self.header.offsetNames)
self.nameGroups = NameGroups(colFile, self.header)
colFile.seek(self.header.offsetMeshes)
self.meshes = []
for i in range(self.header.meshCount):
self.meshes.append(Mesh(colFile))
self.meshMaps = []
if self.header.meshMapCount > 0:
colFile.seek(self.header.offsetMeshMap)
for i in range(self.header.meshMapCount):
self.meshMaps.append(to_uint(colFile.read(4)))
self.boneMaps = []
if self.header.boneMapCount > 0:
colFile.seek(self.header.offsetBoneMap)
for i in range(self.header.boneMapCount):
self.boneMaps.append(to_uint(colFile.read(4)))
self.boneMaps2 = []
if self.header.boneMap2Count > 0:
colFile.seek(self.header.offsetBoneMap2)
for i in range(self.header.boneMap2Count):
self.boneMaps2.append(to_uint(colFile.read(4)))
self.colTreeNodes = []
if self.header.colTreeNodesCount > 0:
colFile.seek(self.header.offsetColTreeNodes)
for i in range(self.header.colTreeNodesCount):
self.colTreeNodes.append(ColTreeNode(colFile))
+140
View File
@@ -0,0 +1,140 @@
import bpy, math
from .col import Col
collisionTypes = [
("-1", "UNKNOWN", ""),
("3", "Block Actors", "If modifier is enabled, this will not block players who are jumping (e.g. to prevent accidentally walking off ledges)."),
("88", "Water", ""),
("127", "Grabbable Block All", ""),
("255", "Block All", "")
]
# Identified by NSA Cloud
surfaceTypes = [
("-1", "UNKNOWN", ""),
("0", "Concrete1", ""),
("1", "Dirt", ""),
("2", "Concrete2", ""),
("3", "Metal Floor", ""),
("4", "Rubble", ""),
("5", "Metal Grate", ""),
("6", "Gravel", ""),
("7", "Rope Bridge", ""),
("8", "Grass", ""),
("9", "Wood Plank", ""),
("11", "Water", ""),
("12", "Sand", ""),
("13", "Rocky Gravel 1", ""),
("15", "Mud", ""),
("16", "Rocky Gravel 2", ""),
("17", "Concrete 3", ""),
("18", "Bunker Floor", ""),
("22", "Concrete 4", ""),
("23", "Car", ""),
("24", "Flowers", "")
]
def setColourByCollisionType(obj):
opacity = 1.0
collisionType = int(obj.collisionType)
if collisionType == 127:
obj.color = [0.0, 1.0, 0.0, opacity]
elif collisionType == 88:
obj.color = [0.0, 0.5, 1.0, opacity]
elif collisionType == 3:
obj.color = [1.0, 0.5, 0.0, opacity]
elif collisionType == 255:
obj.color = [1.0, 0.0, 0.0, opacity]
else:
obj.color = [1.0, 0.45, 1.0, opacity]
def updateCollisionType(self, context):
setColourByCollisionType(self)
def main(colFilePath):
bpy.types.Object.collisionType = bpy.props.EnumProperty(name="Collision Type", items=collisionTypes, update=updateCollisionType)
bpy.types.Object.slidable = bpy.props.BoolProperty(name="Slidable/Modifier")
bpy.types.Object.surfaceType = bpy.props.EnumProperty(name="Surface Type", items=surfaceTypes)
# Setup Viewport
for window in bpy.context.window_manager.windows:
for area in window.screen.areas:
if area.type == 'VIEW_3D':
for space in area.spaces:
if space.type == 'VIEW_3D':
space.shading.type = "SOLID"
space.shading.color_type = "OBJECT"
space.shading.show_backface_culling = True
with open(colFilePath, "rb") as colFile:
print("Parsing Col file...", colFilePath)
col = Col(colFile)
# Create COL Collection
colCollection = bpy.data.collections.get("COL")
if not colCollection:
colCollection = bpy.data.collections.new("COL")
bpy.context.scene.collection.children.link(colCollection)
# Create meshes
for meshIdx, mesh in enumerate(col.meshes):
meshName = col.nameGroups.names[mesh.nameIndex]
# Create batches
for batchIdx, batch in enumerate(mesh.batches):
objName = str(meshIdx) + "-" + meshName + "-" + str(batchIdx)
objMesh = bpy.data.meshes.new(objName)
obj = bpy.data.objects.new(objName, objMesh)
colCollection.objects.link(obj)
#obj.display_type = 'WIRE'
obj.show_wire = True
objMesh.from_pydata(batch.vertices, [], batch.indices)
objMesh.update(calc_edges=True)
try:
obj.collisionType = str(mesh.collisionType)
except:
print("[!] Collision mesh flagged with unknown collsionType:", mesh.collisionType)
obj.collisionType = "-1"
obj["UNKNOWN_collisionType"] = mesh.collisionType
obj.slidable = bool(mesh.slidable)
obj["unknownByte"] = mesh.unknownByte
try:
obj.surfaceType = str(mesh.surfaceType)
except:
print("[!] Collision mesh flagged with unknown surfaceType:", mesh.surfaceType)
obj.surfaceType = "-1"
obj["UNKNOWN_surfaceType"] = mesh.surfaceType
obj.rotation_euler = (math.radians(90),0,0)
# Create colTreeNodes Sub-Collection
colTreeNodesCollection = bpy.data.collections.get("col_colTreeNodes")
if not colTreeNodesCollection:
colTreeNodesCollection = bpy.data.collections.new("col_colTreeNodes")
colCollection.children.link(colTreeNodesCollection)
bpy.context.view_layer.active_layer_collection.children["COL"].children["col_colTreeNodes"].hide_viewport = True
# Create colTreeNodes
rootNode = bpy.data.objects.new("Root_col", None)
rootNode.hide_viewport = True
colTreeNodesCollection.objects.link(rootNode)
rootNode.rotation_euler = (math.radians(90),0,0)
for nodeIdx, node in enumerate(col.colTreeNodes):
objName = str(nodeIdx) + "_" + str(node.left) + "_" + str(node.right) + "_col"
obj = bpy.data.objects.new(objName, None)
colTreeNodesCollection.objects.link(obj)
obj.parent = rootNode
obj.empty_display_type = 'CUBE'
obj.location = node.p1
obj.scale = node.p2
if len(node.meshIndices) > 0:
obj["meshIndices"] = node.meshIndices
print('Importing finished. ;)')
return {'FINISHED'}
+211
View File
@@ -0,0 +1,211 @@
#encoding = utf-8
import os
import sys
import struct
from .util import to_uint
def little_endian_to_float(bs):
return struct.unpack("<f", bs)[0]
def little_endian_to_int(bs):
return int.from_bytes(bs, byteorder='little')
def create_dir(dirpath):
if not os.path.exists(dirpath):
os.makedirs(dirpath)
def read_header(fp):
Magic = fp.read(4)
if list(Magic) == [68, 65, 84, 0]:
FileCount = little_endian_to_int(fp.read(4))
FileTableOffset = little_endian_to_int(fp.read(4))
ExtensionTableOffset = little_endian_to_int(fp.read(4))
NameTableOffset = little_endian_to_int(fp.read(4))
SizeTableOffset = little_endian_to_int(fp.read(4))
hashMapOffset = little_endian_to_int(fp.read(4))
print(
'''FileCount: %08x
FileTableOffset: %08x
ExtensionTableOffset:%08x
NameTableOffset:%08x
SizeTableOffset:%08x
hashMapOffset:%08x
'''%
(FileCount, FileTableOffset, ExtensionTableOffset,NameTableOffset,SizeTableOffset,hashMapOffset)
)
return (FileCount, FileTableOffset, ExtensionTableOffset,NameTableOffset,SizeTableOffset,hashMapOffset)
else:
print('[-] error magic number detected')
return False
def get_fileinfo(fp, index, FileTableOffset, ExtensionTableOffset, NameTableOffset, SizeTableOffset):
fp.seek(FileTableOffset + index * 4)
FileOffset = little_endian_to_int(fp.read(4))
fp.seek(ExtensionTableOffset + index * 4)
Extension = fp.read(4).decode('utf-8')
fp.seek(SizeTableOffset + index * 4)
Size = little_endian_to_int(fp.read(4))
fp.seek(NameTableOffset)
FilenameAlignment = little_endian_to_int(fp.read(4))
i = 0
while i < index:
if list(fp.read(FilenameAlignment))[FilenameAlignment-1] == 0:
i += 1
Filename = fp.read(256).split(b'\x00')[0].decode('ascii')
print(
'''
FileIndex: %d
Filename: %s
FileOffset: %08x
Size: %08x
Extension: %s'''%
(index,Filename,FileOffset,Size,Extension)
)
return index,Filename,FileOffset,Size,Extension
def extract_file(fp, filename, FileOffset, Size, extract_dir):
create_dir(extract_dir)
fp.seek(FileOffset)
FileContent = fp.read(Size)
with open(extract_dir + '/'+filename,'wb') as outfile:
print("extracting file %s to %s/%s"%(filename,extract_dir,filename))
outfile.write(FileContent)
if filename.find('wtp') > -1 and False: # Removed due to not needed anymore when using Blender DTT import.
wtp_fp = open(extract_dir + '/'+filename,"rb")
content = wtp_fp.read(Size)
dds_group = content.split(b'DDS ')
dds_group = dds_group[1:]
for i in range(len(dds_group)):
print("unpacking %s to %s/%s"%(filename,extract_dir ,filename.replace('.wtp','_%d.dds'%i)))
dds_fp = open(extract_dir + '/'+filename.replace('.wtp','_%d.dds'%i), "wb")
dds_fp.write(b'DDS ')
dds_fp.write(dds_group[i])
dds_fp.close()
wtp_fp.close()
#os.remove("%s/%s"%(extract_dir,filename))
print("done")
def get_all_files(path):
pass
def extract_hashes(fp, extract_dir, FileCount, hashMapOffset, fileNamesOffset):
create_dir(extract_dir)
# file_order.metadata
# Filename Size
fp.seek(fileNamesOffset)
fileNameSize = little_endian_to_int(fp.read(4))
# Filenames
fileNames = []
for i in range(FileCount):
fileNames.append(fp.read(fileNameSize))
# Extraction
filename = 'file_order.metadata'
extract_dir_sub = extract_dir + '\\' + filename
with open(extract_dir_sub,'wb') as outfile:
# Header
outfile.write(struct.pack('<i', FileCount))
outfile.write(struct.pack('<i', fileNameSize))
#Filenames
for fileName in fileNames:
outfile.write(fileName)
# hash_data.metadata
# Header
fp.seek(hashMapOffset)
preHashShift = to_uint(fp.read(4))
bucketOffsetsOffset = to_uint(fp.read(4))
hashesOffset = to_uint(fp.read(4))
fileIndicesOffset = to_uint(fp.read(4))
# Bucket Offsets
fp.seek(hashMapOffset + bucketOffsetsOffset)
bucketOffsets = []
while fp.tell() < (hashMapOffset + hashesOffset):
bucketOffsets.append(to_uint(fp.read(2)))
# Hashes
fp.seek(hashMapOffset + hashesOffset)
hashes = []
for i in range(FileCount):
hashes.append(fp.read(4))
# File Indices
fp.seek(hashMapOffset + fileIndicesOffset)
fileIndices = []
for i in range(FileCount):
fileIndices.append(to_uint(fp.read(2)))
# Extraction
filename = 'hash_data.metadata'
extract_dir_sub = extract_dir + '\\' + filename
with open(extract_dir_sub,'wb') as outfile:
# Header
outfile.write(struct.pack('<i', preHashShift))
outfile.write(struct.pack('<i', bucketOffsetsOffset))
outfile.write(struct.pack('<i', hashesOffset))
outfile.write(struct.pack('<i', fileIndicesOffset))
# Bucket Offsets
for i in bucketOffsets:
#print(bucketOffsets)
outfile.write(struct.pack('<H', i))
# Hashes
for i in hashes:
outfile.write(i)
# File Indices
for i in fileIndices:
#print(i)
outfile.write(struct.pack('<H', i))
def main(filename, extract_dir, ROOT_DIR):
with open(filename,"rb") as fp:
headers = read_header(fp)
if headers:
FileCount, FileTableOffset, ExtensionTableOffset,NameTableOffset,SizeTableOffset,hashMapOffset = headers
for i in range(FileCount):
extract_dir_sub = ''
index,Filename,FileOffset,Size,Extension = get_fileinfo(fp, i, FileTableOffset,ExtensionTableOffset, NameTableOffset,SizeTableOffset)
if extract_dir != '':
extract_dir_sub = extract_dir + '\\' + filename.replace(ROOT_DIR ,'')
extract_file(fp, Filename, FileOffset, Size, extract_dir_sub)
extract_hashes(fp, extract_dir, FileCount, hashMapOffset, NameTableOffset)
return Filename
if __name__ == '__main__':
extract_dir = ''
dirname = ''
useage = "\nUseage:\npython dat_unpacker.py your_dat_path your_extract_path"
useage1 = "\nUseage:\nblender --background --python dat_unpacker.py your_dat_path your_extract_path"
if len(sys.argv) < 3:
print(useage)
exit()
if len(sys.argv) > 2:
dir_name = sys.argv[1]
extract_dir = sys.argv[2]
print()
if os.path.split(sys.argv[0])[-1].lower().find("blender") >-1:
if len(sys.argv) < 6:
print(useage1)
exit()
dir_name = sys.argv[4]
extract_dir = sys.argv[5]
if not os.path.exists(extract_dir):
create_dir(extract_dir)
ROOT_DIR = dir_name
for dirpath,dirnames,filename in os.walk(dir_name):
for file in filename:
filename = "%s\%s"%(dirpath,file)
main(filename, extract_dir, ROOT_DIR)
Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

+69
View File
@@ -0,0 +1,69 @@
from .util import *
# Based on binary template by NSA Cloud
class Header:
def __init__(self, layFile):
self.id = layFile.read(4)
self.unknownVer = to_float(layFile.read(4))
self.modelListOffset = to_uint(layFile.read(4))
self.modelListCount = to_uint(layFile.read(4))
self.assetsOffset = to_uint(layFile.read(4))
self.assetsCount = to_uint(layFile.read(4))
self.instancesOffset = to_uint(layFile.read(4))
self.instancesCount = to_uint(layFile.read(4))
class ModelEntry:
def __init__(self, layFile):
self.dir = layFile.read(2)
self.id = layFile.read(2)
class Asset:
def __init__(self, layFile):
self.name = to_string(layFile.read(32))
self.position = [to_float(layFile.read(4)) for val in range(3)]
self.rotation = [to_float(layFile.read(4)) for val in range(3)]
self.scale = [to_float(layFile.read(4)) for val in range(3)]
self.null0 = to_uint(layFile.read(4))
self.unknownIndex = to_uint(layFile.read(4))
self.null1 = [to_uint(layFile.read(1)) for val in range(32)]
self.instanceCount = to_uint(layFile.read(4))
self.instances = []
class Instance:
def __init__(self, layFile):
self.position = [to_float(layFile.read(4)) for val in range(3)]
self.rotation = [to_float(layFile.read(4)) for val in range(3)]
self.scale = [to_float(layFile.read(4)) for val in range(3)]
class Lay:
def __init__(self, layFile):
self.header = Header(layFile)
layFile.seek(self.header.modelListOffset)
self.modelList = []
for i in range(self.header.modelListCount):
self.modelList.append(ModelEntry(layFile))
layFile.seek(self.header.assetsOffset)
self.assets = []
for i in range(self.header.assetsCount):
self.assets.append(Asset(layFile))
layFile.seek(self.header.instancesOffset)
self.instances = []
for i in range(self.header.instancesCount):
self.instances.append(Instance(layFile))
currentInstanceIndx = 0
for asset in self.assets:
for i in range(asset.instanceCount):
asset.instances.append(self.instances[currentInstanceIndx])
currentInstanceIndx = currentInstanceIndx + 1
+154
View File
@@ -0,0 +1,154 @@
import bpy, math, os
from .lay import Lay
from .util import *
def main(layFilePath, addonName):
with open(layFilePath, "rb") as layFile:
print("Parsing Lay file...", layFilePath)
lay = Lay(layFile)
# Create LAY Collection
layCollection = bpy.data.collections.get("LAY")
if not layCollection:
layCollection = bpy.data.collections.new("LAY")
bpy.context.scene.collection.children.link(layCollection)
# Create layAssets Sub-Collection
layAssetsCollection = bpy.data.collections.get("lay_layAssets")
if not layAssetsCollection:
layAssetsCollection = bpy.data.collections.new("lay_layAssets")
layCollection.children.link(layAssetsCollection)
# Create layInstances Sub-Collection
layInstancesCollection = bpy.data.collections.get("lay_layInstances")
if not layInstancesCollection:
layInstancesCollection = bpy.data.collections.new("lay_layInstances")
layCollection.children.link(layInstancesCollection)
# Create layAssets and layInstances
assetRootNode = bpy.data.objects.new("Root_layAsset", None)
assetRootNode.hide_viewport = True
layAssetsCollection.objects.link(assetRootNode)
assetRootNode.rotation_euler = (math.radians(90),0,0)
instanceRootNode = bpy.data.objects.new("Root_layInstance", None)
instanceRootNode.hide_viewport = True
layInstancesCollection.objects.link(instanceRootNode)
instanceRootNode.rotation_euler = (math.radians(90),0,0)
for asset in lay.assets:
assetName = asset.name
print("Placing asset", assetName)
boundingBox = getModelBoundingBox(assetName.split("_")[0], addonName)
assetObj = createLayObject(assetName, layAssetsCollection, assetRootNode, asset.position, asset.rotation, asset.scale, boundingBox)
assetObj["unknownIndex"] = asset.unknownIndex
assetObj["null1"] = asset.null1
for instance in asset.instances:
instanceName = assetName + "-Instance"
createLayObject(instanceName, layInstancesCollection, instanceRootNode, instance.position, instance.rotation, instance.scale, boundingBox)
print('Importing finished. ;)')
return {'FINISHED'}
def createLayObject(name, collection, parent, pos, rot, scale, boundingBox):
obj = bpy.data.objects.new(name, None)
collection.objects.link(obj)
obj.parent = parent
obj.empty_display_type = 'SPHERE'
obj.empty_display_size = 0.5
obj.location = pos
obj.rotation_euler = rot
obj.scale = scale
obj.show_axis = True
if boundingBox != None:
createBoundingBoxObject(obj, name + "-BoundingBox", collection, boundingBox)
return obj
def createBoundingBoxObject(obj: bpy.types.Object, name, collection, boundingBox):
boundingBoxObj = bpy.data.objects.new(name, None)
collection.objects.link(boundingBoxObj)
for child in obj.children:
bpy.data.objects.remove(child, do_unlink=True)
boundingBoxObj.parent = obj
boundingBoxObj.empty_display_type = 'CUBE'
boundingBoxObj.location = boundingBox[:3]
boundingBoxObj.scale = boundingBox[-3:]
boundingBoxObj.hide_select = True
def getModelBoundingBox(modelName, addonName):
data005_dir = bpy.context.preferences.addons[addonName].preferences.data005_dir
data015_dir = bpy.context.preferences.addons[addonName].preferences.data015_dir
if not os.path.isdir(data005_dir) or not os.path.isdir(data015_dir):
return None
#print("Model To Find", modelName)
fileFound = False
filePath = ""
# Search data005.cpk
for pathName in os.listdir(data005_dir):
if fileFound:
break
fullPathName = os.path.join(data005_dir, pathName)
if os.path.isdir(fullPathName):
for file in os.listdir(fullPathName):
if file == (modelName + ".dtt"):
filePath = fullPathName + "\\" + file
fileFound = True
break
else:
if pathName == (modelName + ".dtt"):
filePath = fullPathName + "\\" + pathName
fileFound = True
break
# Search data015.cpk
for pathName in os.listdir(data015_dir):
if fileFound:
break
fullPathName = os.path.join(data015_dir, pathName)
if os.path.isdir(fullPathName):
for file in os.listdir(fullPathName):
if file == (modelName + ".dtt"):
filePath = fullPathName + "\\" + file
fileFound = True
break
else:
if pathName == (modelName + ".dtt"):
filePath = fullPathName + "\\" + pathName
fileFound = True
break
if not fileFound:
return None
with open(filePath, "rb") as modelDTTFile:
id = modelDTTFile.read(4)
numFiles = to_uint(modelDTTFile.read(4))
fileOffsetsOffset = to_uint(modelDTTFile.read(4))
fileExtensionsOffset = to_uint(modelDTTFile.read(4))
fileOffsets = []
modelDTTFile.seek(fileOffsetsOffset)
for i in range(numFiles):
fileOffsets.append(to_uint(modelDTTFile.read(4)))
fileExtensions = []
modelDTTFile.seek(fileExtensionsOffset)
for i in range(numFiles):
fileExtensions.append(to_string(modelDTTFile.read(4)))
for i, ext in enumerate(fileExtensions):
if ext == "wmb":
modelDTTFile.seek(fileOffsets[i] + 16)
boundingBox = [to_float(modelDTTFile.read(4)) for val in range(6)]
return boundingBox
+45
View File
@@ -0,0 +1,45 @@
![Nier2Blender](https://user-images.githubusercontent.com/54476280/64488614-519a7280-d24a-11e9-8627-784cdc5ac3de.png)
<br>
This fork is being updated by **Woeful_Wolf** in an attempt to keep Nier2Blender alive for Blender 2.8 and future versions. <br>
<br>
Materials/textures have been redone completely due to the new Blender 2.8+ rendering engines updates. <br>
This means it now has **Eevee/Cycles material import support for the Principled Shader! PBR Yay!** <br>
<br>
This is still a bit WIP so reporting any bugs would be appreciated, I could not test with every model in the game but if you find any that are problematic, be sure to include what model it was (helps with debugging). <br>
<br>
**If looking for the exporter, here it is:** <br>
https://github.com/WoefulWolf/Blender2NieR
<br>
(And thanks for helping with testing; Kekoulis)
<br>
<br>
If you use my tool and release something, please give appropriate credit and info. I would really love for my tools to become more
widely known so that others can start modding too. :)
## What have I added exactly?
* Blender 2.8+ Support
* Importing to Eevee/Cycles
* Alpha Channel Support
* MaskMap Support
* LightMap Support
* Multiple Normal Map Support
* Importing straight from .DTT files directly (skips manual extraction)
* (and probably some other small things I've forgotten)
<br>
![2B](https://i.imgur.com/WObGcDP.png)
* Some useful folders in cpk <br>
/pl -> main character models<br>
/wd -> scene models<br>
/wp -> weapon models<br>
/um -> npc models<br>
/em -> enemy models<br>
/et -> item models<br>
+301
View File
@@ -0,0 +1,301 @@
bl_info = {
"name": "NieR2Blender (NieR:Automata Data Importer)",
"author": "Woeful_Wolf (Original by C4nf3ng)",
"version": (3, 0),
"blender": (2, 80, 0),
"api": 38019,
"location": "File > Import",
"description": "Import Nier:Automata Data",
"warning": "",
"wiki_url": "",
"tracker_url": "",
"category": "Import-Export"}
import bpy
import os
from bpy_extras.io_utils import ExportHelper,ImportHelper
from bpy.props import StringProperty, BoolProperty, EnumProperty
class ImportNier2blender(bpy.types.Operator, ImportHelper):
'''Load a Nier:Automata WMB File.'''
bl_idname = "import_scene.wmb_data"
bl_label = "Import WMB Data"
bl_options = {'PRESET'}
filename_ext = ".wmb"
filter_glob: StringProperty(default="*.wmb", options={'HIDDEN'})
reset_blend: bpy.props.BoolProperty(name="Reset Blender Scene on Import", default=True)
def execute(self, context):
from . import wmb_importer
if self.reset_blend:
wmb_importer.reset_blend()
return wmb_importer.main(False, self.filepath)
def importDat(only_extract, filepath):
head = os.path.split(filepath)[0]
tail = os.path.split(filepath)[1]
tailless_tail = tail[:-4]
dat_filepath = head + '\\' + tailless_tail + '.dat'
extract_dir = head + '\\nier2blender_extracted'
from . import dat_unpacker
if os.path.isfile(dat_filepath):
dat_unpacker.main(dat_filepath, extract_dir + '\\' + tailless_tail + '.dat', dat_filepath) # dat
else:
print('DAT not found. Only extracting DTT. (No materials, collisions or layouts will automatically be imported)')
last_filename = dat_unpacker.main(filepath, extract_dir + '\\' + tailless_tail + '.dtt', filepath) # dtt
wmb_filepath = extract_dir + '\\' + tailless_tail + '.dtt\\' + last_filename[:-4] + '.wmb'
if not os.path.exists(wmb_filepath):
wmb_filepath = extract_dir + '\\' + tailless_tail + '.dat\\' + last_filename[:-4] + '.wmb' # if not in dtt, then must be in dat
# WMB
from . import wmb_importer
wmb_importer.main(only_extract, wmb_filepath)
if only_extract:
return {'FINISHED'}
# COL
col_filepath = extract_dir + '\\' + tailless_tail + '.dat\\' + tailless_tail + '.col'
if os.path.isfile(col_filepath):
from . import col_importer
col_importer.main(col_filepath)
# LAY
lay_filepath = extract_dir + '\\' + tailless_tail + '.dat\\' + 'Layout.lay'
if os.path.isfile(lay_filepath):
from . import lay_importer
lay_importer.main(lay_filepath, __name__)
return {'FINISHED'}
class ImportDATNier2blender(bpy.types.Operator, ImportHelper):
'''Load a Nier:Automata DTT (and DAT) File.'''
bl_idname = "import_scene.dtt_data"
bl_label = "Import DTT (and DAT) Data"
bl_options = {'PRESET'}
filename_ext = ".dtt"
filter_glob: StringProperty(default="*.dtt", options={'HIDDEN'})
reset_blend: bpy.props.BoolProperty(name="Reset Blender Scene on Import", default=True)
bulk_import: bpy.props.BoolProperty(name="Bulk Import All DTT/DATs In Folder (Experimental)", default=False)
only_extract: bpy.props.BoolProperty(name="Only Extract DTT/DAT Contents. (Experimental)", default=False)
def execute(self, context):
from . import wmb_importer
if self.reset_blend and not self.only_extract:
wmb_importer.reset_blend()
if self.bulk_import:
folder = os.path.split(self.filepath)[0]
for filename in os.listdir(folder):
if filename[-4:] == '.dtt':
try:
filepath = folder + '\\' + filename
importDat(self.only_extract, filepath)
except:
print('ERROR: FAILED TO IMPORT', filename)
return {'FINISHED'}
else:
return importDat(self.only_extract, self.filepath)
class ImportActualDATNier2blender(bpy.types.Operator, ImportHelper):
'''Load a Nier:Automata DAT File.'''
bl_idname = "import_scene.dat_data"
bl_label = "Import DAT Data"
bl_options = {'PRESET'}
filename_ext = ".dat"
filter_glob: StringProperty(default="*.dat", options={'HIDDEN'})
reset_blend: bpy.props.BoolProperty(name="Reset Blender Scene on Import", default=True)
bulk_import: bpy.props.BoolProperty(name="Bulk Import All DTT/DATs In Folder (Experimental)", default=False)
only_extract: bpy.props.BoolProperty(name="Only Extract DTT/DAT Contents. (Experimental)", default=False)
def doImport(self, onlyExtract, filepath):
head = os.path.split(filepath)[0]
tail = os.path.split(filepath)[1]
tailless_tail = tail[:-4]
dat_filepath = head + '\\' + tailless_tail + '.dat'
extract_dir = head + '\\nier2blender_extracted'
from . import dat_unpacker
if os.path.isfile(dat_filepath):
dat_unpacker.main(dat_filepath, extract_dir + '\\' + tailless_tail + '.dat', dat_filepath) # dat
if onlyExtract:
return {'FINISHED'}
# COL
col_filepath = extract_dir + '\\' + tailless_tail + '.dat\\' + tailless_tail + '.col'
if os.path.isfile(col_filepath):
from . import col_importer
col_importer.main(col_filepath)
# LAY
lay_filepath = extract_dir + '\\' + tailless_tail + '.dat\\' + 'Layout.lay'
if os.path.isfile(lay_filepath):
from . import lay_importer
lay_importer.main(lay_filepath, __name__)
return {'FINISHED'}
def execute(self, context):
from . import wmb_importer
if self.reset_blend and not self.only_extract:
wmb_importer.reset_blend()
if self.bulk_import:
folder = os.path.split(self.filepath)[0]
for filename in os.listdir(folder):
if filename[-4:] == '.dat':
try:
filepath = folder + '\\' + filename
return self.doImport(self.only_extract, filepath)
except:
print('ERROR: FAILED TO IMPORT', filename)
return {'FINISHED'}
else:
return self.doImport(self.only_extract, self.filepath)
class ImportColNier2Blender(bpy.types.Operator, ImportHelper):
'''Load a Nier:Automata Col (Collision) File.'''
bl_idname = "import_scene.col_data"
bl_label = "Import Col Data"
bl_options = {'PRESET'}
filename_ext = ".col"
filter_glob: StringProperty(default="*.col", options={'HIDDEN'})
def execute(self, context):
from . import col_importer
return col_importer.main(self.filepath)
class ImportLayNier2Blender(bpy.types.Operator, ImportHelper):
'''Load a Nier:Automata Lay (Layout) File.'''
bl_idname = "import_scene.lay_data"
bl_label = "Import Lay Data"
bl_options = {'PRESET'}
filename_ext = ".lay"
filter_glob: StringProperty(default="*.lay", options={'HIDDEN'})
def execute(self, context):
from . import lay_importer
return lay_importer.main(self.filepath, __name__)
class SelectDirectory(bpy.types.Operator, ImportHelper):
'''Select Directory'''
bl_idname = "n2b.folder_select"
bl_label = "Select Directory"
filename_ext = ""
dirpath : StringProperty(name = "", description="Choose directory:", subtype='DIR_PATH')
target : bpy.props.StringProperty(options={'HIDDEN'})
def execute(self, context):
directory = os.path.dirname(self.filepath)
if self.target == "data005":
context.preferences.addons[__name__].preferences.data005_dir = directory
elif self.target == "data015":
context.preferences.addons[__name__].preferences.data015_dir = directory
else:
print("Invalid target", self.target)
return {"CANCELLED"}
return {'FINISHED'}
class NieR2BlenderPreferences(bpy.types.AddonPreferences):
bl_idname = __package__
data005_dir : StringProperty(options={'HIDDEN'})
data015_dir : StringProperty(options={'HIDDEN'})
def draw(self, context):
layout = self.layout
layout.label(text="Assign Directories Below If You Wish To Enable Bounding Box Visualization With Layout Import:")
box = layout.box()
box.label(text="Path To Extracted data005.cpk Directory:")
row = box.row(align=True)
row.prop(self, "data005_dir", text="")
row.operator("n2b.folder_select", icon="FILE_FOLDER", text="").target = "data005"
box.label(text="Path To Extracted data015.cpk Directory:")
row = box.row(align=True)
row.prop(self, "data015_dir", text="")
row.operator("n2b.folder_select", icon="FILE_FOLDER", text="").target = "data015"
class NieR2BlenderCreateObjBBox(bpy.types.Operator):
"""Create Layout Object Bounding Box"""
bl_idname = "n2b.create_lay_bb"
bl_label = "Create Layout Object Bounding Box"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
from .lay_importer import getModelBoundingBox, createBoundingBoxObject
for obj in bpy.context.selected_objects:
boundingBox = getModelBoundingBox(obj.name.split("_")[0], __name__)
if boundingBox:
createBoundingBoxObject(obj, obj.name + "-BoundingBox", bpy.data.collections.get("lay_layAssets"), boundingBox)
else:
self.report({'WARNING'}, "Couldn't find dtt of " + obj.name)
return {'FINISHED'}
class N2BLayoutObjectMenu(bpy.types.Menu):
bl_idname = 'OBJECT_MT_n2blayout'
bl_label = 'NieR2Blender'
def draw(self, context):
self.layout.operator(NieR2BlenderCreateObjBBox.bl_idname, icon="CUBE")
def menu_func_utils(self, context):
pcoll = preview_collections["main"]
yorha_icon = pcoll["yorha"]
self.layout.menu(N2BLayoutObjectMenu.bl_idname, icon_value=yorha_icon.icon_id)
def menu_func_import(self, context):
pcoll = preview_collections["main"]
yorha_icon = pcoll["yorha"]
self.layout.operator(ImportDATNier2blender.bl_idname, text="DTT File for Nier:Automata (.dtt)", icon_value=yorha_icon.icon_id)
self.layout.operator(ImportNier2blender.bl_idname, text="WMB File for Nier:Automata (.wmb)", icon_value=yorha_icon.icon_id)
self.layout.operator(ImportActualDATNier2blender.bl_idname, text="DAT File for Nier:Automata (col+lay) (.dat)", icon_value=yorha_icon.icon_id)
self.layout.operator(ImportColNier2Blender.bl_idname, text="Collision File for Nier:Automata (.col)", icon_value=yorha_icon.icon_id)
self.layout.operator(ImportLayNier2Blender.bl_idname, text="Layout File for Nier:Automata (.lay)", icon_value=yorha_icon.icon_id)
classes = (
ImportNier2blender,
ImportDATNier2blender,
ImportActualDATNier2blender,
ImportColNier2Blender,
ImportLayNier2Blender,
SelectDirectory,
NieR2BlenderPreferences,
NieR2BlenderCreateObjBBox,
N2BLayoutObjectMenu
)
preview_collections = {}
def register():
# Custom icons
import bpy.utils.previews
pcoll = bpy.utils.previews.new()
my_icons_dir = os.path.join(os.path.dirname(__file__), "icons")
pcoll.load("yorha", os.path.join(my_icons_dir, "yorha-filled.png"), 'IMAGE')
preview_collections["main"] = pcoll
for cls in classes:
bpy.utils.register_class(cls)
bpy.types.TOPBAR_MT_file_import.append(menu_func_import)
bpy.types.VIEW3D_MT_object.append(menu_func_utils)
def unregister():
for pcoll in preview_collections.values():
bpy.utils.previews.remove(pcoll)
preview_collections.clear()
for cls in classes:
bpy.utils.unregister_class(cls)
bpy.types.TOPBAR_MT_file_import.remove(menu_func_import)
bpy.types.VIEW3D_MT_object.remove(menu_func_utils)
if __name__ == '__main__':
register()
+43
View File
@@ -0,0 +1,43 @@
#encoding = utf-8
import os
import sys
import struct
def to_float(bs):
return struct.unpack("<f", bs)[0]
def to_float16(bs):
return struct.unpack("<e", bs)[0]
def to_uint(bs):
return (int.from_bytes(bs, byteorder='little', signed=False))
def to_int(bs):
return (int.from_bytes(bs, byteorder='little', signed=True))
def to_string(bs, encoding = 'utf8'):
return bs.split(b'\x00')[0].decode(encoding)
def to_ushort(bs):
return struct.unpack("<H", bs)[0]
def create_dir(dirpath):
if not os.path.exists(dirpath):
os.makedirs(dirpath)
def find_files(dir_name,ext):
filenameArray = []
for dirpath,dirnames,filename in os.walk(dir_name):
for file in filename:
filename = "%s\%s"%(dirpath,file)
#print(filename)
if filename.find(ext) > -1:
filenameArray.append(filename)
return filenameArray
def print_class(obj):
print ('\n'.join(sorted(['%s:\t%s ' % item for item in obj.__dict__.items() if item[0].find('Offset') < 0 or item[0].find('unknown') < 0 ])))
print('\n')
def current_postion(fp):
print(hex(fp.tell()))
+713
View File
@@ -0,0 +1,713 @@
from .util import *
from .wta import *
import numpy as np
import json
class WMB_Header(object):
""" fucking header """
def __init__(self, wmb_fp):
super(WMB_Header, self).__init__()
self.magicNumber = wmb_fp.read(4) # ID
if self.magicNumber == b'WMB3':
self.version = "%08x" % (to_uint(wmb_fp.read(4))) # Version
self.unknown08 = to_uint(wmb_fp.read(4)) # UnknownA
self.flags = to_uint(wmb_fp.read(4)) # flags & referenceBone
self.bounding_box1 = to_float(wmb_fp.read(4)) # bounding_box
self.bounding_box2 = to_float(wmb_fp.read(4))
self.bounding_box3 = to_float(wmb_fp.read(4))
self.bounding_box4 = to_float(wmb_fp.read(4))
self.bounding_box5 = to_float(wmb_fp.read(4))
self.bounding_box6 = to_float(wmb_fp.read(4))
self.boneArrayOffset = to_uint(wmb_fp.read(4)) # offsetBones
self.boneCount = to_uint(wmb_fp.read(4)) # numBones
self.offsetBoneIndexTranslateTable = to_uint(wmb_fp.read(4)) # offsetBoneIndexTranslateTable
self.boneIndexTranslateTableSize = to_uint(wmb_fp.read(4)) # boneIndexTranslateTableSize
self.vertexGroupArrayOffset = to_uint(wmb_fp.read(4)) # offsetVertexGroups
self.vertexGroupCount = to_uint(wmb_fp.read(4)) # numVertexGroups
self.meshArrayOffset = to_uint(wmb_fp.read(4)) # offsetBatches
self.meshCount = to_uint(wmb_fp.read(4)) # numBatches
self.meshGroupInfoArrayHeaderOffset = to_uint(wmb_fp.read(4)) # offsetLODS
self.meshGroupInfoArrayCount = to_uint(wmb_fp.read(4)) # numLODS
self.colTreeNodesOffset = to_uint(wmb_fp.read(4)) # offsetColTreeNodes
self.colTreeNodesCount = to_uint(wmb_fp.read(4)) # numColTreeNodes
self.boneMapOffset = to_uint(wmb_fp.read(4)) # offsetBoneMap
self.boneMapCount = to_uint(wmb_fp.read(4)) # numBoneMap
self.bonesetOffset = to_uint(wmb_fp.read(4)) # offsetBoneSets
self.bonesetCount = to_uint(wmb_fp.read(4)) # numBoneSets
self.materialArrayOffset = to_uint(wmb_fp.read(4)) # offsetMaterials
self.materialCount = to_uint(wmb_fp.read(4)) # numMaterials
self.meshGroupOffset = to_uint(wmb_fp.read(4)) # offsetMeshes
self.meshGroupCount = to_uint(wmb_fp.read(4)) # numMeshes
self.offsetMeshMaterials = to_uint(wmb_fp.read(4)) # offsetMeshMaterials
self.numMeshMaterials = to_uint(wmb_fp.read(4)) # numMeshMaterials
self.unknownWorldDataArrayOffset = to_uint(wmb_fp.read(4)) # offsetUnknown0 World Model Stuff
self.unknownWorldDataArrayCount = to_uint(wmb_fp.read(4)) # numUnknown0 World Model Stuff
self.unknown8C = to_uint(wmb_fp.read(4))
class wmb3_vertexHeader(object):
"""docstring for wmb3_vertexHeader"""
def __init__(self, wmb_fp):
super(wmb3_vertexHeader, self).__init__()
self.vertexArrayOffset = to_uint(wmb_fp.read(4))
self.vertexExDataArrayOffset = to_uint(wmb_fp.read(4))
self.unknown08 = to_uint(wmb_fp.read(4))
self.unknown0C = to_uint(wmb_fp.read(4))
self.vertexStride = to_uint(wmb_fp.read(4))
self.vertexExDataStride = to_uint(wmb_fp.read(4))
self.unknown18 = to_uint(wmb_fp.read(4))
self.unknown1C = to_uint(wmb_fp.read(4))
self.vertexCount = to_uint(wmb_fp.read(4))
self.vertexFlags = to_uint(wmb_fp.read(4))
self.faceArrayOffset = to_uint(wmb_fp.read(4))
self.faceCount = to_uint(wmb_fp.read(4))
class wmb3_vertex(object):
"""docstring for wmb3_vertex"""
def __init__(self, wmb_fp, vertex_flags):
super(wmb3_vertex, self).__init__()
self.positionX = to_float(wmb_fp.read(4))
self.positionY = to_float(wmb_fp.read(4))
self.positionZ = to_float(wmb_fp.read(4))
self.normalX = to_uint(wmb_fp.read(1)) * 2 / 255
self.normalY = to_uint(wmb_fp.read(1)) * 2 / 255
self.normalZ = to_uint(wmb_fp.read(1)) * 2 / 255
wmb_fp.read(1)
self.textureU = to_float16(wmb_fp.read(2))
self.textureV = to_float16(wmb_fp.read(2))
if vertex_flags in [0]:
self.normal = hex(to_uint(wmb_fp.read(8)))
if vertex_flags in [1, 4, 5, 12, 14]:
self.textureU2 = to_float16(wmb_fp.read(2))
self.textureV2 = to_float16(wmb_fp.read(2))
if vertex_flags in [7, 10, 11]:
self.boneIndices = [to_uint(wmb_fp.read(1)) for i in range(4)]
self.boneWeights = [to_uint(wmb_fp.read(1))/255 for i in range(4)]
if vertex_flags in [4, 5, 12, 14]:
self.color = [to_uint(wmb_fp.read(1)) for i in range(4)]
class wmb3_vertexExData(object):
"""docstring for wmb3_vertexExData"""
def __init__(self, wmb_fp, vertex_flags):
super(wmb3_vertexExData, self).__init__()
#0x0 has no ExVertexData
if vertex_flags in [1, 4]: #0x1, 0x4
self.normal = hex(to_uint(wmb_fp.read(8)))
elif vertex_flags in [5]: #0x5
self.normal = hex(to_uint(wmb_fp.read(8)))
self.textureU3 = to_float16(wmb_fp.read(2))
self.textureV3 = to_float16(wmb_fp.read(2))
elif vertex_flags in [7]: #0x7
self.textureU2 = to_float16(wmb_fp.read(2))
self.textureV2 = to_float16(wmb_fp.read(2))
self.normal = hex(to_uint(wmb_fp.read(8)))
elif vertex_flags in [10]: #0xa
self.textureU2 = to_float16(wmb_fp.read(2))
self.textureV2 = to_float16(wmb_fp.read(2))
self.color = [to_uint(wmb_fp.read(1)) for i in range(4)]
self.normal = hex(to_uint(wmb_fp.read(8)))
elif vertex_flags in [11]: #0xb
self.textureU2 = to_float16(wmb_fp.read(2))
self.textureV2 = to_float16(wmb_fp.read(2))
self.color = [to_uint(wmb_fp.read(1)) for i in range(4)]
self.normal = hex(to_uint(wmb_fp.read(8)))
self.textureU3 = to_float16(wmb_fp.read(2))
self.textureV3 = to_float16(wmb_fp.read(2))
elif vertex_flags in [12]: #0xc
self.normal = hex(to_uint(wmb_fp.read(8)))
self.textureU3 = to_float16(wmb_fp.read(2))
self.textureV3 = to_float16(wmb_fp.read(2))
self.textureU4 = to_float16(wmb_fp.read(2))
self.textureV4 = to_float16(wmb_fp.read(2))
self.textureU5 = to_float16(wmb_fp.read(2))
self.textureV5 = to_float16(wmb_fp.read(2))
elif vertex_flags in [14]: #0xe
self.normal = hex(to_uint(wmb_fp.read(8)))
self.textureU3 = to_float16(wmb_fp.read(2))
self.textureV3 = to_float16(wmb_fp.read(2))
self.textureU4 = to_float16(wmb_fp.read(2))
self.textureV4 = to_float16(wmb_fp.read(2))
class wmb3_vertexGroup(object):
"""docstring for wmb3_vertexGroup"""
def __init__(self, wmb_fp, faceSize):
super(wmb3_vertexGroup, self).__init__()
self.faceSize = faceSize
self.vertexGroupHeader = wmb3_vertexHeader(wmb_fp)
self.vertexFlags = self.vertexGroupHeader.vertexFlags
self.vertexArray = []
wmb_fp.seek(self.vertexGroupHeader.vertexArrayOffset)
for vertex_index in range(self.vertexGroupHeader.vertexCount):
vertex = wmb3_vertex(wmb_fp, self.vertexGroupHeader.vertexFlags)
self.vertexArray.append(vertex)
self.vertexesExDataArray = []
wmb_fp.seek(self.vertexGroupHeader.vertexExDataArrayOffset)
for vertexIndex in range(self.vertexGroupHeader.vertexCount):
self.vertexesExDataArray.append(wmb3_vertexExData(wmb_fp, self.vertexGroupHeader.vertexFlags))
self.faceRawArray = []
wmb_fp.seek(self.vertexGroupHeader.faceArrayOffset)
for face_index in range(self.vertexGroupHeader.faceCount):
if faceSize == 2:
self.faceRawArray.append(to_uint(wmb_fp.read(2)) + 1)
else:
self.faceRawArray.append(to_uint(wmb_fp.read(4)) + 1)
class wmb3_mesh(object):
"""docstring for wmb3_mesh"""
def __init__(self, wmb_fp):
super(wmb3_mesh, self).__init__()
self.vertexGroupIndex = to_uint(wmb_fp.read(4))
self.bonesetIndex = to_uint(wmb_fp.read(4))
self.vertexStart = to_uint(wmb_fp.read(4))
self.faceStart = to_uint(wmb_fp.read(4))
self.vertexCount = to_uint(wmb_fp.read(4))
self.faceCount = to_uint(wmb_fp.read(4))
self.unknown18 = to_uint(wmb_fp.read(4))
class wmb3_bone(object):
"""docstring for wmb3_bone"""
def __init__(self, wmb_fp,index):
super(wmb3_bone, self).__init__()
self.boneIndex = index
self.boneNumber = to_uint(wmb_fp.read(2))
self.parentIndex = to_uint(wmb_fp.read(2))
local_positionX = to_float(wmb_fp.read(4))
local_positionY = to_float(wmb_fp.read(4))
local_positionZ = to_float(wmb_fp.read(4))
local_rotationX = to_float(wmb_fp.read(4))
local_rotationY = to_float(wmb_fp.read(4))
local_rotationZ = to_float(wmb_fp.read(4))
self.local_scaleX = to_float(wmb_fp.read(4))
self.local_scaleY = to_float(wmb_fp.read(4))
self.local_scaleZ = to_float(wmb_fp.read(4))
world_positionX = to_float(wmb_fp.read(4))
world_positionY = to_float(wmb_fp.read(4))
world_positionZ = to_float(wmb_fp.read(4))
world_rotationX = to_float(wmb_fp.read(4))
world_rotationY = to_float(wmb_fp.read(4))
world_rotationZ = to_float(wmb_fp.read(4))
world_scaleX = to_float(wmb_fp.read(4))
world_scaleY = to_float(wmb_fp.read(4))
world_scaleZ = to_float(wmb_fp.read(4))
world_position_tposeX = to_float(wmb_fp.read(4))
world_position_tposeY = to_float(wmb_fp.read(4))
world_position_tposeZ = to_float(wmb_fp.read(4))
self.local_position = (local_positionX, local_positionY, local_positionZ)
self.local_rotation = (local_rotationX, local_rotationY, local_rotationZ)
self.world_position = (world_positionX, world_positionY, world_positionZ)
self.world_rotation = (world_rotationX, world_rotationY, world_rotationZ)
self.world_scale = (world_scaleX, world_scaleY, world_scaleZ)
self.world_position_tpose = (world_position_tposeX, world_position_tposeY, world_position_tposeZ)
class wmb3_boneMap(object):
"""docstring for wmb3_boneMap"""
def __init__(self, wmb_fp):
super(wmb3_boneMap, self).__init__()
self.boneMapOffset = to_uint(wmb_fp.read(4))
self.boneMapCount = to_uint(wmb_fp.read(4))
class wmb3_boneSet(object):
"""docstring for wmb3_boneSet"""
def __init__(self, wmb_fp, boneSetCount):
super(wmb3_boneSet, self).__init__()
self.boneSetArray = []
self.boneSetCount = boneSetCount
boneSetInfoArray = []
for index in range(boneSetCount):
offset = to_uint(wmb_fp.read(4))
count = to_uint(wmb_fp.read(4))
boneSetInfoArray.append([offset, count])
for boneSetInfo in boneSetInfoArray:
wmb_fp.seek(boneSetInfo[0])
boneSet = []
for index in range(boneSetInfo[1]):
boneSet.append(to_uint(wmb_fp.read(2)))
self.boneSetArray.append(boneSet)
class wmb3_material(object):
"""docstring for wmb3_material"""
def __init__(self, wmb_fp):
super(wmb3_material, self).__init__()
to_uint(wmb_fp.read(2))
to_uint(wmb_fp.read(2))
to_uint(wmb_fp.read(2))
to_uint(wmb_fp.read(2))
materialNameOffset = to_uint(wmb_fp.read(4))
effectNameOffset = to_uint(wmb_fp.read(4))
techniqueNameOffset = to_uint(wmb_fp.read(4))
to_uint(wmb_fp.read(4))
textureOffset = to_uint(wmb_fp.read(4))
textureNum = to_uint(wmb_fp.read(4))
paramterGroupsOffset = to_uint(wmb_fp.read(4))
numParameterGroups = to_uint(wmb_fp.read(4))
varOffset = to_uint(wmb_fp.read(4))
varNum = to_uint(wmb_fp.read(4))
wmb_fp.seek(materialNameOffset)
self.materialName = to_string(wmb_fp.read(256))
wmb_fp.seek(effectNameOffset)
self.effectName = to_string(wmb_fp.read(256))
wmb_fp.seek(techniqueNameOffset)
self.techniqueName = to_string(wmb_fp.read(256))
self.textureArray = {}
path_split = wmb_fp.name.split('\\')
mat_list_filepath = "\\".join(path_split[:-3])
mat_list_file = open(mat_list_filepath + '\\materials.json', 'a+')
mat_list_file.seek(0)
file_dict = {}
# Try to load json from pre-existing file
try:
file_dict = json.load(mat_list_file)
except Exception as ex:
#print("Could not load json: " , ex)
pass
# Clear file contents
mat_list_file.truncate(0)
file_dict[self.materialName] = {}
# Append textures to materials in the dictionary
for i in range(textureNum):
wmb_fp.seek(textureOffset + i * 8)
offset = to_uint(wmb_fp.read(4))
identifier = "%08x"%to_uint(wmb_fp.read(4))
wmb_fp.seek(offset)
textureTypeName = to_string(wmb_fp.read(256))
self.textureArray[textureTypeName] = identifier
# Add new texture to nested material dictionary
file_dict[self.materialName][textureTypeName] = identifier
# Write the current material to materials.json
json.dump(file_dict, mat_list_file, indent= 4)
mat_list_file.close()
wmb_fp.seek(paramterGroupsOffset)
self.parameterGroups = []
for i in range(numParameterGroups):
wmb_fp.seek(paramterGroupsOffset + i * 12)
parameters = []
index = to_uint(wmb_fp.read(4))
offset = to_uint(wmb_fp.read(4))
num = to_uint(wmb_fp.read(4))
wmb_fp.seek(offset)
for k in range(num):
param = to_float(wmb_fp.read(4))
parameters.append(param)
self.parameterGroups.append(parameters)
wmb_fp.seek(varOffset)
self.uniformArray = {}
for i in range(varNum):
wmb_fp.seek(varOffset + i * 8)
offset = to_uint(wmb_fp.read(4))
value = to_float(wmb_fp.read(4))
wmb_fp.seek(offset)
self.uniformArray [to_string(wmb_fp.read(256))] = value
class wmb3_meshGroup(object):
"""docstring for wmb3_meshGroupInfo"""
def __init__(self, wmb_fp):
super(wmb3_meshGroup, self).__init__()
nameOffset = to_uint(wmb_fp.read(4))
self.boundingBox = []
for i in range(6):
self.boundingBox.append(to_float(wmb_fp.read(4)))
materialIndexArrayOffset = to_uint(wmb_fp.read(4))
materialIndexArrayCount = to_uint(wmb_fp.read(4))
boneIndexArrayOffset =to_uint(wmb_fp.read(4))
boneIndexArrayCount = to_uint(wmb_fp.read(4))
wmb_fp.seek(nameOffset)
self.meshGroupname = to_string(wmb_fp.read(256))
self.materialIndexArray = []
self.boneIndexArray = []
wmb_fp.seek(materialIndexArrayOffset)
for i in range(materialIndexArrayCount):
self.materialIndexArray.append(to_uint(wmb_fp.read(2)))
wmb_fp.seek(boneIndexArrayOffset)
for i in range(boneIndexArrayCount):
self.boneIndexArray.append(to_uint(wmb_fp.read(2)))
class wmb3_groupedMesh(object):
"""docstring for wmb3_groupedMesh"""
def __init__(self, wmb_fp):
super(wmb3_groupedMesh, self).__init__()
self.vertexGroupIndex = to_uint(wmb_fp.read(4))
self.meshGroupIndex = to_uint(wmb_fp.read(4))
self.materialIndex = to_uint(wmb_fp.read(4))
self.colTreeNodeIndex = to_uint(wmb_fp.read(4))
if self.colTreeNodeIndex == 4294967295:
self.colTreeNodeIndex = -1
self.meshGroupInfoMaterialPair = to_uint(wmb_fp.read(4))
self.unknownWorldDataIndex = to_uint(wmb_fp.read(4))
if self.unknownWorldDataIndex == 4294967295:
self.unknownWorldDataIndex = -1
class wmb3_meshGroupInfo(object):
"""docstring for wmb3_meshGroupInfo"""
def __init__(self, wmb_fp):
super(wmb3_meshGroupInfo, self).__init__()
self.nameOffset = to_uint(wmb_fp.read(4))
self.lodLevel = to_uint(wmb_fp.read(4))
if self.lodLevel == 4294967295:
self.lodLevel = -1
self.meshStart = to_uint(wmb_fp.read(4))
meshGroupInfoOffset = to_uint(wmb_fp.read(4))
self.meshCount = to_uint(wmb_fp.read(4))
wmb_fp.seek(self.nameOffset)
self.meshGroupInfoname = to_string(wmb_fp.read(256))
wmb_fp.seek(meshGroupInfoOffset)
self.groupedMeshArray = []
for i in range(self.meshCount):
groupedMesh = wmb3_groupedMesh(wmb_fp)
self.groupedMeshArray.append(groupedMesh)
class wmb3_colTreeNode(object):
"""docstring for colTreeNode"""
def __init__(self, wmb_fp):
p1_x = to_float(wmb_fp.read(4))
p1_y = to_float(wmb_fp.read(4))
p1_z = to_float(wmb_fp.read(4))
self.p1 = (p1_x, p1_y, p1_z)
p2_x = to_float(wmb_fp.read(4))
p2_y = to_float(wmb_fp.read(4))
p2_z = to_float(wmb_fp.read(4))
self.p2 = (p2_x, p2_y, p2_z)
self.left = to_uint(wmb_fp.read(4))
if self.left == 4294967295:
self.left = -1
self.right = to_uint(wmb_fp.read(4))
if self.right == 4294967295:
self.right = -1
class wmb3_worldData(object):
"""docstring for wmb3_unknownWorldData"""
def __init__(self, wmb_fp):
self.unknownWorldData = []
for entry in range(6):
self.unknownWorldData.append(wmb_fp.read(4))
class WMB3(object):
"""docstring for WMB3"""
def __init__(self, wmb_file):
super(WMB3, self).__init__()
wmb_fp = 0
wta_fp = 0
wtp_fp = 0
self.wta = 0
wmb_path = wmb_file
if not os.path.exists(wmb_path):
wmb_path = wmb_file.replace('.dat','.dtt')
wtp_path = wmb_file.replace('.dat','.dtt').replace('.wmb','.wtp')
wta_path = wmb_file.replace('.dtt','.dat').replace('.wmb','.wta')
if os.path.exists(wtp_path):
print('open wtp file')
self.wtp_fp = open(wtp_path,'rb')
if os.path.exists(wta_path):
print('open wta file')
wta_fp = open(wta_path,'rb')
if wta_fp:
self.wta = WTA(wta_fp)
wta_fp.close()
if os.path.exists(wmb_path):
wmb_fp = open(wmb_path, "rb")
else:
print("DTT/DAT does not contain WMB file.")
return
self.wmb3_header = WMB_Header(wmb_fp)
self.hasBone = False
if self.wmb3_header.boneCount > 0:
self.hasBone = True
print_class(self.wmb3_header)
wmb_fp.seek(self.wmb3_header.boneArrayOffset)
self.boneArray = []
for boneIndex in range(self.wmb3_header.boneCount):
self.boneArray.append(wmb3_bone(wmb_fp,boneIndex))
# indexBoneTranslateTable
wmb_fp.seek(self.wmb3_header.offsetBoneIndexTranslateTable)
self.firstLevel = []
for entry in range(16):
self.firstLevel.append(to_uint(wmb_fp.read(2)))
if self.firstLevel[-1] == 65535:
self.firstLevel[-1] = -1
firstLevel_Entry_Count = 0
for entry in self.firstLevel:
if entry != -1:
firstLevel_Entry_Count += 1
self.secondLevel = []
for entry in range(firstLevel_Entry_Count * 16):
self.secondLevel.append(to_uint(wmb_fp.read(2)))
if self.secondLevel[-1] == 65535:
self.secondLevel[-1] = -1
secondLevel_Entry_Count = 0
for entry in self.secondLevel:
if entry != -1:
secondLevel_Entry_Count += 1
self.thirdLevel = []
for entry in range(secondLevel_Entry_Count * 16):
self.thirdLevel.append(to_uint(wmb_fp.read(2)))
if self.thirdLevel[-1] == 65535:
self.thirdLevel[-1] = -1
wmb_fp.seek(self.wmb3_header.offsetBoneIndexTranslateTable)
unknownData1Array = []
for i in range(self.wmb3_header.boneIndexTranslateTableSize):
unknownData1Array.append(to_uint(wmb_fp.read(1)))
self.vertexGroupArray = []
for vertexGroupIndex in range(self.wmb3_header.vertexGroupCount):
wmb_fp.seek(self.wmb3_header.vertexGroupArrayOffset + 0x30 * vertexGroupIndex)
vertexGroup = wmb3_vertexGroup(wmb_fp,((self.wmb3_header.flags & 0x8) and 4 or 2))
self.vertexGroupArray.append(vertexGroup)
self.meshArray = []
wmb_fp.seek(self.wmb3_header.meshArrayOffset)
for meshIndex in range(self.wmb3_header.meshCount):
mesh = wmb3_mesh(wmb_fp)
self.meshArray.append(mesh)
self.meshGroupInfoArray = []
for meshGroupInfoArrayIndex in range(self.wmb3_header.meshGroupInfoArrayCount):
wmb_fp.seek(self.wmb3_header.meshGroupInfoArrayHeaderOffset + meshGroupInfoArrayIndex * 0x14)
meshGroupInfo= wmb3_meshGroupInfo(wmb_fp)
self.meshGroupInfoArray.append(meshGroupInfo)
self.meshGroupArray = []
for meshGroupIndex in range(self.wmb3_header.meshGroupCount):
wmb_fp.seek(self.wmb3_header.meshGroupOffset + meshGroupIndex * 0x2c)
meshGroup = wmb3_meshGroup(wmb_fp)
self.meshGroupArray.append(meshGroup)
self.materialArray = []
for materialIndex in range(self.wmb3_header.materialCount):
wmb_fp.seek(self.wmb3_header.materialArrayOffset + materialIndex * 0x30)
material = wmb3_material(wmb_fp)
self.materialArray.append(material)
wmb_fp.seek(self.wmb3_header.boneMapOffset)
self.boneMap = []
for index in range(self.wmb3_header.boneMapCount):
self.boneMap.append(to_uint(wmb_fp.read(4)))
wmb_fp.seek(self.wmb3_header.bonesetOffset)
self.boneSetArray = wmb3_boneSet(wmb_fp, self.wmb3_header.bonesetCount).boneSetArray
# colTreeNode
self.hasColTreeNodes = False
if self.wmb3_header.colTreeNodesOffset > 0:
self.hasColTreeNodes = True
self.colTreeNodes = []
wmb_fp.seek(self.wmb3_header.colTreeNodesOffset)
for index in range(self.wmb3_header.colTreeNodesCount):
self.colTreeNodes.append(wmb3_colTreeNode(wmb_fp))
# World Model Data
self.hasUnknownWorldData = False
if self.wmb3_header.unknownWorldDataArrayOffset > 0:
self.hasUnknownWorldData = True
self.unknownWorldDataArray = []
wmb_fp.seek(self.wmb3_header.unknownWorldDataArrayOffset)
for index in range(self.wmb3_header.unknownWorldDataArrayCount):
self.unknownWorldDataArray.append(wmb3_worldData(wmb_fp))
def clear_unused_vertex(self, meshArrayIndex,vertexGroupIndex):
mesh = self.meshArray[meshArrayIndex]
faceRawStart = mesh.faceStart
faceRawCount = mesh.faceCount
vertexStart = mesh.vertexStart
vertexCount = mesh.vertexCount
vertexesExDataArray = self.vertexGroupArray[vertexGroupIndex].vertexesExDataArray
vertexesExData = vertexesExDataArray[vertexStart : vertexStart + vertexCount]
vertex_colors = []
faceRawArray = self.vertexGroupArray[vertexGroupIndex].faceRawArray
facesRaw = faceRawArray[faceRawStart : faceRawStart + faceRawCount ]
facesRaw = [index - 1 for index in facesRaw]
usedVertexIndexArray = sorted(list(set(facesRaw)))
mappingDict = {}
for newIndex in range(len(usedVertexIndexArray)):
mappingDict[usedVertexIndexArray[newIndex]] = newIndex
for i in range(len(facesRaw)):
facesRaw[i] = mappingDict[facesRaw[i]]
faces = [0] * int(faceRawCount / 3)
usedVertices = [0] * len(usedVertexIndexArray)
boneWeightInfos = [[],[]]
for i in range(0, faceRawCount, 3):
faces[int(i/3)] = (facesRaw[i] , facesRaw[i + 1] , facesRaw[i + 2] )
meshVertices = self.vertexGroupArray[vertexGroupIndex].vertexArray
if self.hasBone:
boneWeightInfos = [0] * len(usedVertexIndexArray)
for newIndex in range(len(usedVertexIndexArray)):
i = usedVertexIndexArray[newIndex]
usedVertices[newIndex] = (meshVertices[i].positionX, meshVertices[i].positionY, meshVertices[i].positionZ)
# Vertex_Colors are stored in VertexData
if self.vertexGroupArray[vertexGroupIndex].vertexFlags in [4, 5, 12, 14]:
vertex_colors.append(meshVertices[i].color)
# Vertex_Colors are stored in VertexExData
if self.vertexGroupArray[vertexGroupIndex].vertexFlags in [10, 11]:
vertex_colors.append(vertexesExData[i].color)
if self.hasBone:
bonesetIndex = mesh.bonesetIndex
boneSetArray = self.boneSetArray
boneMap = self.boneMap
if bonesetIndex < 0xffffffff:
boneSet = boneSetArray[bonesetIndex]
boneIndices = [boneMap[boneSet[index]] for index in meshVertices[i].boneIndices]
boneWeightInfos[newIndex] = [boneIndices, meshVertices[i].boneWeights]
s = sum([weight for weight in meshVertices[i].boneWeights])
if s > 1.000000001 or s < 0.999999:
print('[-] error weight detect %f' % s)
print(meshVertices[i].boneWeights)
else:
self.hasBone = False
return usedVertices, faces, usedVertexIndexArray, boneWeightInfos, vertex_colors
def export_obj(wmb, wta, wtp_fp, obj_file):
if not obj_file:
obj_file = 'test'
create_dir('out/%s'%obj_file)
obj_file = 'out/%s/%s'%(obj_file, obj_file)
textureArray = []
if (wta and wtp_fp):
for materialIndex in range(wmb.wmb3_header.materialCount):
material = wmb.materialArray[materialIndex]
materialName = material.materialName
if 'g_AlbedoMap' in material.textureArray.keys():
identifier = material.textureArray['g_AlbedoMap']
textureFile = "%s%s"%('out/texture/',identifier)
textureArray.append(textureFile)
if 'g_NormalMap' in material.textureArray.keys():
identifier = material.textureArray['g_NormalMap']
textureFile = "%s%s"%('out/texture/',identifier)
textureArray.append(textureFile)
for textureFile in textureArray:
texture = wta.getTextureByIdentifier(textureFile.replace('out/texture/',''), wtp_fp)
if texture:
texture_fp = open("%s.dds"%textureFile, "wb")
print('dumping %s.dds'%textureFile)
texture_fp.write(texture)
texture_fp.close()
mtl = open("%s.mtl"%obj_file, 'w')
for materialIndex in range(wmb.wmb3_header.materialCount):
material = wmb.materialArray[materialIndex]
materialName = material.materialName
if 'g_AlbedoMap' in material.textureArray.keys():
identifier = material.textureArray['g_AlbedoMap']
textureFile = "%s%s"%('out/texture/',identifier)
mtl.write('newmtl %s\n'%(identifier))
mtl.write('Ns 96.0784\nNi 1.5000\nd 1.0000\nTr 0.0000\nTf 1.0000 1.0000 1.0000 \nillum 2\nKa 0.0000 0.0000 0.0000\nKd 0.6400 0.6400 0.6400\nKs 0.0873 0.0873 0.0873\nKe 0.0000 0.0000 0.0000\n')
mtl.write('map_Ka %s.dds\nmap_Kd %s.dds\n'%(textureFile.replace('out','..'),textureFile.replace('out','..')))
if 'g_NormalMap' in material.textureArray.keys():
identifier = material.textureArray['g_NormalMap']
textureFile2 = "%s%s"%('out/texture/',identifier)
mtl.write("bump %s.dds\n"%textureFile2.replace('out','..'))
mtl.write('\n')
mtl.close()
for vertexGroupIndex in range(wmb.wmb3_header.vertexGroupCount):
for meshGroupIndex in range(wmb.wmb3_header.meshGroupCount):
meshIndexArray = []
groupedMeshArray = wmb.meshGroupInfoArray[0].groupedMeshArray
for groupedMeshIndex in range(len(groupedMeshArray)):
if groupedMeshArray[groupedMeshIndex].meshGroupIndex == meshGroupIndex:
meshIndexArray.append(groupedMeshIndex)
meshGroup = wmb.meshGroupArray[meshGroupIndex]
for meshArrayIndex in (meshIndexArray):
meshVertexGroupIndex = wmb.meshArray[meshArrayIndex].vertexGroupIndex
if meshVertexGroupIndex == vertexGroupIndex:
if not os.path.exists('%s_%s_%d.obj'%(obj_file,meshGroup.meshGroupname,vertexGroupIndex)):
obj = open('%s_%s_%d.obj'%(obj_file,meshGroup.meshGroupname,vertexGroupIndex),"w")
obj.write('mtllib ./%s.mtl\n'%obj_file.split('/')[-1])
for vertexIndex in range(wmb.vertexGroupArray[vertexGroupIndex].vertexGroupHeader.vertexCount):
vertex = wmb.vertexGroupArray[vertexGroupIndex].vertexArray[vertexIndex]
obj.write('v %f %f %f\n'%(vertex.positionX,vertex.positionY,vertex.positionZ))
obj.write('vt %f %f\n'%(vertex.textureU,1 - vertex.textureV))
obj.write('vn %f %f %f\n'%(vertex.normalX, vertex.normalY, vertex.normalZ))
else:
obj = open('%s_%s_%d.obj'%(obj_file,meshGroup.meshGroupname,vertexGroupIndex),"a+")
if 'g_AlbedoMap' in wmb.materialArray[groupedMeshArray[meshArrayIndex].materialIndex].textureArray.keys():
textureFile = wmb.materialArray[groupedMeshArray[meshArrayIndex].materialIndex].textureArray["g_AlbedoMap"]
obj.write('usemtl %s\n'%textureFile.split('/')[-1])
print('dumping %s_%s_%d.obj'%(obj_file,meshGroup.meshGroupname,vertexGroupIndex))
obj.write('g %s%d\n'% (meshGroup.meshGroupname,vertexGroupIndex))
faceRawStart = wmb.meshArray[meshArrayIndex].faceStart
faceRawNum = wmb.meshArray[meshArrayIndex].faceCount
vertexStart = wmb.meshArray[meshArrayIndex].vertexStart
vertexNum = wmb.meshArray[meshArrayIndex].vertexCount
faceRawArray = wmb.vertexGroupArray[meshVertexGroupIndex].faceRawArray
for i in range(int(faceRawNum/3)):
obj.write('f %d/%d/%d %d/%d/%d %d/%d/%d\n'%(
faceRawArray[faceRawStart + i * 3],faceRawArray[faceRawStart + i * 3],faceRawArray[faceRawStart + i * 3],
faceRawArray[faceRawStart + i * 3 + 1],faceRawArray[faceRawStart + i * 3 + 1],faceRawArray[faceRawStart + i * 3 + 1],
faceRawArray[faceRawStart + i * 3 + 2],faceRawArray[faceRawStart + i * 3 + 2],faceRawArray[faceRawStart + i * 3 + 2],
)
)
obj.close()
def main(arg, wmb_fp, wta_fp, wtp_fp, dump):
wmb = WMB3(wmb_fp)
wmb_fp.close()
wta = 0
if wta_fp:
wta = WTA(wta_fp)
wta_fp.close()
if dump:
obj_file = arg.split('\\')[-1].replace('.wmb','')
export_obj(wmb, wta, wtp_fp, obj_file)
if wtp_fp:
wtp_fp.close()
if __name__ == '__main__':
pass
+665
View File
@@ -0,0 +1,665 @@
import bpy, bmesh, math
from mathutils import Vector, Matrix
from .wmb import *
def show_message(message = "", title = "Message Box", icon = 'INFO'):
def draw(self, context):
self.layout.label(text = message)
self.layout.alignment = 'CENTER'
bpy.context.window_manager.popup_menu(draw, title = title, icon = icon)
def reset_blend():
#bpy.ops.object.mode_set(mode='OBJECT')
for collection in bpy.data.collections:
for obj in collection.objects:
collection.objects.unlink(obj)
bpy.data.collections.remove(collection)
for bpy_data_iter in (bpy.data.objects,bpy.data.meshes,bpy.data.lights,bpy.data.cameras):
for id_data in bpy_data_iter:
bpy_data_iter.remove(id_data)
for material in bpy.data.materials:
bpy.data.materials.remove(material)
for amt in bpy.data.armatures:
bpy.data.armatures.remove(amt)
for obj in bpy.data.objects:
bpy.data.objects.remove(obj)
obj.user_clear()
def construct_armature(name, bone_data_array, firstLevel, secondLevel, thirdLevel, boneMap, boneSetArray, collection_name): # bone_data =[boneIndex, boneName, parentIndex, parentName, bone_pos, optional, boneNumber, localPos, local_rotation, world_rotation, world_position_tpose]
print('[+] importing armature')
amt = bpy.data.armatures.new(name +'Amt')
ob = bpy.data.objects.new(name, amt)
#ob = bpy.context.active_object
ob.show_in_front = False
ob.name = name
bpy.data.collections.get(collection_name).objects.link(ob)
bpy.context.view_layer.objects.active = ob
bpy.ops.object.mode_set(mode='EDIT')
amt['firstLevel'] = firstLevel
amt['secondLevel'] = secondLevel
amt['thirdLevel'] = thirdLevel
amt['boneMap'] = boneMap
amt['boneSetArray'] = boneSetArray
for bone_data in bone_data_array:
bone = amt.edit_bones.new(bone_data[1])
bone.head = Vector(bone_data[4])
bone.tail = Vector(bone_data[4]) + Vector((0 , 0.01, 0))
bone['ID'] = bone_data[6]
bone['localPosition'] = bone_data[7]
bone['localRotation'] = bone_data[8]
bone['worldRotation'] = bone_data[9]
bone['TPOSE_worldPosition'] = bone_data[10]
bones = amt.edit_bones
for bone_data in bone_data_array:
if bone_data[2] < 0xffff: #this value need to edit in different games
bone = bones[bone_data[1]]
bone.parent = bones[bone_data[3]]
#if bones[bone_data[3]]['ID'] != 0:
if bones[bone_data[3]].head != bone.head:
bones[bone_data[3]].tail = bone.head
bpy.ops.object.mode_set(mode='OBJECT')
ob.rotation_euler = (math.radians(90),0,0)
# split armature
return ob
def split_armature(name):
amt = bpy.data.armatures[name]
name = name.replace('Amt','')
bones = amt.bones
root_bones = [bone for bone in bones if not bone.parent]
for i in range(len(root_bones)):
bpy.ops.object.add(
type='ARMATURE',
enter_editmode=True,
location=(i * 2,0,0))
ob_new = bpy.context.object
ob_new.show_x_ray = False
ob_new.name = "%s_%d" % (name, i)
amt_new = ob_new.data
amt_new.name = '%s_%d_Amt' % (name, i)
copy_bone_tree(root_bones[i] ,amt_new)
bpy.ops.object.mode_set(mode="OBJECT")
ob_new.rotation_euler = (math.radians(90),0,0)
bpy.ops.object.select_all(action="DESELECT")
obj = bpy.data.objects[name]
scene = bpy.context.scene
scene.objects.unlink(obj)
return False
def copy_bone_tree(source_root, target_amt):
bone = target_amt.edit_bones.new(source_root.name)
bone.head = source_root.head_local
bone.tail = source_root.tail_local
if source_root.parent:
bone.parent = target_amt.edit_bones[source_root.parent.name]
for child in source_root.children:
copy_bone_tree(child, target_amt)
def construct_mesh(mesh_data, collection_name): # [meshName, vertices, faces, has_bone, boneWeightInfoArray, boneSetIndex, meshGroupIndex, vertex_colors, LOD_name, LOD_level, colTreeNodeIndex, unknownWorldDataIndex, boundingBox], collection_name
name = mesh_data[0]
for obj in bpy.data.objects:
if obj.name == name:
name = name + '-' + collection_name
vertices = mesh_data[1]
faces = mesh_data[2]
has_bone = mesh_data[3]
weight_infos = [[[],[]]] # A real fan can recognize me even I am a 2 dimensional array
print("[+] importing %s" % name)
objmesh = bpy.data.meshes.new(name)
if not name in bpy.data.objects.keys():
obj = bpy.data.objects.new(name, objmesh)
else:
obj = bpy.data.objects[name]
obj.location = Vector((0,0,0))
bpy.data.collections.get(collection_name).objects.link(obj)
objmesh.from_pydata(vertices, [], faces)
objmesh.update(calc_edges=True)
if len(mesh_data[7]) != 0:
if objmesh.vertex_colors:
vcol_layer = objmesh.vertex_colors.active
else:
vcol_layer = objmesh.vertex_colors.new()
for loop_idx, loop in enumerate(objmesh.loops):
vcol_layer.data[loop_idx].color[0] = mesh_data[7][loop.vertex_index][0]/255
vcol_layer.data[loop_idx].color[1] = mesh_data[7][loop.vertex_index][1]/255
vcol_layer.data[loop_idx].color[2] = mesh_data[7][loop.vertex_index][2]/255
vcol_layer.data[loop_idx].color[3] = mesh_data[7][loop.vertex_index][3]/255
if has_bone:
weight_infos = mesh_data[4]
group_names = sorted(list(set(["bone%d" % i for weight_info in weight_infos for i in weight_info[0]])))
for group_name in group_names:
obj.vertex_groups.new(name=group_name)
for i in range(len(weight_infos)):
for index in range(4):
group_name = "bone%d"%weight_infos[i][0][index]
weight = weight_infos[i][1][index]
group = obj.vertex_groups[group_name]
if weight:
group.add([i], weight, "REPLACE")
obj.rotation_euler = (math.radians(90),0,0)
if mesh_data[5] != "None":
obj['boneSetIndex'] = mesh_data[5]
obj['meshGroupIndex'] = mesh_data[6]
obj['LOD_Name'] = mesh_data[8]
obj['LOD_Level'] = mesh_data[9]
obj['colTreeNodeIndex'] = mesh_data[10]
obj['unknownWorldDataIndex'] = mesh_data[11]
obj.data.flip_normals()
return obj
def set_partent(parent, child):
bpy.context.view_layer.objects.active = parent
child.select_set(True)
parent.select_set(True)
bpy.ops.object.parent_set(type="ARMATURE")
child.select_set(False)
parent.select_set(False)
def consturct_materials(texture_dir, material):
material_name = material[0]
textures = material[1]
uniforms = material[2]
shader_name = material[3]
technique_name = material[4]
parameterGroups = material[5]
print('[+] importing material %s' % material_name)
material = bpy.data.materials.new( '%s' % (material_name))
material['Shader_Name'] = shader_name
material['Technique_Name'] = technique_name
# Enable Nodes
material.use_nodes = True
# Clear Nodes and Links
material.node_tree.links.clear()
material.node_tree.nodes.clear()
# Recreate Nodes and Links with references
nodes = material.node_tree.nodes
links = material.node_tree.links
# PrincipledBSDF and Ouput Shader
output = nodes.new(type='ShaderNodeOutputMaterial')
output.location = 1200,0
principled = nodes.new(type='ShaderNodeBsdfPrincipled')
principled.location = 900,0
output_link = links.new( principled.outputs['BSDF'], output.inputs['Surface'] )
# Normal Map Amount Counter
normal_map_count = 0
# Mask Map Count
mask_map_count = 0
# Alpha Channel
material.blend_method = 'CLIP'
#print("\n".join(["%s:%f" %(key, uniforms[key]) for key in sorted(uniforms.keys())]))
# Shader Parameters
for key in uniforms.keys():
material[key] = uniforms.get(key)
#print(key, material[key])
if key.lower().find("g_glossiness") > -1:
principled.inputs['Roughness'].default_value = 1 - uniforms[key]
# Custom Shader Parameters
for gindx, parameterGroup in enumerate(parameterGroups):
for pindx, parameter in enumerate(parameterGroup):
if pindx == 5:
material[str(gindx) + '_UseAlpha_' + str(pindx)] = parameter
else:
material[str(gindx) + '_' + str(pindx)] = parameter
albedo_maps = {}
normal_maps = {}
mask_maps = {}
for texturesType in textures.keys():
textures_type = texturesType.lower()
material[texturesType] = textures.get(texturesType)
texture_file = "%s/%s.dds" % (texture_dir, textures[texturesType])
if os.path.exists(texture_file):
if textures_type.find('albedo') > -1:
albedo_maps[textures_type] = textures.get(texturesType)
elif textures_type.find('normal') > -1:
normal_maps[textures_type] = textures.get(texturesType)
elif textures_type.find('mask') > -1:
mask_maps[textures_type] = textures.get(texturesType)
# Albedo Nodes
albedo_nodes = []
albedo_mixRGB_nodes = []
for i, textureID in enumerate(albedo_maps.values()):
texture_file = "%s/%s.dds" % (texture_dir, textureID)
if os.path.exists(texture_file):
albedo_image = nodes.new(type='ShaderNodeTexImage')
albedo_nodes.append(albedo_image)
albedo_image.location = 0,i*-60
albedo_image.image = bpy.data.images.load(texture_file)
albedo_image.hide = True
if i > 0:
albedo_image.label = "g_AlbedoMap" + str(i-1)
else:
albedo_image.label = "g_AlbedoMap"
if i > 0:
mixRGB_shader = nodes.new(type='ShaderNodeMixRGB')
albedo_mixRGB_nodes.append(mixRGB_shader)
mixRGB_shader.location = 300,(i-1)*-60
mixRGB_shader.hide = True
# Albedo Links
if len(albedo_nodes) == 1:
albedo_principled = links.new(albedo_nodes[0].outputs['Color'], principled.inputs['Base Color'])
alpha_link = links.new(albedo_nodes[0].outputs['Alpha'], principled.inputs['Alpha'])
else:
if len(albedo_mixRGB_nodes) > 0:
albedo_link = links.new(albedo_nodes[0].outputs['Color'], albedo_mixRGB_nodes[0].inputs['Color2'])
for i in range(len(albedo_mixRGB_nodes)):
albedo_link = links.new(albedo_nodes[i+1].outputs['Color'], albedo_mixRGB_nodes[i].inputs['Color1'])
alpha_link = links.new(albedo_nodes[i].outputs['Alpha'], albedo_mixRGB_nodes[i].inputs['Fac'])
if i > 0:
mixRGB_link = links.new(albedo_mixRGB_nodes[i-1].outputs['Color'], albedo_mixRGB_nodes[i].inputs['Color2'])
mixRGB_link = links.new(albedo_mixRGB_nodes[-1].outputs['Color'], principled.inputs['Base Color'])
# Mask Nodes
# Mask Image Texture (R = Metallic, G = Glossines (Inverted Roughness), B = AO)
mask_nodes = []
mask_sepRGB_nodes = []
mask_invert_nodes = []
for i, textureID in enumerate(mask_maps.values()):
texture_file = "%s/%s.dds" % (texture_dir, textureID)
if os.path.exists(texture_file):
mask_image = nodes.new(type='ShaderNodeTexImage')
mask_nodes.append(mask_image)
mask_image.location = 0, ((len(albedo_maps)+1)*-60)-i*60
mask_image.image = bpy.data.images.load(texture_file)
mask_image.image.colorspace_settings.name = 'Non-Color'
mask_image.hide = True
if i > 0:
mask_image.label = "g_MaskMap" + str(i-1)
else:
mask_image.label = "g_MaskMap"
if 'Hair' not in material['Shader_Name']:
sepRGB_shader = nodes.new(type="ShaderNodeSeparateRGB")
mask_sepRGB_nodes.append(sepRGB_shader)
sepRGB_shader.location = 300, ((len(albedo_maps)+1)*-60)-i*60
sepRGB_shader.hide = True
invert_shader = nodes.new(type="ShaderNodeInvert")
mask_invert_nodes.append(invert_shader)
invert_shader.location = 600, ((len(albedo_maps)+1)*-60)-i*60
invert_shader.hide = True
#Mask Links
if len(mask_nodes) > 0:
if 'Hair' not in material['Shader_Name']:
mask_link = links.new(mask_nodes[0].outputs['Color'], mask_sepRGB_nodes[0].inputs['Image'])
r_link = links.new(mask_sepRGB_nodes[0].outputs['R'], principled.inputs['Metallic'])
g_link = links.new(mask_sepRGB_nodes[0].outputs['G'], mask_invert_nodes[0].inputs['Color'])
invert_link = links.new(mask_invert_nodes[0].outputs['Color'], principled.inputs['Roughness'])
else:
mask_link = links.new(mask_nodes[0].outputs['Color'], principled.inputs['Metallic'])
# Normal Nodes
normal_nodes = []
normal_mixRGB_nodes = []
for i, textureID in enumerate(normal_maps.values()):
texture_file = "%s/%s.dds" % (texture_dir, textureID)
if os.path.exists(texture_file):
normal_image = nodes.new(type='ShaderNodeTexImage')
normal_nodes.append(normal_image)
normal_image.location = 0, ((len(albedo_maps)+1)*-60) + ((len(mask_maps)+1)*-60)-i*60
normal_image.image = bpy.data.images.load(texture_file)
normal_image.image.colorspace_settings.name = 'Non-Color'
normal_image.hide = True
if i > 0:
normal_image.label = "g_NormalMap" + str(i-1)
else:
normal_image.label = "g_NormalMap"
if i > 0:
n_mixRGB_shader = nodes.new(type='ShaderNodeMixRGB')
normal_mixRGB_nodes.append(n_mixRGB_shader)
n_mixRGB_shader.location = 300, ((len(albedo_maps)+1)*-60) + ((len(mask_maps)+1)*-60)-(i-1)*60
n_mixRGB_shader.hide = True
if len(normal_nodes) > 0:
normalmap_shader = nodes.new(type='ShaderNodeNormalMap')
normalmap_shader.location = 600, ((len(albedo_maps)+1)*-60) + ((len(mask_maps)+1)*-60)-(i-1)*60
normalmap_link = links.new(normalmap_shader.outputs['Normal'], principled.inputs['Normal'])
normalmap_shader.hide = True
# Normal Links
if len(normal_nodes) == 1:
normal_link = links.new(normal_nodes[0].outputs['Color'], normalmap_shader.inputs['Color'])
else:
if len(normal_mixRGB_nodes) > 0:
normal_link = links.new(normal_nodes[0].outputs['Color'], normal_mixRGB_nodes[0].inputs['Color2'])
for i in range(len(normal_mixRGB_nodes)):
normal_link = links.new(normal_nodes[i+1].outputs['Color'], normal_mixRGB_nodes[i].inputs['Color1'])
if i < len(albedo_nodes):
n_alpha_link = links.new(albedo_nodes[i].outputs['Alpha'], normal_mixRGB_nodes[i].inputs['Fac'])
if i > 0:
n_mixRGB_link = links.new(normal_mixRGB_nodes[i-1].outputs['Color'], normal_mixRGB_nodes[i].inputs['Color2'])
mixRGB_link = links.new(normal_mixRGB_nodes[-1].outputs['Color'], normalmap_shader.inputs['Color'])
return material
def add_material_to_mesh(mesh, materials , uvs):
for material in materials:
#print('linking material %s to mesh object %s' % (material.name, mesh.name))
mesh.data.materials.append(material)
bpy.context.view_layer.objects.active = mesh
bpy.ops.object.mode_set(mode="EDIT")
bm = bmesh.from_edit_mesh(mesh.data)
uv_layer = bm.loops.layers.uv.verify()
#bm.faces.layers.tex.verify()
for face in bm.faces:
face.material_index = 0
for l in face.loops:
luv = l[uv_layer]
ind = l.vert.index
luv.uv = Vector(uvs[0][ind])
for i in range (1, 5):
if len(uvs[i]) > 0:
new_uv_layer = bm.loops.layers.uv.new("UVMap" + str(i + 1))
for face in bm.faces:
face.material_index = 0
for l in face.loops:
luv = l[new_uv_layer]
ind = l.vert.index
luv.uv = Vector(uvs[i][ind])
bpy.ops.object.mode_set(mode='OBJECT')
mesh.select_set(True)
bpy.ops.object.shade_smooth()
#mesh.hide = True
mesh.select_set(False)
def format_wmb_mesh(wmb, collection_name):
meshes = []
uvMaps = [[], [], [], [], []]
usedVerticeIndexArrays = []
mesh_array = wmb.meshArray
#each vertexgroup -> each lod -> each group -> mesh
for vertexGroupIndex in range(wmb.wmb3_header.vertexGroupCount):
vertex_flags = wmb.vertexGroupArray[vertexGroupIndex].vertexFlags
if vertex_flags in [0]:
uv = [(vertex.textureU, 1 - vertex.textureV) for vertex in wmb.vertexGroupArray[vertexGroupIndex].vertexArray]
uvMaps[0].append(uv)
uv = [(vertex.textureU2, 1 - vertex.textureV2) for vertex in wmb.vertexGroupArray[vertexGroupIndex].vertexArray]
uvMaps[1].append(uv)
uvMaps[2].append(None)
uvMaps[3].append(None)
uvMaps[4].append(None)
if vertex_flags in [1, 4]:
uv = [(vertex.textureU, 1 - vertex.textureV) for vertex in wmb.vertexGroupArray[vertexGroupIndex].vertexArray]
uvMaps[0].append(uv)
uv = [(vertex.textureU2, 1 - vertex.textureV2) for vertex in wmb.vertexGroupArray[vertexGroupIndex].vertexArray]
uvMaps[1].append(uv)
uvMaps[2].append(None)
uvMaps[3].append(None)
uvMaps[4].append(None)
if vertex_flags in [5]:
uv = [(vertex.textureU, 1 - vertex.textureV) for vertex in wmb.vertexGroupArray[vertexGroupIndex].vertexArray]
uvMaps[0].append(uv)
uv = [(vertex.textureU2, 1 - vertex.textureV2) for vertex in wmb.vertexGroupArray[vertexGroupIndex].vertexArray]
uvMaps[1].append(uv)
uv = [(vertexExData.textureU3, 1 - vertexExData.textureV3) for vertexExData in wmb.vertexGroupArray[vertexGroupIndex].vertexesExDataArray]
uvMaps[2].append(uv)
uvMaps[3].append(None)
uvMaps[4].append(None)
if vertex_flags in [7, 10]:
uv = [(vertex.textureU, 1 - vertex.textureV) for vertex in wmb.vertexGroupArray[vertexGroupIndex].vertexArray]
uvMaps[0].append(uv)
uv = [(vertexExData.textureU2, 1 - vertexExData.textureV2) for vertexExData in wmb.vertexGroupArray[vertexGroupIndex].vertexesExDataArray]
uvMaps[1].append(uv)
uvMaps[2].append(None)
uvMaps[3].append(None)
uvMaps[4].append(None)
if vertex_flags in [11]:
uv = [(vertex.textureU, 1 - vertex.textureV) for vertex in wmb.vertexGroupArray[vertexGroupIndex].vertexArray]
uvMaps[0].append(uv)
uv = [(vertexExData.textureU2, 1 - vertexExData.textureV2) for vertexExData in wmb.vertexGroupArray[vertexGroupIndex].vertexesExDataArray]
uvMaps[1].append(uv)
uv = [(vertexExData.textureU3, 1 - vertexExData.textureV3) for vertexExData in wmb.vertexGroupArray[vertexGroupIndex].vertexesExDataArray]
uvMaps[2].append(uv)
uvMaps[3].append(None)
uvMaps[4].append(None)
if vertex_flags in [12]:
uv = [(vertex.textureU, 1 - vertex.textureV) for vertex in wmb.vertexGroupArray[vertexGroupIndex].vertexArray]
uvMaps[0].append(uv)
uv = [(vertex.textureU2, 1 - vertex.textureV2) for vertex in wmb.vertexGroupArray[vertexGroupIndex].vertexArray]
uvMaps[1].append(uv)
uv = [(vertexExData.textureU3, 1 - vertexExData.textureV3) for vertexExData in wmb.vertexGroupArray[vertexGroupIndex].vertexesExDataArray]
uvMaps[2].append(uv)
uv = [(vertexExData.textureU4, 1 - vertexExData.textureV4) for vertexExData in wmb.vertexGroupArray[vertexGroupIndex].vertexesExDataArray]
uvMaps[3].append(uv)
uv = [(vertexExData.textureU5, 1 - vertexExData.textureV5) for vertexExData in wmb.vertexGroupArray[vertexGroupIndex].vertexesExDataArray]
uvMaps[4].append(uv)
if vertex_flags in [14]:
uv = [(vertex.textureU, 1 - vertex.textureV) for vertex in wmb.vertexGroupArray[vertexGroupIndex].vertexArray]
uvMaps[0].append(uv)
uv = [(vertex.textureU2, 1 - vertex.textureV2) for vertex in wmb.vertexGroupArray[vertexGroupIndex].vertexArray]
uvMaps[1].append(uv)
uv = [(vertexExData.textureU3, 1 - vertexExData.textureV3) for vertexExData in wmb.vertexGroupArray[vertexGroupIndex].vertexesExDataArray]
uvMaps[2].append(uv)
uv = [(vertexExData.textureU4, 1 - vertexExData.textureV4) for vertexExData in wmb.vertexGroupArray[vertexGroupIndex].vertexesExDataArray]
uvMaps[3].append(uv)
uvMaps[4].append(None)
for meshGroupInfoArrayIndex in range(len(wmb.meshGroupInfoArray)):
meshGroupInfo = wmb.meshGroupInfoArray[meshGroupInfoArrayIndex]
groupedMeshArray = meshGroupInfo.groupedMeshArray
mesh_start = meshGroupInfo.meshStart
LOD_name = meshGroupInfo.meshGroupInfoname
LOD_level = meshGroupInfo.lodLevel
for meshGroupIndex in range(wmb.wmb3_header.meshGroupCount):
meshIndexArray = []
for groupedMeshIndex in range(len(groupedMeshArray)):
if groupedMeshArray[groupedMeshIndex].meshGroupIndex == meshGroupIndex:
meshIndexArray.append([mesh_start + groupedMeshIndex, groupedMeshArray[groupedMeshIndex].colTreeNodeIndex, groupedMeshArray[groupedMeshIndex].unknownWorldDataIndex])
meshGroup = wmb.meshGroupArray[meshGroupIndex]
for meshArrayData in (meshIndexArray):
meshArrayIndex = meshArrayData[0]
colTreeNodeIndex = meshArrayData[1]
unknownWorldDataIndex = meshArrayData[2]
meshVertexGroupIndex = wmb.meshArray[meshArrayIndex].vertexGroupIndex
if meshVertexGroupIndex == vertexGroupIndex:
meshName = "%d-%s-%d"%(meshArrayIndex, meshGroup.meshGroupname, vertexGroupIndex)
meshInfo = wmb.clear_unused_vertex(meshArrayIndex, meshVertexGroupIndex)
vertices = meshInfo[0]
faces = meshInfo[1]
usedVerticeIndexArray = meshInfo[2]
boneWeightInfoArray = meshInfo[3]
vertex_colors = meshInfo[4]
usedVerticeIndexArrays.append(usedVerticeIndexArray)
flag = False
has_bone = wmb.hasBone
boneSetIndex = wmb.meshArray[meshArrayIndex].bonesetIndex
if boneSetIndex == 0xffffffff:
boneSetIndex = -1
boundingBox = meshGroup.boundingBox
obj = construct_mesh([meshName, vertices, faces, has_bone, boneWeightInfoArray, boneSetIndex, meshGroupIndex, vertex_colors, LOD_name, LOD_level, colTreeNodeIndex, unknownWorldDataIndex, boundingBox], collection_name)
meshes.append(obj)
return meshes, uvMaps, usedVerticeIndexArrays
def get_wmb_material(wmb, texture_dir):
materials = []
if wmb.wta:
if hasattr(wmb, 'materialArray'):
for materialIndex in range(len(wmb.materialArray)):
material = wmb.materialArray[materialIndex]
material_name = material.materialName
shader_name = material.effectName
technique_name = material.techniqueName
uniforms = material.uniformArray
textures = material.textureArray
parameterGroups = material.parameterGroups
for textureIndex in range(wmb.wta.textureCount): # for key in textures.keys():
#identifier = textures[key]
identifier = wmb.wta.wtaTextureIdentifier[textureIndex]
try:
texture_stream = wmb.wta.getTextureByIdentifier(identifier,wmb.wtp_fp)
if texture_stream:
if not os.path.exists("%s\%s.dds" %(texture_dir, identifier)):
create_dir(texture_dir)
texture_fp = open("%s\%s.dds" %(texture_dir, identifier), "wb")
print('[+] dumping %s.dds'% identifier)
texture_fp.write(texture_stream)
texture_fp.close()
except:
continue
materials.append([material_name,textures,uniforms,shader_name,technique_name,parameterGroups])
else:
texture_dir = texture_dir.replace('.dat','.dtt')
for textureIndex in range(wmb.wta.textureCount):
print(textureIndex)
identifier = wmb.wta.wtaTextureIdentifier[textureIndex]
texture_stream = wmb.wta.getTextureByIdentifier(identifier,wmb.wtp_fp)
if texture_stream:
if not os.path.exists("%s\%s.dds" %(texture_dir, identifier)):
create_dir(texture_dir)
texture_fp = open("%s\%s.dds" %(texture_dir, identifier), "wb")
print('[+] dumping %s.dds'% identifier)
texture_fp.write(texture_stream)
texture_fp.close()
else:
print('Missing .wta')
show_message("Error: Could not open .wta file, textures not imported. Is it missing? (Maybe DAT not extracted?)", 'Could Not Open .wta File', 'ERROR')
for materialIndex in range(len(wmb.materialArray)):
material = wmb.materialArray[materialIndex]
material_name = material.materialName
shader_name = material.effectName
technique_name = material.techniqueName
uniforms = material.uniformArray
textures = material.textureArray
parameterGroups = material.parameterGroups
materials.append([material_name,textures,uniforms,shader_name,technique_name,parameterGroups])
return materials
def import_colTreeNodes(wmb, collection):
colTreeNodesDict = {}
#collision_col = bpy.data.collections.new("CollisionNodes")
#collection.children.link(collision_col)
colTreeNodesCollection = bpy.data.collections.get("wmb_colTreeNodes")
if not colTreeNodesCollection:
colTreeNodesCollection = bpy.data.collections.new("wmb_colTreeNodes")
collection.children.link(colTreeNodesCollection)
bpy.context.view_layer.active_layer_collection.children["WMB"].children[collection.name].children["wmb_colTreeNodes"].hide_viewport = True
rootNode = bpy.data.objects.new("Root_wmb", None)
rootNode.hide_viewport = True
colTreeNodesCollection.objects.link(rootNode)
rootNode.rotation_euler = (math.radians(90),0,0)
for nodeIdx, node in enumerate(wmb.colTreeNodes):
colTreeNodeName = 'colTreeNode' + str(nodeIdx)
objName = str(nodeIdx) + "_" + str(node.left) + "_" + str(node.right) + "_wmb"
obj = bpy.data.objects.new(objName, None)
colTreeNodesCollection.objects.link(obj)
obj.parent = rootNode
obj.empty_display_type = 'CUBE'
obj.location = node.p1
obj.scale = node.p2
meshIndices = []
for bObj in (x for x in bpy.data.collections['WMB'].all_objects if x.type == "MESH"):
if bObj["colTreeNodeIndex"] == nodeIdx:
idx = int(bObj.name.split("-")[0])
meshIndices.append(idx)
if len(meshIndices) > 0:
obj["meshIndices"] = meshIndices
colTreeNode = [node.p1[0], node.p1[1], node.p1[2], node.p2[0], node.p2[1], node.p2[2], node.left, node.right]
colTreeNodesDict[colTreeNodeName] = colTreeNode
bpy.context.scene['colTreeNodes'] = colTreeNodesDict
def import_unknowWorldDataArray(wmb):
unknownWorldDataDict = {}
for index, unknownWorldData in enumerate(wmb.unknownWorldDataArray):
unknownWorldDataName = 'unknownWorldData' + str(index)
unknownWorldDataDict[unknownWorldDataName] = unknownWorldData.unknownWorldData
bpy.context.scene['unknownWorldData'] = unknownWorldDataDict
def main(only_extract = False, wmb_file = os.path.split(os.path.realpath(__file__))[0] + '\\test\\pl0000.dtt\\pl0000.wmb'):
#reset_blend()
wmb = WMB3(wmb_file)
wmbname = wmb_file.split('\\')[-1]
if only_extract:
texture_dir = wmb_file.replace(wmbname, '\\textures\\')
wmb_materials = get_wmb_material(wmb, texture_dir)
print('Extraction finished. ;)')
return {'FINISHED'}
wmbCollection = bpy.data.collections.get("WMB")
if not wmbCollection:
wmbCollection = bpy.data.collections.new("WMB")
bpy.context.scene.collection.children.link(wmbCollection)
collection_name = wmbname[:-4]
col = bpy.data.collections.new(collection_name)
wmbCollection.children.link(col)
#bpy.context.view_layer.active_layer_collection = bpy.context.view_layer.layer_collection.children[-1]
texture_dir = wmb_file.replace(wmbname, '\\textures\\')
if wmb.hasBone:
boneArray = [[bone.boneIndex, "bone%d"%bone.boneIndex, bone.parentIndex,"bone%d"%bone.parentIndex, bone.world_position, bone.world_rotation, bone.boneNumber, bone.local_position, bone.local_rotation, bone.world_rotation, bone.world_position_tpose] for bone in wmb.boneArray]
armature_no_wmb = wmbname.replace('.wmb','')
armature_name_split = armature_no_wmb.split('/')
armature_name = armature_name_split[len(armature_name_split)-1] # THIS IS SPAGHETT I KNOW. I WAS TIRED
construct_armature(armature_name, boneArray, wmb.firstLevel, wmb.secondLevel, wmb.thirdLevel, wmb.boneMap, wmb.boneSetArray, collection_name)
meshes, uvs, usedVerticeIndexArrays = format_wmb_mesh(wmb, collection_name)
wmb_materials = get_wmb_material(wmb, texture_dir)
materials = []
for materialIndex in range(len(wmb_materials)):
material = wmb_materials[materialIndex]
materials.append(consturct_materials(texture_dir, material))
print('Linking materials to objects...')
for meshGroupInfo in wmb.meshGroupInfoArray:
for Index in range(len(meshGroupInfo.groupedMeshArray)):
mesh_start = meshGroupInfo.meshStart
meshIndex = int(meshes[Index + mesh_start].name.split('-')[0])
materialIndex = meshGroupInfo.groupedMeshArray[meshIndex - mesh_start].materialIndex
groupIndex = int(meshes[Index + mesh_start].name.split('-')[2])
uvMaps = [[], [], [], [], []]
for i in range(len(usedVerticeIndexArrays[Index + mesh_start])):
VertexIndex = usedVerticeIndexArrays[Index + mesh_start][i]
for k in range(5):
if uvs[k][groupIndex] != None:
uvMaps[k].append( uvs[k][groupIndex][VertexIndex])
if len(materials) > 0:
add_material_to_mesh(meshes[Index + mesh_start], [materials[materialIndex]], uvMaps)
if wmb.hasBone:
amt = bpy.data.objects.get(armature_name)
if wmb.hasBone:
for mesh in meshes:
set_partent(amt,mesh)
if wmb.hasColTreeNodes:
import_colTreeNodes(wmb, col)
if wmb.hasUnknownWorldData:
import_unknowWorldDataArray(wmb)
print('Importing finished. ;)')
return {'FINISHED'}
if __name__ == '__main__':
main()
+46
View File
@@ -0,0 +1,46 @@
import os
import sys
from .util import *
class WTA(object):
def __init__(self, wta_fp):
super(WTA, self).__init__()
self.magicNumber = wta_fp.read(4)
if self.magicNumber == b'WTB\x00':
self.unknown04 = to_uint(wta_fp.read(4))
self.textureCount = to_uint(wta_fp.read(4))
self.textureOffsetArrayOffset = to_uint(wta_fp.read(4))
self.textureSizeArrayOffset = to_uint(wta_fp.read(4))
self.unknownArrayOffset1 = to_uint(wta_fp.read(4))
self.textureIdentifierArrayOffset = to_uint(wta_fp.read(4))
self.unknownArrayOffset2 = to_uint(wta_fp.read(4))
self.wtaTextureOffset = [0] * self.textureCount
self.wtaTextureSize = [0] * self.textureCount
self.wtaTextureIdentifier = [0] * self.textureCount
self.unknownArray1 = [0] * self.textureCount
self.unknownArray2 = []
for i in range(self.textureCount):
wta_fp.seek(self.textureOffsetArrayOffset + i * 4)
self.wtaTextureOffset[i] = to_uint(wta_fp.read(4))
wta_fp.seek(self.textureSizeArrayOffset + i * 4)
self.wtaTextureSize[i] = to_uint(wta_fp.read(4))
wta_fp.seek(self.textureIdentifierArrayOffset + i * 4)
self.wtaTextureIdentifier[i] = "%08x"%to_uint(wta_fp.read(4))
wta_fp.seek(self.unknownArrayOffset1 + i * 4)
self.unknownArray1[i] = "%08x"%to_uint(wta_fp.read(4))
wta_fp.seek(self.unknownArrayOffset2 )
unknownval = (wta_fp.read(4))
while unknownval:
self.unknownArray2.append(to_uint(unknownval))
unknownval = (wta_fp.read(4))
self.pointer2 = hex(wta_fp.tell())
def getTextureByIndex(self, texture_index, texture_fp):
texture_fp.seek(self.wtaTextureOffset[texture_index])
texture = texture_fp.read(self.wtaTextureSize[texture_index])
return texture
def getTextureByIdentifier(self, textureIdentifier, texture_fp):
for index in range(self.textureCount):
if self.wtaTextureIdentifier[index] == textureIdentifier:
return self.getTextureByIndex(index,texture_fp)
return False