mirror of
https://github.com/YorhaX2P/NieR2Blender2NieR-PS4.git
synced 2026-08-26 19:43:33 +00:00
Merge branch 'n2b'
This commit is contained in:
@@ -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
@@ -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
@@ -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 |
Binary file not shown.
|
After Width: | Height: | Size: 95 KiB |
@@ -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
@@ -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
|
||||
@@ -0,0 +1,45 @@
|
||||

|
||||
<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>
|
||||
|
||||

|
||||
|
||||
* 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
@@ -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
@@ -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()))
|
||||
@@ -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
@@ -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()
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user