mirror of
https://github.com/YorhaX2P/NieR2Blender2NieR-PS4.git
synced 2026-08-26 19:43:33 +00:00
Added 2. LAY visualization: Instead of bounding box, now actual mesh instances, from pre-imported library
This commit is contained in:
+2
-6
@@ -52,13 +52,9 @@ class CreateLayBoundingBox(bpy.types.Operator):
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
from .lay.importer.lay_importer import getModelBoundingBox, createBoundingBoxObject
|
||||
from .lay.importer.lay_importer import getModelBoundingBox, createVisualizationObject
|
||||
for obj in bpy.context.selected_objects:
|
||||
boundingBox = getModelBoundingBox(obj.name.split("_")[0], ADDON_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)
|
||||
createVisualizationObject(obj, obj.name + "-BoundingBox", obj.users_collection[0])
|
||||
return {'FINISHED'}
|
||||
|
||||
def menu_func_import(self, context):
|
||||
|
||||
@@ -7,7 +7,6 @@ from bpy_extras.io_utils import ImportHelper
|
||||
from ...col.exporter.col_ui_manager import enableCollisionTools
|
||||
from ...utils.visibilitySwitcher import enableVisibilitySelector
|
||||
from ...utils.util import setExportFieldsFromImportFile
|
||||
from ...consts import ADDON_NAME
|
||||
|
||||
|
||||
def importDat(only_extract, filepath):
|
||||
@@ -49,7 +48,7 @@ def importDat(only_extract, filepath):
|
||||
lay_filepath = os.path.join(extract_dir, tailless_tail + '.dat', 'Layout.lay')
|
||||
if os.path.isfile(lay_filepath):
|
||||
from ...lay.importer import lay_importer
|
||||
lay_importer.main(lay_filepath, ADDON_NAME)
|
||||
lay_importer.main(lay_filepath)
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
@@ -121,7 +120,7 @@ class ImportNierDat(bpy.types.Operator, ImportHelper):
|
||||
lay_filepath = os.path.join(extract_dir, tailless_tail + '.dat', 'Layout.lay')
|
||||
if os.path.isfile(lay_filepath):
|
||||
from ...lay.importer import lay_importer
|
||||
lay_importer.main(lay_filepath, ADDON_NAME)
|
||||
lay_importer.main(lay_filepath)
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ from bpy.props import StringProperty
|
||||
from bpy_extras.io_utils import ImportHelper
|
||||
|
||||
from ...utils.util import setExportFieldsFromImportFile
|
||||
from ...consts import ADDON_NAME
|
||||
|
||||
|
||||
class ImportNierLay(bpy.types.Operator, ImportHelper):
|
||||
@@ -19,4 +18,4 @@ class ImportNierLay(bpy.types.Operator, ImportHelper):
|
||||
|
||||
setExportFieldsFromImportFile(self.filepath)
|
||||
|
||||
return lay_importer.main(self.filepath, ADDON_NAME)
|
||||
return lay_importer.main(self.filepath)
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
from __future__ import annotations
|
||||
import math
|
||||
from time import time
|
||||
|
||||
from ...utils.ioUtils import to_string, read_float, read_uint32
|
||||
from .lay import Lay
|
||||
from ...utils.util import *
|
||||
|
||||
|
||||
def main(layFilePath, addonName):
|
||||
def main(layFilePath):
|
||||
t1 = time()
|
||||
|
||||
with open(layFilePath, "rb") as layFile:
|
||||
print("Parsing Lay file...", layFilePath)
|
||||
lay = Lay(layFile)
|
||||
@@ -42,18 +46,18 @@ def main(layFilePath, addonName):
|
||||
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 = createLayObject(assetName, layAssetsCollection, assetRootNode, asset.position, asset.rotation, asset.scale)
|
||||
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)
|
||||
createLayObject(instanceName, layInstancesCollection, instanceRootNode, instance.position, instance.rotation, instance.scale)
|
||||
|
||||
print('Importing finished. ;)')
|
||||
tD = time() - t1
|
||||
print(f"Importing finished in {tD:.1}s ;)")
|
||||
return {'FINISHED'}
|
||||
|
||||
def createLayObject(name, collection, parent, pos, rot, scale, boundingBox):
|
||||
def createLayObject(name, collection, parent, pos, rot, scale):
|
||||
obj = bpy.data.objects.new(name, None)
|
||||
collection.objects.link(obj)
|
||||
obj.parent = parent
|
||||
@@ -66,18 +70,24 @@ def createLayObject(name, collection, parent, pos, rot, scale, boundingBox):
|
||||
|
||||
obj.show_axis = True
|
||||
|
||||
if boundingBox is not None:
|
||||
createBoundingBoxObject(obj, name + "-BoundingBox", collection, boundingBox)
|
||||
createVisualizationObject(obj, name + "-BoundingBox", collection)
|
||||
|
||||
return obj
|
||||
|
||||
|
||||
|
||||
def createBoundingBoxObject(obj: bpy.types.Object, name, collection, boundingBox):
|
||||
boundingBoxObj = bpy.data.objects.new(name, None)
|
||||
collection.objects.link(boundingBoxObj)
|
||||
def createVisualizationObject(obj: bpy.types.Object, name, collection):
|
||||
for child in obj.children:
|
||||
bpy.data.objects.remove(child, do_unlink=True)
|
||||
linkAssetModel(obj, obj.name[:6], collection) or createBoundingBoxObject(obj, name, collection)
|
||||
|
||||
def createBoundingBoxObject(obj: bpy.types.Object, name, collection):
|
||||
boundingBox = getModelBoundingBox(obj.name[:6])
|
||||
if boundingBox is None:
|
||||
return None
|
||||
|
||||
boundingBoxObj = bpy.data.objects.new(name, None)
|
||||
collection.objects.link(boundingBoxObj)
|
||||
boundingBoxObj.parent = obj
|
||||
boundingBoxObj.empty_display_type = 'CUBE'
|
||||
|
||||
@@ -86,7 +96,7 @@ def createBoundingBoxObject(obj: bpy.types.Object, name, collection, boundingBox
|
||||
|
||||
boundingBoxObj.hide_select = True
|
||||
|
||||
def searchDirForModel(dir: str, modelName: str, depth = 0) -> str:
|
||||
def searchDirForModel(dir: str, modelName: str, depth = 0) -> str | None:
|
||||
if depth > 0 and dir.endswith("nier2blender_extracted") or depth > 6:
|
||||
return None
|
||||
|
||||
@@ -101,7 +111,7 @@ def searchDirForModel(dir: str, modelName: str, depth = 0) -> str:
|
||||
|
||||
return None
|
||||
|
||||
def getModelBoundingBox(modelName, addonName):
|
||||
def getModelBoundingBox(modelName):
|
||||
searchDirs = getPreferences().assetDirs
|
||||
searchDirs = [dir.directory for dir in searchDirs]
|
||||
|
||||
@@ -119,7 +129,7 @@ def getModelBoundingBox(modelName, addonName):
|
||||
return None
|
||||
|
||||
with open(filePath, "rb") as modelDTTFile:
|
||||
id = modelDTTFile.read(4)
|
||||
modelDTTFile.read(4) # id
|
||||
numFiles = read_uint32(modelDTTFile)
|
||||
fileOffsetsOffset = read_uint32(modelDTTFile)
|
||||
fileExtensionsOffset = read_uint32(modelDTTFile)
|
||||
@@ -137,5 +147,45 @@ def getModelBoundingBox(modelName, addonName):
|
||||
for i, ext in enumerate(fileExtensions):
|
||||
if ext == "wmb":
|
||||
modelDTTFile.seek(fileOffsets[i] + 16)
|
||||
boundingBox = [read_float(modelDTTFile) for val in range(6)]
|
||||
boundingBox = [read_float(modelDTTFile) for _ in range(6)]
|
||||
return boundingBox
|
||||
|
||||
def linkAssetModel(obj: bpy.types.Object, modelName: str, collection: bpy.types.Collection) -> bpy.types.Object | None:
|
||||
global linked
|
||||
linkedObjName = f"{modelName}-linked-model"
|
||||
|
||||
# link file for first object
|
||||
if modelName not in bpy.data.collections:
|
||||
searchDirs = getPreferences().assetBlendDirs
|
||||
searchDirs = [dir.directory for dir in searchDirs]
|
||||
|
||||
filePath = ""
|
||||
for pathName in searchDirs:
|
||||
if not os.path.isdir(pathName):
|
||||
continue
|
||||
|
||||
modelPath = searchDirForModel(pathName, modelName + ".blend")
|
||||
if modelPath is not None:
|
||||
filePath = modelPath
|
||||
break
|
||||
|
||||
if not filePath:
|
||||
return False
|
||||
|
||||
if f"{modelName}.blend" not in bpy.data.libraries:
|
||||
with bpy.data.libraries.load(filepath = filePath, link = True, relative = True) as (data_from, data_to):
|
||||
data_to.collections = [modelName]
|
||||
if modelName in bpy.data.objects and bpy.data.objects[modelName].instance_type == "COLLECTION":
|
||||
bpy.data.objects.remove(bpy.data.objects[modelName], do_unlink=True)
|
||||
|
||||
# create instance object
|
||||
newObj = bpy.data.objects.new(linkedObjName, None)
|
||||
newObj.instance_type = "COLLECTION"
|
||||
newObj.instance_collection = bpy.data.collections[modelName]
|
||||
collection.objects.link(newObj)
|
||||
newObj.parent = obj
|
||||
newObj.name = linkedObjName
|
||||
newObj.rotation_euler[0] = -math.radians(90)
|
||||
newObj.hide_select = True
|
||||
|
||||
return newObj
|
||||
|
||||
+37
-4
@@ -16,9 +16,17 @@ class SelectDirectory(bpy.types.Operator, ImportHelper):
|
||||
filename_ext = ""
|
||||
dirpath : bpy.props.StringProperty(name = "", description="Choose directory:", subtype='DIR_PATH')
|
||||
|
||||
settingsType: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
directory = os.path.dirname(self.filepath)
|
||||
newDir = getPreferences().assetDirs.add()
|
||||
if self.settingsType == 0:
|
||||
newDir = getPreferences().assetDirs.add()
|
||||
elif self.settingsType == 1:
|
||||
newDir = getPreferences().assetBlendDirs.add()
|
||||
else:
|
||||
print("Invalid settingsType")
|
||||
return {'CANCELLED'}
|
||||
newDir.directory = directory
|
||||
|
||||
return {'FINISHED'}
|
||||
@@ -28,10 +36,17 @@ class RemoveDirectory(bpy.types.Operator):
|
||||
bl_idname = "n2b.remove_asset_dir"
|
||||
bl_label = "Remove Directory"
|
||||
|
||||
settingsType: bpy.props.IntProperty()
|
||||
index : bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
getPreferences().assetDirs.remove(self.index)
|
||||
if self.settingsType == 0:
|
||||
getPreferences().assetDirs.remove(self.index)
|
||||
elif self.settingsType == 1:
|
||||
getPreferences().assetBlendDirs.remove(self.index)
|
||||
else:
|
||||
print("Invalid settingsType")
|
||||
return {'CANCELLED'}
|
||||
return {'FINISHED'}
|
||||
|
||||
ArmatureDisplayTypeEnum = [
|
||||
@@ -46,6 +61,7 @@ ArmatureDisplayTypeEnum = [
|
||||
class N2B2NPreferences(bpy.types.AddonPreferences):
|
||||
bl_idname = ADDON_NAME
|
||||
assetDirs: bpy.props.CollectionProperty(type=DirectoryProperty)
|
||||
assetBlendDirs: bpy.props.CollectionProperty(type=DirectoryProperty)
|
||||
armatureDefaultDisplayType: bpy.props.EnumProperty(name="Armature Display Type", items=ArmatureDisplayTypeEnum, default="DEFAULT")
|
||||
armatureDefaultInFront: bpy.props.BoolProperty(name="Armature Default In Front", default=False)
|
||||
|
||||
@@ -61,13 +77,30 @@ class N2B2NPreferences(bpy.types.AddonPreferences):
|
||||
row.label(text="In Front:")
|
||||
row.prop(self, "armatureDefaultInFront", text="")
|
||||
|
||||
# asset dirs selection
|
||||
box = layout.box()
|
||||
drawMultilineLabel(context, "Assign extracted cpk directories below if you wish to enable bounding box visualization with layout import", box)
|
||||
for i, assetDir in enumerate(self.assetDirs):
|
||||
row = box.row(align=True)
|
||||
row.prop(self.assetDirs[i], "directory", text="")
|
||||
row.operator(RemoveDirectory.bl_idname, text="", icon="X").index = i
|
||||
box.operator(SelectDirectory.bl_idname, text="Add Directory", icon="FILE_FOLDER")
|
||||
remOp = row.operator(RemoveDirectory.bl_idname, text="", icon="X")
|
||||
remOp.index = i
|
||||
remOp.settingsType = 0
|
||||
addOp = box.operator(SelectDirectory.bl_idname, text="Add Directory", icon="FILE_FOLDER")
|
||||
addOp.settingsType = 0
|
||||
|
||||
# asset blend dirs selection
|
||||
box = layout.box()
|
||||
drawMultilineLabel(context, "Assign downloaded blend directories below if you wish to enable full model visualization with layout import", box)
|
||||
for i, assetDir in enumerate(self.assetBlendDirs):
|
||||
row = box.row(align=True)
|
||||
row.prop(self.assetBlendDirs[i], "directory", text="")
|
||||
remOp = row.operator(RemoveDirectory.bl_idname, text="", icon="X")
|
||||
remOp.index = i
|
||||
remOp.settingsType = 1
|
||||
addOp = box.operator(SelectDirectory.bl_idname, text="Add Directory", icon="FILE_FOLDER")
|
||||
addOp.settingsType = 1
|
||||
|
||||
|
||||
def register():
|
||||
bpy.utils.register_class(DirectoryProperty)
|
||||
|
||||
Reference in New Issue
Block a user