How do I query project engine settings config from python?

Greetings all. I’m quite new to Python in Unreal, but am very comfortable with python in general.

I’m trying to use python to find the project level DefaultEngine.ini config file and query specific values from it. I would ultimately like to be able to modify values and update the config too, but happy to start simple.

Is there a built in class or struct I can pull these values from, or do I have to pull these from the DefaultEngine.ini directly?
If I need to source the DefaultEngine.ini, is there a convenient way to grab the file path?

I’ve managed to get a rough version of this working by parsing the file using pythons builtin configparser module. But this has a tendency to raise errors when it finds duplicate entries, which I’ve already run into on an empty project.

Here is what I have (roughly) so far…

import configparser
proj_dir = unreal.Paths.project_dir()
engine_config = os.path.join(proj_dir, 'Config', 'DefaultEngine.ini')
config = configparser.ConfigParser()
config.read(engine_config)
config.get('/Script/PythonScriptPlugin.PythonScriptPluginSettings', 'bIsolateInterpreterEnvironment')
3 Likes

Although this seems like it would work, the ini file format used in UE doesn’t conform to the spec defined in configparser. UE uses multiple identical keys for array values and this breaks the base implementation for configparser which requires section keys to be unique when reading. I’ve had to write my own parser implementation to cast it to list when more then one entry appears in the ini file.

1 Like

Have you guys been able to make this work at all? I’m just trying to figure out the same thing. I was hoping to find a Python example that reads Editor Preferences and writes them, but I can’t find anything like that :frowning:

To read/write from it isn’t the hard part.

This will get it in for you as a dictionary, that supports the weird ini syntax of duplicate keys:

data = {}

with open(ini_path, "r") as f:
    raw_data = f.read()
    
    section = ""
    lines = raw_data.split("\n")

    for line in lines:
        
        if len(line) == 0:
            continue

        if line.startswith("[") and line.endswith("]"):
            section = line[1:-1]
            data[section] = {}
            continue

        parts = line.split("=", 1)
        key = parts[0]
        value = ""

        if len(parts) > 1:
            value = parts[1]

        if not data[section].get(key):
            data[section][key] = value
            continue
        
        if isinstance(data[section][key], str):
            data[section][key] = [data[section][key]]
            
        data[section][key].append(value)

Writing would look something like this:

if deleting_key:
    if section in data:
        data[section].pop(key, None)
else:
    if not section in data:
        data[section] = {}
        
    data[section][key] = value

output = ""

for section_key, section_value in data.items():
    output += "[" + section_key + "]" + "\n"
    
    for item_key, item_value in section_value.items():
        
        if isinstance(item_value, str):
            output += item_key + "=" + item_value + "\n"
            
        elif isinstance(item_value, list):
            for list_item in item_value:
                output += item_key + "=" + list_item + "\n"
                
    output += "\n"

with open(ini_path, "w") as f:
    f.write(output)

Note that the data writing is assuming the format of dictionary using the read snippet from the first part.

The hard part is that even though you’ve written to the ini file, unreal will decide to purge your changes when you close the editor with the current editor cache config status. So when modding the config values you also need to invalidate the editor cache.

I’m still looking into how to do that with C++. The workaround I have at the moment is to pass my ini changes to an external python script on a timer. After the editor closes and clobbers my ini file, the timer python runs subsequently and writes the ini file properly afterwards, Then that timer script reloads the project using the newly established config files.

1 Like

Warren thanks so much! I’m not really a programmer so I’m struggling a bit but I’m slowly understanding. LOL.

I understand that, basically, we can change the settings but they don’t STAY changed. And you are trying to do that with C++ because it doesn’t seem possible with Python, right?

So, in my case, I’m trying to make changes to the INI file in an Unreal project folder, in “/Saved/Config/WindowsEditor/EditorPerProjectUserSettings.ini”. That’s where the Editor Preferences are stored. And you’re saying that it’s possible to make changes to items in there (for example, set “bInvertMiddleMousePan=True”) but those changes won’t save when you close the editor. Is that right?

