Skip to content

Instantly share code, notes, and snippets.

@tuket
Last active October 15, 2025 10:14
Show Gist options
  • Select an option

  • Save tuket/b17a324a43f1f2eda8bd704b30d971b0 to your computer and use it in GitHub Desktop.

Select an option

Save tuket/b17a324a43f1f2eda8bd704b30d971b0 to your computer and use it in GitHub Desktop.
Update Evergine project to support changes in gamma correction
# Run this script on an Evergine project to port to version 2025.10.21 or later.
# You should run this script when you want to update an existing Evergine project, which uses an Evergine version older than 2025.10.21, to a newer version.
# It will fix issues with gamma correction of textures.
# Usage: python3 evergine_project_upgrade.py path/to/my_evergine.weproj
import yaml
import argparse
import sys
import os
from pathlib import Path
def is_bin_or_obj_folder(path):
return path.is_dir() and (path.name == 'bin' or path.name == 'obj')
def validate_weproj_file(path):
if not path.exists():
print(f"Error: File '{path}' does not exist.")
sys.exit(1)
if not path.is_file():
print(f"Error: '{path}' is not a file.")
sys.exit(1)
if path.suffix.lower() != '.weproj':
print(f"Error: '{path}' is not a .weproj file.")
sys.exit(1)
def main():
parser = argparse.ArgumentParser(
description='Upgrade tool for .weproj files',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
python3 upgrade.py project.weproj
python3 upgrade.py "C:\\path\\to\\my project.weproj"
"""
)
parser.add_argument(
'weproj_file',
help='Path to the .weproj file to process'
)
args = parser.parse_args()
weproj_path = Path(args.weproj_file)
validate_weproj_file(weproj_path)
folder_path = weproj_path.parent
visit_files_recursively(folder_path, ext='.wemt', process_fn=process_material_file)
visit_files_recursively(folder_path, ext='.weps', process_fn=process_particle_system_file) # Just to skip bin/obj folders
visit_files_recursively(folder_path, ext='.wetx', process_fn=process_texture_file)
def visit_files_recursively(folder_path, ext, process_fn):
try:
for item in folder_path.iterdir():
if item.name.startswith('.') or is_bin_or_obj_folder(item):
continue
if item.is_file() and item.suffix.lower() == ext:
process_fn(item)
elif item.is_dir():
visit_files_recursively(item, ext, process_fn)
except PermissionError as e:
print(f"Permission denied accessing '{folder_path}': {e}")
except Exception as e:
print(f"Error accessing '{folder_path}': {e}")
textures_used_as_color = {}
textures_used_as_other = {}
def process_material_file(file_path):
with open(file_path, 'r+', encoding='utf-8') as f:
try:
txt = "\n".join(f.readlines()[1:])
y = yaml.safe_load(txt)
textures = y["MaterialInfo"]["Textures"]
for tex in textures:
tex_id = tex["ID"]
tex_usage = tex["Name"]
info = (file_path, tex_usage)
if tex_usage == "BaseColorTexture" or tex_usage == "EmissiveTexture":
textures_used_as_color[tex_id] = info
else:
textures_used_as_other[tex_id] = info
except yaml.YAMLError as e:
print(f"Error parsing YAML in '{file_path}': {e}")
return
def process_particle_system_file(file_path):
with open(file_path, 'r+', encoding='utf-8') as f:
for line in f.readlines():
tag = "ParticleTexture:"
ind = line.find(tag)
if ind >= 0:
tex_id = line[ind + len(tag):].strip()
info = (file_path, "ParticleTexture")
textures_used_as_color[tex_id] = info
def process_texture_file(file_path):
newTxt = None
modified = []
with open(file_path, 'r+', encoding='utf-8') as f:
try:
lines = f.readlines()
first_line = lines[0]
tex_id = lines[1][4:-1]
used_as_color = textures_used_as_color.get(tex_id, None)
used_as_other = textures_used_as_other.get(tex_id, None)
not_used = not used_as_color and not used_as_other
name_lower = file_path.name.lower()
name_suggests_is_color = ("color" in name_lower) or ("emissive" in name_lower)
name_suggests_is_other = ("normal" in name_lower) or ("roughness" in name_lower) or ("metallic" in name_lower) or ("pbr" in name_lower)
if used_as_color and used_as_other:
target_format = "R8G8B8A8_UNorm_SRgb"
print(f"Warning: Texture '{tex_id}' in '{file_path}' is used both as {used_as_color[1]} in {used_as_color[0]} and as {used_as_other[1]} in {used_as_other[0]}. Will be considered as color.")
elif not_used:
if (not name_suggests_is_color and not name_suggests_is_other) or (name_suggests_is_color and name_suggests_is_other):
target_format = None
print(f"Warning: Texture '{tex_id}' in '{file_path}' is not used in any material and the name doesn't suggest any specific usage. Format won't be changed.")
else:
target_format = "R8G8B8A8_UNorm_SRgb" if name_suggests_is_color else "R8G8B8A8_UNorm"
else:
target_format = "R8G8B8A8_UNorm_SRgb" if used_as_color else "R8G8B8A8_UNorm"
def replace_format(label):
for i in range(len(lines)):
if lines[i].find(label + ":") >= 0:
for srcFormat in ["R8G8B8A8_UNorm_SRgb", "R8G8B8A8_UNorm"]:
if srcFormat == target_format:
continue
j = lines[i].find(srcFormat)
if j != -1:
lines[i] = lines[i][:j] + target_format + "\n"
modified.append(f"{srcFormat} -> {target_format}")
break
if target_format:
replace_format("Format")
replace_format("PixelFormat")
if modified:
newTxt = first_line + "".join(lines[1:])
except Exception as e:
print(f"Error processing texture file '{file_path}': {e}")
return
if modified:
with open(file_path, 'w', encoding='utf-8') as f:
f.truncate(0)
f.write(newTxt)
print(f"Updated texture '{tex_id}' in '{file_path}': {modified}")
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment