diff --git a/build.gradle b/build.gradle index f933065..a99589e 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ plugins { } group 'com.tb24' -version '0.2.2' +version '0.3.0' sourceCompatibility = 1.8 @@ -47,6 +47,7 @@ dependencies { implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.8' implementation 'com.google.code.gson:gson:2.8.6' // implementation 'com.mojang:brigadier:1.0.17' + implementation 'com.squareup.okhttp3:okhttp:3.14.9' implementation 'me.fungames:JFortniteParse:+' // :3.0.2' implementation 'org.slf4j:slf4j-api:1.7.30' testImplementation 'junit:junit:4.12' diff --git a/src/main/java/com/tb24/blenderumap/AssetUtils.kt b/src/main/java/com/tb24/blenderumap/AssetUtils.kt index 5f08a8a..d5f265c 100644 --- a/src/main/java/com/tb24/blenderumap/AssetUtils.kt +++ b/src/main/java/com/tb24/blenderumap/AssetUtils.kt @@ -1,9 +1,9 @@ package com.tb24.blenderumap -import me.fungames.jfortniteparse.ue4.FGuid import me.fungames.jfortniteparse.ue4.assets.exports.UExport import me.fungames.jfortniteparse.ue4.assets.exports.UObject import me.fungames.jfortniteparse.ue4.assets.objects.FPropertyTag +import me.fungames.jfortniteparse.ue4.objects.core.misc.FGuid import java.util.* fun getProp(properties: List, name: String, clazz: Class): T? { @@ -33,7 +33,7 @@ fun getProps(properties: List, name: String, clazz: Class): fun UExport.getProp(name: String, clazz: Class) = baseObject.getProp(name, clazz) fun UObject.getProp(name: String, clazz: Class) = getProp(properties, name, clazz) -inline operator fun UExport.get(name: String): T? = getProp(name, T::class.java) -inline operator fun UObject.get(name: String): T? = getProp(name, T::class.java) +inline fun UExport.get(name: String): T? = getProp(name, T::class.java) +inline fun UObject.get(name: String): T? = getProp(name, T::class.java) -fun FGuid?.asString() = if (this == null) null else "%08x%08x%08x%08x".format(part1, part2, part3, part4) +fun FGuid?.asString() = if (this == null) null else "%08x%08x%08x%08x".format(part1.toInt(), part2.toInt(), part3.toInt(), part4.toInt()) diff --git a/src/main/java/com/tb24/blenderumap/FetchFortniteAesKey.java b/src/main/java/com/tb24/blenderumap/FetchFortniteAesKey.java new file mode 100644 index 0000000..f3f421a --- /dev/null +++ b/src/main/java/com/tb24/blenderumap/FetchFortniteAesKey.java @@ -0,0 +1,63 @@ +package com.tb24.blenderumap; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.Reader; +import java.util.Map; + +import okhttp3.OkHttpClient; +import okhttp3.Request; + +public class FetchFortniteAesKey { + private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create(); + + public static void main(String[] args) { + try { + Reader reader = new OkHttpClient().newCall(new Request.Builder().url("https://benbotfn.tk/api/v1/aes").build()).execute().body().charStream(); + AesResponse response = GSON.fromJson(reader, AesResponse.class); + reader.close(); + File configFile = new File("config.json"); + FileReader fileReader = new FileReader(configFile); + JsonElement configTree = JsonParser.parseReader(fileReader); + fileReader.close(); + JsonArray keys = configTree.getAsJsonObject().getAsJsonArray("EncryptionKeys"); + + while (keys.size() > 0) { + keys.remove(0); + } + + JsonObject mainKey = new JsonObject(); + mainKey.addProperty("Guid", "00000000000000000000000000000000"); + mainKey.addProperty("Key", response.mainKey); + keys.add(mainKey); + + for (Map.Entry entry : response.dynamicKeys.entrySet()) { + JsonObject dynKey = new JsonObject(); + dynKey.addProperty("FileName", entry.getKey().substring(entry.getKey().lastIndexOf('/') + 1)); + dynKey.addProperty("Key", entry.getValue()); + keys.add(dynKey); + } + + FileWriter fileWriter = new FileWriter(configFile); + GSON.toJson(configTree, fileWriter); + fileWriter.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public static class AesResponse { + public String version; + public String mainKey; + public Map dynamicKeys; + } +} diff --git a/src/main/java/com/tb24/blenderumap/JWPSerializer.kt b/src/main/java/com/tb24/blenderumap/JWPSerializer.kt index 6ac5c57..6ff1aed 100644 --- a/src/main/java/com/tb24/blenderumap/JWPSerializer.kt +++ b/src/main/java/com/tb24/blenderumap/JWPSerializer.kt @@ -4,11 +4,18 @@ package com.tb24.blenderumap import com.google.gson.* -import me.fungames.jfortniteparse.ue4.FGuid import me.fungames.jfortniteparse.ue4.assets.exports.UDataTable import me.fungames.jfortniteparse.ue4.assets.exports.UExport import me.fungames.jfortniteparse.ue4.assets.objects.* -import me.fungames.jfortniteparse.ue4.assets.util.FName +import me.fungames.jfortniteparse.ue4.objects.core.i18n.FText +import me.fungames.jfortniteparse.ue4.objects.core.i18n.FTextHistory +import me.fungames.jfortniteparse.ue4.objects.core.math.FBox +import me.fungames.jfortniteparse.ue4.objects.core.math.FBox2D +import me.fungames.jfortniteparse.ue4.objects.core.misc.FGuid +import me.fungames.jfortniteparse.ue4.objects.gameplaytags.FGameplayTagContainer +import me.fungames.jfortniteparse.ue4.objects.uobject.FName +import me.fungames.jfortniteparse.ue4.objects.uobject.FPackageIndex +import me.fungames.jfortniteparse.ue4.objects.uobject.FSoftObjectPath import me.fungames.jfortniteparse.util.parseHexBinary import java.lang.reflect.Type import java.util.* @@ -60,14 +67,6 @@ object JWPSerializer { } }) .registerTypeAdapter(FGuid::class.java, GuidSerializer()) - .registerTypeAdapter(FLinearColor::class.java, JsonSerializer { src, typeOfSrc, context -> - JsonObject().apply { - addProperty("r", src.r) - addProperty("g", src.g) - addProperty("b", src.b) - addProperty("a", src.a) - } - }) .registerTypeAdapter(FName::class.java, JsonSerializer { src, typeOfSrc, context -> JsonPrimitive(src.text) }) @@ -94,21 +93,6 @@ object JWPSerializer { out }) - .registerTypeAdapter(FQuat::class.java, JsonSerializer { src, typeOfSrc, context -> - JsonObject().apply { - addProperty("x", src.x) - addProperty("y", src.y) - addProperty("z", src.z) - addProperty("w", src.w) - } - }) - .registerTypeAdapter(FRotator::class.java, JsonSerializer { src, typeOfSrc, context -> - JsonObject().apply { - addProperty("pitch", src.pitch) - addProperty("yaw", src.yaw) - addProperty("roll", src.roll) - } - }) .registerTypeAdapter(UScriptArray::class.java, JsonSerializer { src, typeOfSrc, context -> JsonArray().apply { src.contents.forEach { add(context.serialize(it)) } } }) @@ -147,24 +131,12 @@ object JWPSerializer { } } }) - .registerTypeAdapter(FVector::class.java, JsonSerializer { src, typeOfSrc, context -> - JsonObject().apply { - addProperty("x", src.x) - addProperty("y", src.y) - addProperty("z", src.z) - } - }) - .registerTypeAdapter(FVector2D::class.java, JsonSerializer { src, typeOfSrc, context -> - JsonObject().apply { - addProperty("x", src.x) - addProperty("y", src.y) - } - }) .create() private fun serializeProperties(obj: JsonObject, properties: List, context: JsonSerializationContext) { properties.forEach { obj.add(it.name.text + (if (it.arrayIndex != 0) "[${it.arrayIndex}]" else ""), context.serialize(it.tag)) +// obj.add(it.name.text + (if (it.arrayIndex != 0) "[${it.arrayIndex}]" else ""), context.serialize(it.prop)) } } diff --git a/src/main/java/com/tb24/blenderumap/Main.java b/src/main/java/com/tb24/blenderumap/Main.java index 1a995e0..b9c4d70 100644 --- a/src/main/java/com/tb24/blenderumap/Main.java +++ b/src/main/java/com/tb24/blenderumap/Main.java @@ -31,19 +31,19 @@ import me.fungames.jfortniteparse.converters.ue4.meshes.StaticMeshesKt; import me.fungames.jfortniteparse.converters.ue4.meshes.psk.ExportStaticMeshKt; import me.fungames.jfortniteparse.converters.ue4.textures.TexturesKt; -import me.fungames.jfortniteparse.ue4.FGuid; import me.fungames.jfortniteparse.ue4.assets.Package; import me.fungames.jfortniteparse.ue4.assets.exports.UExport; import me.fungames.jfortniteparse.ue4.assets.exports.UStaticMesh; -import me.fungames.jfortniteparse.ue4.assets.exports.tex.UTexture; -import me.fungames.jfortniteparse.ue4.assets.objects.FObjectExport; -import me.fungames.jfortniteparse.ue4.assets.objects.FPackageIndex; -import me.fungames.jfortniteparse.ue4.assets.objects.FRotator; -import me.fungames.jfortniteparse.ue4.assets.objects.FSoftObjectPath; +import me.fungames.jfortniteparse.ue4.assets.exports.tex.UTexture2D; import me.fungames.jfortniteparse.ue4.assets.objects.FStructFallback; -import me.fungames.jfortniteparse.ue4.assets.objects.FVector; -import me.fungames.jfortniteparse.ue4.assets.util.FName; import me.fungames.jfortniteparse.ue4.assets.util.StructFallbackReflectionUtilKt; +import me.fungames.jfortniteparse.ue4.objects.core.math.FRotator; +import me.fungames.jfortniteparse.ue4.objects.core.math.FVector; +import me.fungames.jfortniteparse.ue4.objects.core.misc.FGuid; +import me.fungames.jfortniteparse.ue4.objects.uobject.FName; +import me.fungames.jfortniteparse.ue4.objects.uobject.FObjectExport; +import me.fungames.jfortniteparse.ue4.objects.uobject.FPackageIndex; +import me.fungames.jfortniteparse.ue4.objects.uobject.FSoftObjectPath; import me.fungames.jfortniteparse.ue4.versions.GameKt; import me.fungames.jfortniteparse.ue4.versions.Ue4Version; @@ -88,8 +88,8 @@ public static void main(String[] args) { provider = new MyFileProvider(paksDir, config.UEVersion, config.EncryptionKeys, config.bDumpAssets); - JsonArray components = exportAndProduceProcessed(config.ExportPackage); - if (components == null) return; + Package pkg = exportAndProduceProcessed(config.ExportPackage); + if (pkg == null) return; if (!exportQueue.isEmpty()) { exportUmodel(); @@ -99,7 +99,8 @@ public static void main(String[] args) { LOGGER.info("Writing to " + file.getAbsolutePath()); try (FileWriter writer = new FileWriter(file)) { - GSON.toJson(components, writer); +// GSON.toJson(components, writer); + GSON.toJson(MyFileProvider.compactFilePath(pkg.getName()), writer); } LOGGER.info(String.format("All done in %,.1f sec. In the Python script, replace the line with data_dir with this line below:\n\ndata_dir = r\"%s\"", (System.currentTimeMillis() - start) / 1000.0F, new File("").getAbsolutePath())); @@ -114,8 +115,8 @@ public static void main(String[] args) { } } - private static JsonArray exportAndProduceProcessed(String s) { - Package pkg = provider.loadIfNot(s); + private static Package exportAndProduceProcessed(String s) { + Package pkg = provider.loadGameFile(s); if (pkg == null) { return null; @@ -139,7 +140,7 @@ private static JsonArray exportAndProduceProcessed(String s) { comps.add(comp); FGuid guid = getProp(export, "MyGuid", FGuid.class); comp.add(guid != null ? asString(guid) : UUID.randomUUID().toString().replace("-", "")); - comp.add(exportType); + comp.add(objectExport.getObjectName().getText()); // region mesh FPackageIndex mesh = getProp(staticMeshExp, "StaticMesh", FPackageIndex.class); @@ -230,7 +231,8 @@ private static JsonArray exportAndProduceProcessed(String s) { if (additionalWorlds != null) { for (FSoftObjectPath additionalWorld : additionalWorlds) { String text = additionalWorld.getAssetPathName().getText(); - children.add(exportAndProduceProcessed(StringsKt.substringBeforeLast(text, '.', text) + ".umap")); + Package cpkg = exportAndProduceProcessed(StringsKt.substringBeforeLast(text, '.', text) + ".umap"); + children.add(cpkg != null ? MyFileProvider.compactFilePath(cpkg.getName()) : null); } } // endregion @@ -244,7 +246,17 @@ private static JsonArray exportAndProduceProcessed(String s) { comp.add(children); } - return comps; + File file = new File(MyFileProvider.JSONS_FOLDER, MyFileProvider.compactFilePath(pkg.getName()) + ".processed.json"); + file.getParentFile().mkdirs(); + LOGGER.info("Writing to " + file.getAbsolutePath()); + + try (FileWriter writer = new FileWriter(file)) { + GSON.toJson(comps, writer); + } catch (IOException e) { + LOGGER.error("Writing failed", e); + } + + return pkg; } private static void addToArray(JsonArray array, FPackageIndex index) { @@ -263,7 +275,7 @@ private static void exportTexture(FPackageIndex index) { } try { - UTexture texExport = (UTexture) provider.loadObject(index); + UTexture2D texExport = (UTexture2D) provider.loadObject(index); File output = new File(getExportDir(texExport), texExport.getName() + ".png"); if (output.exists()) { diff --git a/src/main/java/com/tb24/blenderumap/MyFileProvider.java b/src/main/java/com/tb24/blenderumap/MyFileProvider.java index 8afe4c2..934d56b 100644 --- a/src/main/java/com/tb24/blenderumap/MyFileProvider.java +++ b/src/main/java/com/tb24/blenderumap/MyFileProvider.java @@ -1,5 +1,6 @@ package com.tb24.blenderumap; +import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -12,12 +13,10 @@ import kotlin.collections.MapsKt; import me.fungames.jfortniteparse.fileprovider.DefaultFileProvider; -import me.fungames.jfortniteparse.ue4.FGuid; import me.fungames.jfortniteparse.ue4.assets.Package; import me.fungames.jfortniteparse.ue4.assets.exports.UExport; -import me.fungames.jfortniteparse.ue4.assets.objects.FObjectExport; -import me.fungames.jfortniteparse.ue4.assets.objects.FObjectImport; -import me.fungames.jfortniteparse.ue4.assets.objects.FPackageIndex; +import me.fungames.jfortniteparse.ue4.objects.core.misc.FGuid; +import me.fungames.jfortniteparse.ue4.objects.uobject.FObjectExport; import me.fungames.jfortniteparse.ue4.pak.GameFile; import me.fungames.jfortniteparse.ue4.pak.PakFileReader; import me.fungames.jfortniteparse.ue4.versions.Ue4Version; @@ -38,8 +37,6 @@ public MyFileProvider(File folder, Ue4Version game, Iterable encr for (EncryptionKey entry : encryptionKeys) { if (entry.FileName != null && !entry.FileName.isEmpty()) { - keysToSubmit.put(entry.Guid, entry.Key); - } else { Optional foundGuid = getUnloadedPaks().stream().filter(it -> it.getFileName().equals(entry.FileName)).findFirst(); if (foundGuid.isPresent()) { @@ -47,34 +44,37 @@ public MyFileProvider(File folder, Ue4Version game, Iterable encr } else { LOGGER.warn("PAK file not found: " + entry.FileName); } + } else { + keysToSubmit.put(entry.Guid, entry.Key); } } submitKeys(keysToSubmit); } - public Package loadIfNot(String pkg) { - GameFile gameFile = findGameFile(pkg); + @Override + public GameFile findGameFile(@NotNull String filePath) { + GameFile gameFile = super.findGameFile(filePath); - if (gameFile != null) { - return loadIfNot(gameFile); - } else { - LOGGER.warn("Package " + pkg + " not found"); - return null; + if (gameFile == null) { + LOGGER.warn("File " + filePath + " not found"); } + + return gameFile; } - public Package loadIfNot(GameFile pkg) { - return MapsKt.getOrPut(loaded, pkg, () -> { - LOGGER.info("Loading " + pkg); - Package loadedPkg = loadGameFile(pkg); + @Override + public Package loadGameFile(@NotNull GameFile file) { + return MapsKt.getOrPut(loaded, file, () -> { + LOGGER.info("Loading " + file); + Package loadedPkg = super.loadGameFile(file); if (loadedPkg != null && bDumpAssets) { - File file = new File(JSONS_FOLDER, pkg.getPathWithoutExtension() + ".json"); - LOGGER.info("Writing JSON to " + file.getAbsolutePath()); - file.getParentFile().mkdirs(); + File jsonDump = new File(JSONS_FOLDER, file.getPathWithoutExtension() + ".json"); + LOGGER.info("Writing JSON to " + jsonDump.getAbsolutePath()); + jsonDump.getParentFile().mkdirs(); - try (FileWriter writer = new FileWriter(file)) { + try (FileWriter writer = new FileWriter(jsonDump)) { GSON.toJson(loadedPkg.getExports(), writer); } catch (IOException e) { LOGGER.error("Writing failed", e); @@ -99,7 +99,7 @@ public UExport loadObjectPath(String objectPath) { objectName = objectPath.substring(objectPath.lastIndexOf('/') + 1); } - Package pkg = loadIfNot(pkgPath); + Package pkg = loadGameFile(pkgPath); for (FObjectExport export : pkg.getExportMap()) { if (export.getObjectName().getText().equals(objectName)) { @@ -110,30 +110,6 @@ public UExport loadObjectPath(String objectPath) { return null; } - public UExport loadObject(FPackageIndex index) { - if (index == null) return null; - - Package owner = index.getOwner(); - int i = index.getIndex(); - - if (i < 0) { // import - FObjectImport imp = owner.getImportMap().get(-i - 1); - Package pkg = loadIfNot(owner.getImportMap().get(-imp.getOuterIndex().getIndex() - 1).getObjectName().getText()); - - if (pkg != null) { - for (FObjectExport export : pkg.getExportMap()) { - if (export.getClassIndex().getName().equals(imp.getClassName().getText()) && export.getObjectName().getText().equals(imp.getObjectName().getText())) { - return export.getExportObject().getValue(); - } - } - } - } else if (i > 0) { // export - return owner.getExportMap().get(i - 1).getExportObject().getValue(); - } - - return null; - } - public static String compactFilePath(String path) { if (path.charAt(0) == '/') { return path; diff --git a/src/main/resources/config.json b/src/main/resources/config.json index eb1fcd9..6f9a390 100644 --- a/src/main/resources/config.json +++ b/src/main/resources/config.json @@ -1,19 +1,10 @@ { "_Documentation": "https://github.com/Amrsatrio/BlenderUmap/blob/master/README.md", "PaksDirectory": "C:\\Program Files\\Epic Games\\Fortnite\\FortniteGame\\Content\\Paks", - "UEVersion": "GAME_UE4_25", - "EncryptionKeys": [ - { - "Guid": "00000000000000000000000000000000", - "Key": "0x2ba4708c17abf803ab821c0c89fa1cf3dfe7aa91d526d2e11526bc1ac4e34d13" - }, - { - "FileName": "pakchunk1006-WindowsClient.pak", - "Key": "0x7dec1e6b26ce85b7680555f97064ceaa5c788dfdc674f98a6a711f726dedb943" - } - ], + "UEVersion": "GAME_UE4_LATEST", + "EncryptionKeys": [], "bReadMaterials": true, - "bRunUModel": true, + "bUseUModel": true, "UModelAdditionalArgs": "", "bDumpAssets": false, "ExportPackage": "/Game/Content/Athena/Apollo/Maps/Buildings/3x3/Apollo_3x3_BoatRental.umap" diff --git a/umap.py b/umap.py index d143a3d..41a5faf 100644 --- a/umap.py +++ b/umap.py @@ -1,5 +1,5 @@ """ -BlenderUmap v0.2.2 +BlenderUmap v0.3.0 (C) amrsatrio. All rights reserved. """ import bpy @@ -11,18 +11,35 @@ # Change the value to the working directory of the Java program with the bat. I'm leaving mine here. data_dir = r"C:\Users\satri\Documents\AppProjects\BlenderUmap\run" -clear_scene = True +reuse_maps = True reuse_meshes = True use_cube_as_fallback = True -use_gltf = False verbose = True +import_collection_name = "Imported" +import_collection = bpy.data.collections.get(import_collection_name) # ---------- END INPUTS, DO NOT MODIFY ANYTHING BELOW UNLESS YOU NEED TO ---------- -def import_umap(comps: list, attach_parent: bpy.types.Object = None) -> None: +def import_umap(processed_map_path: str, + into_collection: bpy.types.Collection = import_collection) -> bpy.types.Object: + map_name = processed_map_path[processed_map_path.rindex("/") + 1:processed_map_path.rindex(".")] + map_collection = bpy.data.collections.get(map_name) + + if reuse_maps and map_collection: + return place_map(map_collection, into_collection) + + map_collection = bpy.data.collections.new(map_name) + map_collection_inst = place_map(map_collection, into_collection) + map_scene = bpy.data.scenes.get(map_collection.name) or bpy.data.scenes.new(map_collection.name) + map_scene.collection.children.link(map_collection) + map_layer_collection = map_scene.view_layers[0].layer_collection.children[map_collection.name] + + with open(os.path.join(data_dir, "jsons" + processed_map_path + ".processed.json")) as file: + comps = json.loads(file.read()) + for comp_i, comp in enumerate(comps): guid = comp[0] - export_type = comp[1] + name = comp[1] mesh_path = comp[2] mats = comp[3] texture_data = comp[4] @@ -30,17 +47,21 @@ def import_umap(comps: list, attach_parent: bpy.types.Object = None) -> None: rotation = comp[6] or [0, 0, 0] scale = comp[7] or [1, 1, 1] child_comps = comp[8] - - name = export_type + (("_" + guid[:8]) if guid else "") print("\nActor %d of %d: %s" % (comp_i + 1, len(comps), name)) if child_comps and len(child_comps) > 0: - bpy.ops.mesh.primitive_plane_add(size=1) - bpy.context.active_object.data = bpy.data.meshes["__empty"] - elif not mesh_path: + for i, child_comp in enumerate(child_comps): + apply_name_and_transforms(import_umap(child_comp, map_collection), "%s_%d" % (name, i), location, rotation, scale) + + continue + + bpy.context.window.scene = map_scene + bpy.context.view_layer.active_layer_collection = map_layer_collection + + if not mesh_path: print("WARNING: No mesh, defaulting to fallback mesh") fallback() - else: + else: # Import the mesh if mesh_path.startswith("/"): mesh_path = mesh_path[1:] @@ -62,32 +83,21 @@ def import_umap(comps: list, attach_parent: bpy.types.Object = None) -> None: bpy.context.active_object.data = existing_mesh else: mesh_import_result = None - - if use_gltf: - final_dir = os.path.join(data_dir, mesh_path + ".gltf") - if verbose: - print("Mesh:", final_dir) - if os.path.exists(final_dir): - mesh_import_result = bpy.ops.import_scene.gltf(filepath=final_dir) - else: - print("WARNING: Mesh not found, defaulting to fallback mesh") - fallback() + final_dir = os.path.join(data_dir, mesh_path) + mesh_path_ = mesh_path + if os.path.exists(final_dir + ".psk"): + final_dir += ".psk" + mesh_path_ += ".psk" + elif os.path.exists(final_dir + ".pskx"): + final_dir += ".pskx" + mesh_path_ += ".pskx" + if verbose: + print("Mesh:", final_dir) + if os.path.exists(final_dir): + mesh_import_result = bpy.ops.import_scene.psk(bReorientBones=True, directory=data_dir, files=[{"name": mesh_path_}]) else: - final_dir = os.path.join(data_dir, mesh_path) - mesh_path_ = mesh_path - if os.path.exists(final_dir + ".psk"): - final_dir += ".psk" - mesh_path_ += ".psk" - elif os.path.exists(final_dir + ".pskx"): - final_dir += ".pskx" - mesh_path_ += ".pskx" - if verbose: - print("Mesh:", final_dir) - if os.path.exists(final_dir): - mesh_import_result = bpy.ops.import_scene.psk(bReorientBones=True, directory=data_dir, files=[{"name": mesh_path_}]) - else: - print("WARNING: Mesh not found, defaulting to fallback mesh") - fallback() + print("WARNING: Mesh not found, defaulting to fallback mesh") + fallback() if mesh_import_result == {"FINISHED"}: if verbose: @@ -102,23 +112,9 @@ def import_umap(comps: list, attach_parent: bpy.types.Object = None) -> None: print("WARNING: Failure importing mesh, defaulting to fallback mesh") fallback() - created = bpy.context.active_object - created.name = name - created.location = [location[0] * 0.01, location[1] * 0.01 * -1, location[2] * 0.01] - created.rotation_mode = "XYZ" - created.rotation_euler = [radians(rotation[2] + (90 if use_gltf else 0)), radians(rotation[0] * -1), radians(rotation[1] * -1)] - created.scale = scale - - if attach_parent: - print("Attaching to parent", attach_parent.name) - created.parent = attach_parent + apply_name_and_transforms(bpy.context.active_object, name, location, rotation, scale) - if child_comps: - if use_gltf: - print("Nested worlds aren't supported yet with GLTF") - else: - for child_comp in child_comps: - import_umap(child_comp, created) + return map_collection_inst def import_material(m_idx: int, path: str, suffix: str, base_textures: list, tex_data: dict) -> bpy.types.Material: @@ -168,7 +164,7 @@ def group(sub_tex_idx, location): tree.links.new(d_tex.outputs[0], sh.inputs[tex_index]) if tex_index is 4: # change mat blend method if there's an alpha mask texture - m.blend_method = "CLIP" + m.blend_method = 'CLIP' return sh @@ -199,11 +195,29 @@ def group(sub_tex_idx, location): return m -def fallback() -> None: +def fallback(): bpy.ops.mesh.primitive_plane_add(size=1) bpy.context.active_object.data = bpy.data.meshes["__fallback" if use_cube_as_fallback else "__empty"] +def apply_name_and_transforms(ob, name, location, rotation, scale): + ob.name = name + ob.location = [location[0] * 0.01, location[1] * 0.01 * -1, location[2] * 0.01] + ob.rotation_mode = "XYZ" + ob.rotation_euler = [radians(rotation[2]), + radians(rotation[0] * -1), + radians(rotation[1] * -1)] + ob.scale = scale + + +def place_map(collection: bpy.types.Collection, into_collection: bpy.types.Collection): + c_inst = bpy.data.objects.new(collection.name, None) + c_inst.instance_type = 'COLLECTION' + c_inst.instance_collection = collection + into_collection.objects.link(c_inst) + return c_inst + + def get_or_load_img(img_path: str) -> bpy.types.Image: name = os.path.basename(img_path) existing = bpy.data.images.get(name) @@ -225,13 +239,14 @@ def get_or_load_img(img_path: str) -> bpy.types.Image: print(img_path) loaded = bpy.data.images.load(filepath=img_path) loaded.name = name + loaded.alpha_mode = 'CHANNEL_PACKED' return loaded else: print("WARNING: " + img_path + " not found") return None -def cleanup() -> None: +def cleanup(): for block in bpy.data.meshes: if block.users == 0: bpy.data.meshes.remove(block) @@ -405,16 +420,25 @@ def string_hash_code(s: str) -> int: tex_shader.inputs[3].name = "Emission" tex_shader.inputs[4].name = "Alpha" -# clear all objects except camera -if clear_scene: - for obj in bpy.context.scene.objects: - if obj.type != "CAMERA": - obj.select_set(True) +# make sure we're on main scene to deal with the fallback objects +main_scene = bpy.data.scenes.get("Scene") or bpy.data.scenes.new("Scene") +bpy.context.window.scene = main_scene + +# prepare collection for imports +if import_collection: + bpy.ops.object.select_all(action='DESELECT') + + for obj in import_collection.objects: + obj.select_set(True) bpy.ops.object.delete() +else: + import_collection = bpy.data.collections.new(import_collection_name) + main_scene.collection.children.link(import_collection) cleanup() + # setup helper objects # 1. fallback cube bpy.ops.mesh.primitive_cube_add(size=2) @@ -436,8 +460,11 @@ def string_hash_code(s: str) -> int: with open(os.path.join(data_dir, "processed.json")) as file: import_umap(json.loads(file.read())) +# go back to main scene +bpy.context.window.scene = main_scene + # delete helper objects -bpy.ops.object.select_all(action="DESELECT") +bpy.ops.object.select_all(action='DESELECT') fallback_cube.select_set(True) empty_mesh.select_set(True) bpy.ops.object.delete()