Finished Polishing
- Finished the read me - Added support for UE4 - Fixed hopefully most bugs
1
.gitignore
vendored
@ -3,6 +3,7 @@ done/*
|
||||
output/*
|
||||
textures/*
|
||||
units/*
|
||||
converted_textures/*
|
||||
# Ignore specific folder
|
||||
net6.0-windows/
|
||||
output2/
|
||||
|
BIN
ConvertExportedToUnreal.blend1
Normal file
59
README.md
@ -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
|
||||
|
||||
### Profit???
|
||||
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)
|
43
UE4_ConvertTextureFiles.py
Normal 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
@ -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
@ -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)
|
@ -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
After Width: | Height: | Size: 330 KiB |
BIN
assets/images/MIPU.png
Normal file
After Width: | Height: | Size: 341 KiB |
BIN
assets/images/MIPU2.png
Normal file
After Width: | Height: | Size: 644 KiB |
BIN
assets/images/MIPU3.png
Normal file
After Width: | Height: | Size: 832 KiB |
BIN
assets/images/UE41.png
Normal file
After Width: | Height: | Size: 99 KiB |
BIN
assets/images/UE42.png
Normal file
After Width: | Height: | Size: 1.1 MiB |
BIN
assets/images/UE4ScreenShot.png
Normal file
After Width: | Height: | Size: 1.1 MiB |
BIN
assets/images/UEPY1.png
Normal file
After Width: | Height: | Size: 762 KiB |
BIN
assets/images/UEPY2.png
Normal file
After Width: | Height: | Size: 55 KiB |
BIN
assets/images/discord.png
Normal file
After Width: | Height: | Size: 100 KiB |
@ -1 +1,2 @@
|
||||
scipy==1.11.3
|
||||
wand
|
||||
|