mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-24 21:04:40 +01:00
unreal project creation and launching
This commit is contained in:
parent
69c396ec3d
commit
6dfb258151
5 changed files with 411 additions and 16 deletions
|
|
@ -268,7 +268,14 @@ class AppAction(BaseHandler):
|
|||
if application.get("launch_hook"):
|
||||
hook = application.get("launch_hook")
|
||||
self.log.info("launching hook: {}".format(hook))
|
||||
pypelib.execute_hook(application.get("launch_hook"))
|
||||
ret_val = pypelib.execute_hook(
|
||||
application.get("launch_hook"), env=env)
|
||||
if not ret_val:
|
||||
return {
|
||||
'success': False,
|
||||
'message': "Hook didn't finish successfully {0}"
|
||||
.format(self.label)
|
||||
}
|
||||
|
||||
if sys.platform == "win32":
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,82 @@
|
|||
import logging
|
||||
import os
|
||||
|
||||
from pype.lib import PypeHook
|
||||
from pype.unreal import lib as unreal_lib
|
||||
from pypeapp import Logger
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class UnrealPrelaunch(PypeHook):
|
||||
"""
|
||||
This hook will check if current workfile path has Unreal
|
||||
project inside. IF not, it initialize it and finally it pass
|
||||
path to the project by environment variable to Unreal launcher
|
||||
shell script.
|
||||
"""
|
||||
|
||||
def execute(**kwargs):
|
||||
print("I am inside!!!")
|
||||
pass
|
||||
def __init__(self, logger=None):
|
||||
if not logger:
|
||||
self.log = Logger().get_logger(self.__class__.__name__)
|
||||
else:
|
||||
self.log = logger
|
||||
|
||||
self.signature = "( {} )".format(self.__class__.__name__)
|
||||
|
||||
def execute(self, *args, env: dict = None) -> bool:
|
||||
if not env:
|
||||
env = os.environ
|
||||
asset = env["AVALON_ASSET"]
|
||||
task = env["AVALON_TASK"]
|
||||
workdir = env["AVALON_WORKDIR"]
|
||||
engine_version = env["AVALON_APP_NAME"].split("_")[-1]
|
||||
project_name = f"{asset}_{task}"
|
||||
|
||||
# Unreal is sensitive about project names longer then 20 chars
|
||||
if len(project_name) > 20:
|
||||
self.log.warning((f"Project name exceed 20 characters "
|
||||
f"[ {project_name} ]!"))
|
||||
|
||||
# Unreal doesn't accept non alphabet characters at the start
|
||||
# of the project name. This is because project name is then used
|
||||
# in various places inside c++ code and there variable names cannot
|
||||
# start with non-alpha. We append 'P' before project name to solve it.
|
||||
# :scream:
|
||||
if not project_name[:1].isalpha():
|
||||
self.log.warning(f"Project name doesn't start with alphabet "
|
||||
f"character ({project_name}). Appending 'P'")
|
||||
project_name = f"P{project_name}"
|
||||
|
||||
project_path = os.path.join(workdir, project_name)
|
||||
|
||||
self.log.info((f"{self.signature} requested UE4 version: "
|
||||
f"[ {engine_version} ]"))
|
||||
|
||||
detected = unreal_lib.get_engine_versions()
|
||||
detected_str = ', '.join(detected.keys()) or 'none'
|
||||
self.log.info((f"{self.signature} detected UE4 versions: "
|
||||
f"[ {detected_str} ]"))
|
||||
del(detected_str)
|
||||
engine_version = ".".join(engine_version.split(".")[:2])
|
||||
if engine_version not in detected.keys():
|
||||
self.log.error((f"{self.signature} requested version not "
|
||||
f"detected [ {engine_version} ]"))
|
||||
return False
|
||||
|
||||
os.makedirs(project_path, exist_ok=True)
|
||||
|
||||
project_file = os.path.join(project_path, f"{project_name}.uproject")
|
||||
if not os.path.isfile(project_file):
|
||||
self.log.info((f"{self.signature} creating unreal "
|
||||
f"project [ {project_name} ]"))
|
||||
if env.get("AVALON_UNREAL_PLUGIN"):
|
||||
os.environ["AVALON_UNREAL_PLUGIN"] = env.get("AVALON_UNREAL_PLUGIN") # noqa: E501
|
||||
unreal_lib.create_unreal_project(project_name,
|
||||
engine_version, project_path)
|
||||
|
||||
self.log.info((f"{self.signature} preparing unreal project ... "))
|
||||
unreal_lib.prepare_project(project_file, detected[engine_version])
|
||||
|
||||
env["PYPE_UNREAL_PROJECT_FILE"] = project_file
|
||||
return True
|
||||
|
|
|
|||
33
pype/lib.py
33
pype/lib.py
|
|
@ -597,7 +597,20 @@ class CustomNone:
|
|||
return "<CustomNone-{}>".format(str(self.identifier))
|
||||
|
||||
|
||||
def execute_hook(hook, **kwargs):
|
||||
def execute_hook(hook, *args, **kwargs):
|
||||
"""
|
||||
This will load hook file, instantiate class and call `execute` method
|
||||
on it. Hook must be in a form:
|
||||
|
||||
`$PYPE_ROOT/repos/pype/path/to/hook.py/HookClass`
|
||||
|
||||
This will load `hook.py`, instantiate HookClass and then execute_hook
|
||||
`execute(*args, **kwargs)`
|
||||
|
||||
:param hook: path to hook class
|
||||
:type hook: str
|
||||
"""
|
||||
|
||||
class_name = hook.split("/")[-1]
|
||||
|
||||
abspath = os.path.join(os.getenv('PYPE_ROOT'),
|
||||
|
|
@ -606,14 +619,11 @@ def execute_hook(hook, **kwargs):
|
|||
mod_name, mod_ext = os.path.splitext(os.path.basename(abspath))
|
||||
|
||||
if not mod_ext == ".py":
|
||||
return
|
||||
return False
|
||||
|
||||
module = types.ModuleType(mod_name)
|
||||
module.__file__ = abspath
|
||||
|
||||
log.info("-" * 80)
|
||||
print(module)
|
||||
|
||||
try:
|
||||
with open(abspath) as f:
|
||||
six.exec_(f.read(), module.__dict__)
|
||||
|
|
@ -623,13 +633,12 @@ def execute_hook(hook, **kwargs):
|
|||
except Exception as exp:
|
||||
log.exception("loading hook failed: {}".format(exp),
|
||||
exc_info=True)
|
||||
return False
|
||||
|
||||
from pprint import pprint
|
||||
print("-" * 80)
|
||||
pprint(dir(module))
|
||||
|
||||
hook_obj = globals()[class_name]()
|
||||
hook_obj.execute(**kwargs)
|
||||
obj = getattr(module, class_name)
|
||||
hook_obj = obj()
|
||||
ret_val = hook_obj.execute(*args, **kwargs)
|
||||
return ret_val
|
||||
|
||||
|
||||
@six.add_metaclass(ABCMeta)
|
||||
|
|
@ -639,5 +648,5 @@ class PypeHook:
|
|||
pass
|
||||
|
||||
@abstractmethod
|
||||
def execute(**kwargs):
|
||||
def execute(self, *args, **kwargs):
|
||||
pass
|
||||
|
|
|
|||
0
pype/unreal/__init__.py
Normal file
0
pype/unreal/__init__.py
Normal file
305
pype/unreal/lib.py
Normal file
305
pype/unreal/lib.py
Normal file
|
|
@ -0,0 +1,305 @@
|
|||
import os
|
||||
import platform
|
||||
import json
|
||||
from distutils import dir_util
|
||||
import subprocess
|
||||
|
||||
|
||||
def get_engine_versions():
|
||||
"""
|
||||
This will try to detect location and versions of installed Unreal Engine.
|
||||
Location can be overridden by `UNREAL_ENGINE_LOCATION` environment
|
||||
variable.
|
||||
|
||||
Returns dictionary with version as a key and dir as value.
|
||||
"""
|
||||
try:
|
||||
engine_locations = {}
|
||||
root, dirs, files = next(os.walk(os.environ["UNREAL_ENGINE_LOCATION"]))
|
||||
|
||||
for dir in dirs:
|
||||
if dir.startswith("UE_"):
|
||||
ver = dir.split("_")[1]
|
||||
engine_locations[ver] = os.path.join(root, dir)
|
||||
except KeyError:
|
||||
# environment variable not set
|
||||
pass
|
||||
except OSError:
|
||||
# specified directory doesn't exists
|
||||
pass
|
||||
|
||||
# if we've got something, terminate autodetection process
|
||||
if engine_locations:
|
||||
return engine_locations
|
||||
|
||||
# else kick in platform specific detection
|
||||
if platform.system().lower() == "windows":
|
||||
return _win_get_engine_versions()
|
||||
elif platform.system().lower() == "linux":
|
||||
# on linux, there is no installation and getting Unreal Engine involves
|
||||
# git clone. So we'll probably depend on `UNREAL_ENGINE_LOCATION`.
|
||||
pass
|
||||
elif platform.system().lower() == "darwin":
|
||||
return _darwin_get_engine_version()
|
||||
|
||||
return {}
|
||||
|
||||
|
||||
def _win_get_engine_versions():
|
||||
"""
|
||||
If engines are installed via Epic Games Launcher then there is:
|
||||
`%PROGRAMDATA%/Epic/UnrealEngineLauncher/LauncherInstalled.dat`
|
||||
This file is JSON file listing installed stuff, Unreal engines
|
||||
are marked with `"AppName" = "UE_X.XX"`` like `UE_4.24`
|
||||
"""
|
||||
install_json_path = os.path.join(
|
||||
os.environ.get("PROGRAMDATA"),
|
||||
"Epic",
|
||||
"UnrealEngineLauncher",
|
||||
"LauncherInstalled.dat",
|
||||
)
|
||||
|
||||
return _parse_launcher_locations(install_json_path)
|
||||
|
||||
|
||||
def _darwin_get_engine_version():
|
||||
"""
|
||||
It works the same as on Windows, just JSON file location is different.
|
||||
"""
|
||||
install_json_path = os.path.join(
|
||||
os.environ.get("HOME"),
|
||||
"Library",
|
||||
"Application Support",
|
||||
"Epic",
|
||||
"UnrealEngineLauncher",
|
||||
"LauncherInstalled.dat",
|
||||
)
|
||||
|
||||
return _parse_launcher_locations(install_json_path)
|
||||
|
||||
|
||||
def _parse_launcher_locations(install_json_path):
|
||||
engine_locations = {}
|
||||
if os.path.isfile(install_json_path):
|
||||
with open(install_json_path, "r") as ilf:
|
||||
try:
|
||||
install_data = json.load(ilf)
|
||||
except json.JSONDecodeError:
|
||||
raise Exception(
|
||||
"Invalid `LauncherInstalled.dat file. `"
|
||||
"Cannot determine Unreal Engine location."
|
||||
)
|
||||
|
||||
for installation in install_data.get("InstallationList", []):
|
||||
if installation.get("AppName").startswith("UE_"):
|
||||
ver = installation.get("AppName").split("_")[1]
|
||||
engine_locations[ver] = installation.get("InstallLocation")
|
||||
|
||||
return engine_locations
|
||||
|
||||
|
||||
def create_unreal_project(project_name, ue_version, dir):
|
||||
"""
|
||||
This will create `.uproject` file at specified location. As there is no
|
||||
way I know to create project via command line, this is easiest option.
|
||||
Unreal project file is basically JSON file. If we find
|
||||
`AVALON_UNREAL_PLUGIN` environment variable we assume this is location
|
||||
of Avalon Integration Plugin and we copy its content to project folder
|
||||
and enable this plugin.
|
||||
"""
|
||||
|
||||
if os.path.isdir(os.environ.get("AVALON_UNREAL_PLUGIN", "")):
|
||||
# copy plugin to correct path under project
|
||||
plugin_path = os.path.join(dir, "Plugins", "Avalon")
|
||||
if not os.path.isdir(plugin_path):
|
||||
os.makedirs(plugin_path, exist_ok=True)
|
||||
dir_util._path_created = {}
|
||||
dir_util.copy_tree(os.environ.get("AVALON_UNREAL_PLUGIN"),
|
||||
plugin_path)
|
||||
|
||||
data = {
|
||||
"FileVersion": 3,
|
||||
"EngineAssociation": ue_version,
|
||||
"Category": "",
|
||||
"Description": "",
|
||||
"Modules": [
|
||||
{
|
||||
"Name": project_name,
|
||||
"Type": "Runtime",
|
||||
"LoadingPhase": "Default",
|
||||
"AdditionalDependencies": ["Engine"],
|
||||
}
|
||||
],
|
||||
"Plugins": [
|
||||
{"Name": "PythonScriptPlugin", "Enabled": True},
|
||||
{"Name": "EditorScriptingUtilities", "Enabled": True},
|
||||
{"Name": "Avalon", "Enabled": True},
|
||||
],
|
||||
}
|
||||
|
||||
project_file = os.path.join(dir, "{}.uproject".format(project_name))
|
||||
with open(project_file, mode="w") as pf:
|
||||
json.dump(data, pf, indent=4)
|
||||
|
||||
|
||||
def prepare_project(project_file: str, engine_path: str):
|
||||
"""
|
||||
This function will add source files needed for project to be
|
||||
rebuild along with the avalon integration plugin.
|
||||
|
||||
There seems not to be automated way to do it from command line.
|
||||
But there might be way to create at least those target and build files
|
||||
by some generator. This needs more research as manually writing
|
||||
those files is rather hackish. :skull_and_crossbones:
|
||||
|
||||
:param project_file: path to .uproject file
|
||||
:type project_file: str
|
||||
:param engine_path: path to unreal engine associated with project
|
||||
:type engine_path: str
|
||||
"""
|
||||
|
||||
project_name = os.path.splitext(os.path.basename(project_file))[0]
|
||||
project_dir = os.path.dirname(project_file)
|
||||
targets_dir = os.path.join(project_dir, "Source")
|
||||
sources_dir = os.path.join(targets_dir, project_name)
|
||||
|
||||
os.makedirs(sources_dir, exist_ok=True)
|
||||
os.makedirs(os.path.join(project_dir, "Content"), exist_ok=True)
|
||||
|
||||
module_target = '''
|
||||
using UnrealBuildTool;
|
||||
using System.Collections.Generic;
|
||||
|
||||
public class {0}Target : TargetRules
|
||||
{{
|
||||
public {0}Target( TargetInfo Target) : base(Target)
|
||||
{{
|
||||
Type = TargetType.Game;
|
||||
ExtraModuleNames.AddRange( new string[] {{ "{0}" }} );
|
||||
}}
|
||||
}}
|
||||
'''.format(project_name)
|
||||
|
||||
editor_module_target = '''
|
||||
using UnrealBuildTool;
|
||||
using System.Collections.Generic;
|
||||
|
||||
public class {0}EditorTarget : TargetRules
|
||||
{{
|
||||
public {0}EditorTarget( TargetInfo Target) : base(Target)
|
||||
{{
|
||||
Type = TargetType.Editor;
|
||||
|
||||
ExtraModuleNames.AddRange( new string[] {{ "{0}" }} );
|
||||
}}
|
||||
}}
|
||||
'''.format(project_name)
|
||||
|
||||
module_build = '''
|
||||
using UnrealBuildTool;
|
||||
public class {0} : ModuleRules
|
||||
{{
|
||||
public {0}(ReadOnlyTargetRules Target) : base(Target)
|
||||
{{
|
||||
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
|
||||
PublicDependencyModuleNames.AddRange(new string[] {{ "Core",
|
||||
"CoreUObject", "Engine", "InputCore" }});
|
||||
PrivateDependencyModuleNames.AddRange(new string[] {{ }});
|
||||
}}
|
||||
}}
|
||||
'''.format(project_name)
|
||||
|
||||
module_cpp = '''
|
||||
#include "{0}.h"
|
||||
#include "Modules/ModuleManager.h"
|
||||
|
||||
IMPLEMENT_PRIMARY_GAME_MODULE( FDefaultGameModuleImpl, {0}, "{0}" );
|
||||
'''.format(project_name)
|
||||
|
||||
module_header = '''
|
||||
#pragma once
|
||||
#include "CoreMinimal.h"
|
||||
'''
|
||||
|
||||
game_mode_cpp = '''
|
||||
#include "{0}GameModeBase.h"
|
||||
'''.format(project_name)
|
||||
|
||||
game_mode_h = '''
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GameFramework/GameModeBase.h"
|
||||
#include "{0}GameModeBase.generated.h"
|
||||
|
||||
UCLASS()
|
||||
class {1}_API A{0}GameModeBase : public AGameModeBase
|
||||
{{
|
||||
GENERATED_BODY()
|
||||
}};
|
||||
'''.format(project_name, project_name.upper())
|
||||
|
||||
with open(os.path.join(
|
||||
targets_dir, f"{project_name}.Target.cs"), mode="w") as f:
|
||||
f.write(module_target)
|
||||
|
||||
with open(os.path.join(
|
||||
targets_dir, f"{project_name}Editor.Target.cs"), mode="w") as f:
|
||||
f.write(editor_module_target)
|
||||
|
||||
with open(os.path.join(
|
||||
sources_dir, f"{project_name}.Build.cs"), mode="w") as f:
|
||||
f.write(module_build)
|
||||
|
||||
with open(os.path.join(
|
||||
sources_dir, f"{project_name}.cpp"), mode="w") as f:
|
||||
f.write(module_cpp)
|
||||
|
||||
with open(os.path.join(
|
||||
sources_dir, f"{project_name}.h"), mode="w") as f:
|
||||
f.write(module_header)
|
||||
|
||||
with open(os.path.join(
|
||||
sources_dir, f"{project_name}GameModeBase.cpp"), mode="w") as f:
|
||||
f.write(game_mode_cpp)
|
||||
|
||||
with open(os.path.join(
|
||||
sources_dir, f"{project_name}GameModeBase.h"), mode="w") as f:
|
||||
f.write(game_mode_h)
|
||||
|
||||
if platform.system().lower() == "windows":
|
||||
u_build_tool = (f"{engine_path}/Engine/Binaries/DotNET/"
|
||||
"UnrealBuildTool.exe")
|
||||
u_header_tool = (f"{engine_path}/Engine/Binaries/Win64/"
|
||||
f"UnrealHeaderTool.exe")
|
||||
elif platform.system().lower() == "linux":
|
||||
# WARNING: there is no UnrealBuildTool on linux?
|
||||
u_build_tool = ""
|
||||
u_header_tool = ""
|
||||
elif platform.system().lower() == "darwin":
|
||||
# WARNING: there is no UnrealBuildTool on Mac?
|
||||
u_build_tool = ""
|
||||
u_header_tool = ""
|
||||
|
||||
u_build_tool = u_build_tool.replace("\\", "/")
|
||||
u_header_tool = u_header_tool.replace("\\", "/")
|
||||
|
||||
command1 = [u_build_tool, "-projectfiles", f"-project={project_file}",
|
||||
"-progress"]
|
||||
|
||||
subprocess.run(command1)
|
||||
|
||||
command2 = [u_build_tool, f"-ModuleWithSuffix={project_name},3555"
|
||||
"Win64", "Development", "-TargetType=Editor"
|
||||
f'-Project="{project_file}"', f'"{project_file}"'
|
||||
"-IgnoreJunk"]
|
||||
|
||||
subprocess.run(command2)
|
||||
|
||||
uhtmanifest = os.path.join(os.path.dirname(project_file),
|
||||
f"{project_name}.uhtmanifest")
|
||||
|
||||
command3 = [u_header_tool, f'"{project_file}"', f'"{uhtmanifest}"',
|
||||
"-Unattended", "-WarningsAsErrors", "-installed"]
|
||||
|
||||
subprocess.run(command3)
|
||||
Loading…
Add table
Add a link
Reference in a new issue