Finished Polishing

- Finished the read me
- Added support for UE4
- Fixed hopefully most bugs
This commit is contained in:
boo3 2023-11-13 01:51:12 +01:00
parent 7d98ce4da5
commit b01a970ceb
26 changed files with 438 additions and 11 deletions

1
.gitignore vendored
View File

@ -3,6 +3,7 @@ done/*
output/*
textures/*
units/*
converted_textures/*
# Ignore specific folder
net6.0-windows/
output2/

Binary file not shown.

Binary file not shown.

View File

@ -1,7 +1,7 @@
# Fusion
### a more unreal fuel than diesel
## Collection of scripts to help port payday2 diesel maps to unreal engine
### Collection of scripts to help port payday2 diesel maps to unreal engine
## Overview
@ -13,6 +13,10 @@
[Blender](https://www.blender.org/)
[Python]
### If you want to use this to port maps to payday3 you will need imagemagick because the .dds files need to be converted to .png because UE 4.7 cant import .dds files
[imagemagick](https://imagemagick.org/script/download.php#windows)
### pdworld2json.py
Converts world.continent files to the json format and fixes location rotation to work with unreal.
@ -143,24 +147,67 @@ After it finished, we need to sort the data for the next step, so open ModelCopy
It should have created 2 more folders output and textures, and copied the data needed for blender.
### Fixing the models using blender and a script
### **Depending on what engine you will port to stuff is now gonna change remember to install imagemagick if you plan to use UE4**
Now we should have a bunch of models with the .gltf inside output with the proper folder structure but they are not really usable in unreal.
### For Unreal 4 users run UE4_ConvertTextureFiles to convert the texture files to .PNGs
```
python .\UE4_ConvertTextureFiles.py
```
Now we should have a bunch of models inside output with the proper folder structure but they are not really usable in unreal.
So download [Blender](https://www.blender.org/) if you haven't already and open ConvertExportedToUnreal.blend.
If for some reason you cant open the .blend file anymore, you can just make a new project and load the blender_script.py.
If for some reason you cant open the .blend file anymore, you can just make a new project and load the blender scripts
![](/assets/images/B1.png)
If up to this point you have done everything correct you should be able to hit the run script button, and wait for it to finish. It will take a while depending of the amount of files you have.
Chose the right script for what engine UE4 or UE5
![](/assets/images/B3.png)
If up to this point you have done everything correct you should be able to hit the run script button, and wait for it to finish. It will take a while depending of the amount of files you have. Blender will lag but don't worry.
![](/assets/images/B2.png)
When done it should have created a folder called "done" there you will find the models fixed and if you had the textures that where needed in the "texture" folder they should have applied the textures.
When done it should have created a folder called "done" there you will find the fixed models and if you had the textures properly setup they should have applied too.
(Sometimes Overkill uses textures outside dlc folder so there will be missing textures i will have to fix that)
### Importing models into unreal
Because the amount of data to import is so huge unreal sometimes likes to crash so i made 2 scripts (One for UE4 and UE5) to help with that, you might need an auto clicker because you will need to press import a lot with it.
To use it run it inside Unreal Engine
Run python scripts in UE5
![](/assets/images/UEPY1.png)
And UE4
![](/assets/images/UEPY2.png)
Now suffer though the slow import, this will by far take the longest time so get compfy
### Importing map data
Ok you done it all, homestretch. Now make sure to add the Files from the "UnrealEditorFiles/" Folder into your project. Reload the project now try to drag the world.json into the Content Browser It should pop up with a json import window, select the DataTable Row Type to be PDWorldJson and hit apply
![](/assets/images/MIPU.png)
Next open the level spawner widget like this
![](/assets/images/MIPU2.png)
Choose the map files and hit spawn map
![](/assets/images/MIPU3.png)
### Profit???
Well now you done it, i hope at least. When anything goes wrong don't worry, read over this ReadMe.
If you cant figure it out come and reach out to me i'll try to hep as best i can.
You can find me on discord under boo3
![](/assets/images/discord.png)

View File

@ -0,0 +1,43 @@
import os
from wand.image import Image
def convert_dds_to_png(input_folder, output_folder):
# Make sure the output folder exists
if not os.path.exists(output_folder):
os.makedirs(output_folder)
# List all files in the input folder
files = os.listdir(input_folder)
# Iterate through each file in the input folder
for file in files:
# Check if the file has a .dds extension
if file.lower().endswith('.dds'):
# Create the input and output file paths
input_path = os.path.join(input_folder, file)
output_path = os.path.join(output_folder, os.path.splitext(file)[0] + '.png')
print(f"Converting {input_path} to {output_path}")
# Convert DDS to PNG
with Image(filename=input_path) as img:
img.compression = "no"
img.save(filename=output_path)
print(f"Conversion complete: {input_path} -> {output_path}")
if __name__ == "__main__":
# Set the source and destination folders
source_folder = 'textures'
destination_folder = 'converted_textures'
print(f"Converting DDS files from {source_folder} to PNG and saving to {destination_folder}")
# Make sure the output folder exists
if not os.path.exists(destination_folder):
os.makedirs(destination_folder)
# Convert all DDS files in the source folder to PNG
convert_dds_to_png(source_folder, destination_folder)
print("Conversion complete.")

40
UE4_Importer.py Normal file
View File

@ -0,0 +1,40 @@
import os
import unreal
# Get The working Dir
root_folder = r'\done'
dir_path = os.path.dirname(os.path.realpath(__file__))
model_directory = dir_path + root_folder
print(model_directory)
# Get the AssetTools
asset_tools = unreal.AssetToolsHelpers.get_asset_tools()
# Iterate through .fbx files in the specified directory and its subdirectories
for root, dirs, files in os.walk(model_directory):
for file_name in files:
if file_name.endswith(".fbx"):
file_path = os.path.join(root, file_name)
# Extract relative path from the source directory
relative_path = os.path.relpath(file_path, model_directory)
# Construct the destination path in the Content Browser
destination_path = "/Game/units/" + os.path.splitext(relative_path.replace("\\", "/"))[0]
# Create an import task
import_task = unreal.AssetImportTask()
import_task.filename = file_path
import_task.destination_path = destination_path
# Import the .fbx file
asset_tools.import_asset_tasks([import_task])
# Print feedback
print(f"Importing: {file_path}")
print(f"Destination Path: {destination_path}")
print("------")
print("Batch import complete.")
# Add that after importing all the modlels to remove redundent materials and combine them all into one per unique material

292
UE4_blender_script.py Normal file
View File

@ -0,0 +1,292 @@
import bpy
import os
import xml.etree.ElementTree as ET
def custom_print(*args):
message = ' '.join(map(str, args))
for window in bpy.context.window_manager.windows:
screen = window.screen
for area in screen.areas:
if area.type == 'CONSOLE':
override = {'window': window, 'screen': screen, 'area': area}
bpy.ops.console.scrollback_append(override, text=message, type="OUTPUT")
for material in bpy.data.materials:
bpy.data.materials.remove(material, do_unlink=True)
for mesh in bpy.data.meshes:
bpy.data.meshes.remove(mesh, do_unlink=True)
for image in bpy.data.images:
bpy.data.images.remove(image, do_unlink=True)
# Set the input and output directories
wherethefilesare = 'output' # Set where the files are located from PD2ModelParser.exe
wheretosave = 'done' # Where you want to save the fixed files.
wherethetexturesare = 'converted_textures' # Set where all the files are located
# Get the parent directory of the python file
dir_path = os.path.dirname(os.path.realpath(__file__))
parent_dir = os.path.abspath(os.path.join(dir_path, os.pardir))
# Make valid folder paths using os.path.join
input_directory = os.path.join(parent_dir, wherethefilesare)
output_directory = os.path.join(parent_dir, wheretosave)
texture_directory = os.path.join(parent_dir, wherethetexturesare)
custom_print("Input Directory:", input_directory)
custom_print("Output Directory:", output_directory)
custom_print("Texture Directory:", texture_directory)
def get_object_filepath(file_path):
# Replace the extension with .object
object_file_path = os.path.splitext(file_path)[0] + '.object'
return object_file_path
def set_diffuse_texture_as_base_color(material, texture_path):
# Create an image texture node
texture_node = material.node_tree.nodes.new(type='ShaderNodeTexImage')
texture_node.image = bpy.data.images.load(texture_path)
# Get the principled BSDF shader node
principled_bsdf = material.node_tree.nodes.get("Principled BSDF")
# Connect the texture node to the base color input of the principled BSDF shader node
material.node_tree.links.new(principled_bsdf.inputs["Base Color"], texture_node.outputs["Color"])
def process_xml_file(file_path):
try:
# Open the file and read its contents
with open(file_path, 'r') as file:
file_contents = file.read()
# Parse the XML string
root = ET.fromstring(file_contents)
# Find the value of the 'materials' attribute
materials_value = root.find('.//diesel').get('materials')
# custom_print the result
custom_print(f"The value of 'materials' is: {materials_value}")
# return the materials_value
return materials_value
except Exception as e:
custom_print(f"Error processing XML file: {e}")
return None
def get_diffuse_texture_name(xml_file_path, material_name):
"""
Extracts the name of the texture in <diffuse_texture> for a given material name in an XML file.
Parameters:
- xml_file_path (str): The path to the XML file.
- material_name (str): The name of the material to search for.
Returns:
- str: The name of the texture if the material is found, else None.
"""
try:
# Parse the XML file
tree = ET.parse(xml_file_path)
root = tree.getroot()
# Find the material with the specified name
target_material = root.find(f".//material[@name='{material_name}']")
if target_material is not None:
# Find the 'diffuse_texture' element within the material
diffuse_texture_element = target_material.find("diffuse_texture")
if diffuse_texture_element is not None:
# Get the value of the 'file' attribute in <diffuse_texture> and extract the texture name
diffuse_texture_path = diffuse_texture_element.get("file")
texture_name = diffuse_texture_path.split("/")[-1] # Extracting the texture name from the file path
return f"{texture_name}.png"
else:
custom_print(f"Diffuse texture not found for material '{material_name}' in the XML file.")
return None
else:
custom_print(f"Material '{material_name}' not found in the XML file.")
return None
except FileNotFoundError:
custom_print(f"File not found: {xml_file_path}")
return None
except Exception as e:
custom_print(f"Error while processing {xml_file_path}: {e}")
return None
# Main function to process the object
def process_gltf(file_path):
# Get the filename without extension
file_name = os.path.splitext(os.path.basename(file_path))[0]
# Clear existing mesh objects in the scene
bpy.ops.object.select_all(action='SELECT')
bpy.ops.object.delete()
# Import the GLTF file
bpy.ops.import_scene.gltf(filepath=file_path)
# Deselect all objects
bpy.ops.object.select_all(action='DESELECT')
# Find the mesh with the highest poly count
max_poly_count = 0
object_with_max_poly_count = None
for obj in bpy.context.scene.objects:
if obj.type == 'MESH' and len(obj.data.polygons) > max_poly_count:
max_poly_count = len(obj.data.polygons)
object_with_max_poly_count = obj
# Select the parent (root) object of the object with the highest poly count
if object_with_max_poly_count:
root_object = object_with_max_poly_count.parent
if root_object:
root_object.select_set(True)
# Set the 3D cursor as the pivot point for the transformation
bpy.context.scene.tool_settings.transform_pivot_point = 'CURSOR'
# Set the location of the selected object to the 3D cursor
root_object.location = bpy.context.scene.cursor.location
# Set the transform rotation mode to XYZ Euler
root_object.rotation_mode = 'XYZ'
# Add the specified rotation to the object
root_object.rotation_euler[0] = -1.5708 # X-axis rotation, -90 degrees in radians
root_object.rotation_euler[1] = 0 # Y-axis rotation, 0 degrees in radians
root_object.rotation_euler[2] = 0 # Z-axis rotation, 0 degrees in radians
# Select all objects in the scene
bpy.ops.object.select_all(action='SELECT')
# Apply all transforms to the selected objects
bpy.ops.object.transform_apply(location=True, rotation=True, scale=True)
# Select meshes that start with the name "g_"
bpy.ops.object.select_all(action='DESELECT')
for obj in bpy.context.scene.objects:
if obj.type == 'MESH' and obj.name.startswith("g_"):
obj.select_set(True)
# Invert the selection and delete the unselected objects
bpy.ops.object.select_all(action='INVERT')
bpy.ops.object.delete()
# Join remaining objects together
bpy.ops.object.select_all(action='DESELECT')
#Mesh objects
MSH_OBJS = [m for m in bpy.context.scene.objects if m.type == 'MESH']
# Check if there are any objects in the list
if MSH_OBJS:
for OBJS in MSH_OBJS:
# Select all mesh objects
OBJS.select_set(state=True)
# Makes one active
bpy.context.view_layer.objects.active = OBJS
# Joins objects
bpy.ops.object.join()
# Rename the object to the filename
bpy.ops.object.select_all(action='DESELECT')
bpy.ops.object.select_by_type(type='MESH')
bpy.context.selected_objects[0].name = file_name
# Get mat config
object_file_path = get_object_filepath(file_path)
material_config = process_xml_file(object_file_path)
# Loop each material slot get the name
for material_slot in bpy.context.selected_objects[0].material_slots:
# Get the material in the current slot
material = material_slot.material
# Get the material name
material_name = material.name
# Initialize the variable with a default value
diffuse_texture_name = None
# Get the diffuse texture name using get_diffuse_texture_name()
# Makes this not hardcoded
if material_config is not None:
get_material_config_path = material_config.replace("units/", f"{input_directory}/") + ".material_config"
diffuse_texture_name = get_diffuse_texture_name(get_material_config_path, material_name)
else:
custom_print("Material config is None. Handle this case accordingly.")
# CRASHES WHEN IT CANT FIND THE FILES TO LAZY TO FIX IT SO IT JUST SKIPS IT
# Check if the texture exists
if diffuse_texture_name:
# Construct the full path to the texture
texture_path = os.path.join(texture_directory, diffuse_texture_name)
# Check if the texture file exists before trying to load it
if os.path.exists(texture_path):
# Set the diffuse texture as the base color
set_diffuse_texture_as_base_color(material, texture_path)
else:
custom_print(f"Texture not found for material {material_name}: {texture_path}")
else:
custom_print(f"No diffuse texture found for material {material_name}")
# Ensure the output directory exists
output_path = os.path.join(output_directory, os.path.relpath(file_path, start=input_directory))
os.makedirs(os.path.dirname(output_path), exist_ok=True)
# Select all objects in the scene
bpy.ops.object.select_all(action='SELECT')
# Export as to the output directory
bpy.ops.export_scene.fbx(
filepath=output_path.replace('.gltf', '.fbx'),
check_existing=True,
filter_glob="*.fbx",
use_selection=True,
bake_space_transform=False,
object_types={'MESH'},
mesh_smooth_type='EDGE',
use_mesh_edges=True,
add_leaf_bones=False,
path_mode='COPY',
)
# Remove materials and meshes
for material in bpy.data.materials:
bpy.data.materials.remove(material, do_unlink=True)
for mesh in bpy.data.meshes:
bpy.data.meshes.remove(mesh, do_unlink=True)
for image in bpy.data.images:
bpy.data.images.remove(image, do_unlink=True)
else:
custom_print("No objects selected.")
for material in bpy.data.materials:
bpy.data.materials.remove(material, do_unlink=True)
for mesh in bpy.data.meshes:
bpy.data.meshes.remove(mesh, do_unlink=True)
for image in bpy.data.images:
bpy.data.images.remove(image, do_unlink=True)
# Recursive function to process all .gltf files in a directory
def process_directory(directory):
for root, dirs, files in os.walk(directory):
for file in files:
if file.lower().endswith('.gltf'):
file_path = os.path.join(root, file)
process_gltf(file_path)
# Start processing from the input directory
process_directory(input_directory)

View File

@ -1,23 +1,26 @@
import os
import unreal
# Set the path to the directory containing your .gltf files
gltf_directory = "D:/PayDay2/done"
# Get The working Dir
root_folder = r'\done'
dir_path = os.path.dirname(os.path.realpath(__file__))
model_directory = dir_path + root_folder
print(model_directory)
# Get the AssetTools
asset_tools = unreal.AssetToolsHelpers.get_asset_tools()
# Iterate through .gltf files in the specified directory and its subdirectories
for root, dirs, files in os.walk(gltf_directory):
for root, dirs, files in os.walk(model_directory):
for file_name in files:
if file_name.endswith(".gltf"):
file_path = os.path.join(root, file_name)
# Extract relative path from the source directory
relative_path = os.path.relpath(file_path, gltf_directory)
relative_path = os.path.relpath(file_path, model_directory)
# Construct the destination path in the Content Browser
destination_path = "/Game/units/" + os.path.splitext(relative_path)[0]
destination_path = "/Game/units/" + os.path.splitext(relative_path.replace("\\", "/"))[0]
# Create an import task
import_task = unreal.AssetImportTask()

BIN
assets/images/B3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 330 KiB

BIN
assets/images/MIPU.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 341 KiB

BIN
assets/images/MIPU2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 644 KiB

BIN
assets/images/MIPU3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 832 KiB

BIN
assets/images/UE41.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

BIN
assets/images/UE42.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

BIN
assets/images/UEPY1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 762 KiB

BIN
assets/images/UEPY2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

BIN
assets/images/discord.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

View File

@ -1 +1,2 @@
scipy==1.11.3
wand