And I’m guessing it’s not as simple as trying to change the “default” engine values, IN ADDITION to the individual project values? In my understanding, when the INI we are changing is “clobbered” (resetting to default) it is being replaced with values that are inside the Engine folder in this file:
C:\Program Files\Epic Games\UE_5.4\Engine\ConfigBaseEditorPerProjectUserSettings.ini

So, when we write changes to the local project INI, can we write those changes to the Engine INI, so that when they are “clobbered” its fine because they are replaced with the correct values? Or, am I dumb? LOL. I could be, sorry. Hahaha.

What I’m finding now is that it works - it changes the “EditorPerProjectUserSettings.ini” file, and updates it the way I want (it adds / changes settings).

The problem is that Unreal itself needs to be told to “read” the INI (ie. refresh the editor preferences) and I haven’t been able to find how to do that yet. :slight_smile:
Here’s my code anyway:

import os
import unreal
import sys

data = {}

proj_dir = unreal.Paths.project_dir()

ini_path = os.path.join(proj_dir, 'Saved', 'Config', 'WindowsEditor', 'EditorPerProjectUserSettings.ini').replace("\\","/")

# -----------------------------
# Read the INI file and store as
# an array variable called 'data'
# -----------------------------

print("*****************************")
print("-----------------------------")
print("AnimPal: Starting to optimize your settings...")

print("AnimPal: Reading INI file 'EditorPerProjectUserSettings.ini'...")

with open(ini_path, "r") as f:
    raw_data = f.read()
    
    section = ""
    lines = raw_data.split("\n")

    for line in lines:
        
        if len(line) == 0:
            continue

        if line.startswith("[") and line.endswith("]"):
            section = line[1:-1]
            data[section] = {}
            continue

        parts = line.split("=", 1)
        key = parts[0]
        value = ""

        if len(parts) > 1:
            value = parts[1]

        if not data[section].get(key):
            data[section][key] = value
            continue
        
        if isinstance(data[section][key], str):
            data[section][key] = [data[section][key]]
            
        data[section][key].append(value)






# ORBIT CAMERA AROUND SELECTION
print("-------")
try:
    # Tries to pull the value, to see if it exists
    orbitcamstatusstr = str(data['/Script/UnrealEd.LevelEditorViewportSettings']['bOrbitCameraAroundSelection'])
    if orbitcamstatusstr == "True":
        print("AnimPal: A setting for 'Orbit camera around selection' was found, and it's set to True. No change will be made.")
    else:
        print("AnimPal: A setting for 'Orbit camera around selection' was found, and it's set to False.")
        data['/Script/UnrealEd.LevelEditorViewportSettings']['bOrbitCameraAroundSelection'] = "True"
        print("AnimPal: Setting change to True.")
except:
    print("AnimPal: There is no current setting for 'Orbit camera around selection'. Adding a setting and making it True.")
    data['/Script/UnrealEd.LevelEditorViewportSettings']['bOrbitCameraAroundSelection'] = "True"
    print("AnimPal: Setting added.")





# -----------------------------
# Write back the INI file:
# -----------------------------
print("-------")
print("AnimPal: Starting to write INI file 'EditorPerProjectUserSettings.ini'.")

deleting_key=False

if deleting_key:
    if section in data:
        data[section].pop(key, None)
else:
    if not section in data:
        data[section] = {}
        
    data[section][key] = value

output = ""

for section_key, section_value in data.items():
    output += "[" + section_key + "]" + "\n"
    
    for item_key, item_value in section_value.items():
        
        if isinstance(item_value, str):
            output += item_key + "=" + item_value + "\n"
            
        elif isinstance(item_value, list):
            for list_item in item_value:
                output += item_key + "=" + list_item + "\n"
                
    output += "\n"

with open(ini_path, "w") as f:
    f.write(output)

print("AnimPal: Completed update of INI file 'EditorPerProjectUserSettings.ini'.")

print("AnimPal: Finished optimizing your settings.")
print("-----------------------------")
print("*****************************")