mirror of
https://github.com/ynput/ayon-core.git
synced 2026-01-01 08:24:53 +01:00
332 lines
12 KiB
Python
332 lines
12 KiB
Python
import json
|
|
import os
|
|
import platform
|
|
import re
|
|
import subprocess
|
|
from distutils import dir_util
|
|
from pathlib import Path
|
|
from typing import List
|
|
|
|
import openpype.hosts.unreal.lib as ue_lib
|
|
|
|
from qtpy import QtCore
|
|
|
|
|
|
def parse_comp_progress(line: str, progress_signal: QtCore.Signal(int)) -> int:
|
|
match = re.search('\[[1-9]+/[0-9]+\]', line)
|
|
if match is not None:
|
|
split: list[str] = match.group().split('/')
|
|
curr: float = float(split[0][1:])
|
|
total: float = float(split[1][:-1])
|
|
progress_signal.emit(int((curr / total) * 100.0))
|
|
|
|
|
|
def parse_prj_progress(line: str, progress_signal: QtCore.Signal(int)) -> int:
|
|
match = re.search('@progress', line)
|
|
if match is not None:
|
|
percent_match = re.search('\d{1,3}', line)
|
|
progress_signal.emit(int(percent_match.group()))
|
|
|
|
|
|
class UEProjectGenerationWorker(QtCore.QObject):
|
|
finished = QtCore.Signal(str)
|
|
failed = QtCore.Signal(str)
|
|
progress = QtCore.Signal(int)
|
|
log = QtCore.Signal(str)
|
|
stage_begin = QtCore.Signal(str)
|
|
|
|
ue_version: str = None
|
|
project_name: str = None
|
|
env = None
|
|
engine_path: Path = None
|
|
project_dir: Path = None
|
|
dev_mode = False
|
|
|
|
def setup(self, ue_version: str,
|
|
project_name,
|
|
engine_path: Path,
|
|
project_dir: Path,
|
|
dev_mode: bool = False,
|
|
env: dict = None):
|
|
|
|
self.ue_version = ue_version
|
|
self.project_dir = project_dir
|
|
self.env = env or os.environ
|
|
|
|
preset = ue_lib.get_project_settings(
|
|
project_name
|
|
)["unreal"]["project_setup"]
|
|
|
|
if dev_mode or preset["dev_mode"]:
|
|
self.dev_mode = True
|
|
|
|
self.project_name = project_name
|
|
self.engine_path = engine_path
|
|
|
|
def run(self):
|
|
# engine_path should be the location of UE_X.X folder
|
|
|
|
ue_editor_exe = ue_lib.get_editor_exe_path(self.engine_path,
|
|
self.ue_version)
|
|
cmdlet_project = ue_lib.get_path_to_cmdlet_project(self.ue_version)
|
|
project_file = self.project_dir / f"{self.project_name}.uproject"
|
|
|
|
print("--- Generating a new project ...")
|
|
# 1st stage
|
|
stage_count = 2
|
|
if self.dev_mode:
|
|
stage_count = 4
|
|
|
|
self.stage_begin.emit(f'Generating a new UE project ... 1 out of '
|
|
f'{stage_count}')
|
|
|
|
commandlet_cmd = [f'{ue_editor_exe.as_posix()}',
|
|
f'{cmdlet_project.as_posix()}',
|
|
f'-run=OPGenerateProject',
|
|
f'{project_file.resolve().as_posix()}']
|
|
|
|
if self.dev_mode:
|
|
commandlet_cmd.append('-GenerateCode')
|
|
|
|
gen_process = subprocess.Popen(commandlet_cmd,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.PIPE)
|
|
|
|
for line in gen_process.stdout:
|
|
decoded_line = line.decode(errors="replace")
|
|
print(decoded_line, end='')
|
|
self.log.emit(decoded_line)
|
|
gen_process.stdout.close()
|
|
return_code = gen_process.wait()
|
|
|
|
if return_code and return_code != 0:
|
|
msg = 'Failed to generate ' + self.project_name \
|
|
+ f' project! Exited with return code {return_code}'
|
|
self.failed.emit(msg, return_code)
|
|
raise RuntimeError(msg)
|
|
|
|
print("--- Project has been generated successfully.")
|
|
self.stage_begin.emit(f'Writing the Engine ID of the build UE ... 1 out'
|
|
f' of {stage_count}')
|
|
|
|
if not project_file.is_file():
|
|
msg = "Failed to write the Engine ID into .uproject file! Can " \
|
|
"not read!"
|
|
self.failed.emit(msg)
|
|
raise RuntimeError(msg)
|
|
|
|
with open(project_file.as_posix(), mode="r+") as pf:
|
|
pf_json = json.load(pf)
|
|
pf_json["EngineAssociation"] = ue_lib.get_build_id(self.engine_path,
|
|
self.ue_version)
|
|
print(pf_json["EngineAssociation"])
|
|
pf.seek(0)
|
|
json.dump(pf_json, pf, indent=4)
|
|
pf.truncate()
|
|
print(f'--- Engine ID has been written into the project file')
|
|
|
|
self.progress.emit(90)
|
|
if self.dev_mode:
|
|
# 2nd stage
|
|
self.stage_begin.emit(f'Generating project files ... 2 out of '
|
|
f'{stage_count}')
|
|
|
|
self.progress.emit(0)
|
|
ubt_path = ue_lib.get_path_to_ubt(self.engine_path, self.ue_version)
|
|
|
|
arch = "Win64"
|
|
if platform.system().lower() == "windows":
|
|
arch = "Win64"
|
|
elif platform.system().lower() == "linux":
|
|
arch = "Linux"
|
|
elif platform.system().lower() == "darwin":
|
|
# we need to test this out
|
|
arch = "Mac"
|
|
|
|
gen_prj_files_cmd = [ubt_path.as_posix(),
|
|
"-projectfiles",
|
|
f"-project={project_file}",
|
|
"-progress"]
|
|
gen_proc = subprocess.Popen(gen_prj_files_cmd,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.PIPE)
|
|
for line in gen_proc.stdout:
|
|
decoded_line: str = line.decode(errors='replace')
|
|
print(decoded_line, end='')
|
|
self.log.emit(decoded_line)
|
|
parse_prj_progress(decoded_line, self.progress)
|
|
|
|
gen_proc.stdout.close()
|
|
return_code = gen_proc.wait()
|
|
|
|
if return_code and return_code != 0:
|
|
msg = 'Failed to generate project files! ' \
|
|
f'Exited with return code {return_code}'
|
|
self.failed.emit(msg, return_code)
|
|
raise RuntimeError(msg)
|
|
|
|
self.stage_begin.emit(f'Building the project ... 3 out of '
|
|
f'{stage_count}')
|
|
self.progress.emit(0)
|
|
# 3rd stage
|
|
build_prj_cmd = [ubt_path.as_posix(),
|
|
f"-ModuleWithSuffix={self.project_name},3555",
|
|
arch,
|
|
"Development",
|
|
"-TargetType=Editor",
|
|
f'-Project={project_file}',
|
|
f'{project_file}',
|
|
"-IgnoreJunk"]
|
|
|
|
build_prj_proc = subprocess.Popen(build_prj_cmd,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.PIPE)
|
|
for line in build_prj_proc.stdout:
|
|
decoded_line: str = line.decode(errors='replace')
|
|
print(decoded_line, end='')
|
|
self.log.emit(decoded_line)
|
|
parse_comp_progress(decoded_line, self.progress)
|
|
|
|
build_prj_proc.stdout.close()
|
|
return_code = build_prj_proc.wait()
|
|
|
|
if return_code and return_code != 0:
|
|
msg = 'Failed to build project! ' \
|
|
f'Exited with return code {return_code}'
|
|
self.failed.emit(msg, return_code)
|
|
raise RuntimeError(msg)
|
|
|
|
# ensure we have PySide2 installed in engine
|
|
|
|
self.progress.emit(0)
|
|
self.stage_begin.emit(f'Checking PySide2 installation... {stage_count} '
|
|
f'out of {stage_count}')
|
|
python_path = None
|
|
if platform.system().lower() == "windows":
|
|
python_path = self.engine_path / ("Engine/Binaries/ThirdParty/"
|
|
"Python3/Win64/python.exe")
|
|
|
|
if platform.system().lower() == "linux":
|
|
python_path = self.engine_path / ("Engine/Binaries/ThirdParty/"
|
|
"Python3/Linux/bin/python3")
|
|
|
|
if platform.system().lower() == "darwin":
|
|
python_path = self.engine_path / ("Engine/Binaries/ThirdParty/"
|
|
"Python3/Mac/bin/python3")
|
|
|
|
if not python_path:
|
|
msg = "Unsupported platform"
|
|
self.failed.emit(msg, 1)
|
|
raise NotImplementedError(msg)
|
|
if not python_path.exists():
|
|
msg = f"Unreal Python not found at {python_path}"
|
|
self.failed.emit(msg, 1)
|
|
raise RuntimeError(msg)
|
|
subprocess.check_call(
|
|
[python_path.as_posix(), "-m", "pip", "install", "pyside2"]
|
|
)
|
|
self.progress.emit(100)
|
|
self.finished.emit("Project successfully built!")
|
|
|
|
|
|
class UEPluginInstallWorker(QtCore.QObject):
|
|
finished = QtCore.Signal(str)
|
|
installing = QtCore.Signal(str)
|
|
failed = QtCore.Signal(str, int)
|
|
progress = QtCore.Signal(int)
|
|
log = QtCore.Signal(str)
|
|
|
|
engine_path: Path = None
|
|
env = None
|
|
|
|
def setup(self, engine_path: Path, env: dict = None, ):
|
|
self.engine_path = engine_path
|
|
self.env = env or os.environ
|
|
|
|
def _build_and_move_plugin(self, plugin_build_path: Path):
|
|
uat_path: Path = ue_lib.get_path_to_uat(self.engine_path)
|
|
src_plugin_dir = Path(self.env.get("OPENPYPE_UNREAL_PLUGIN", ""))
|
|
|
|
if not os.path.isdir(src_plugin_dir):
|
|
msg = "Path to the integration plugin is null!"
|
|
self.failed.emit(msg, 1)
|
|
raise RuntimeError(msg)
|
|
|
|
if not uat_path.is_file():
|
|
msg = "Building failed! Path to UAT is invalid!"
|
|
self.failed.emit(msg, 1)
|
|
raise RuntimeError(msg)
|
|
|
|
temp_dir: Path = src_plugin_dir.parent / "Temp"
|
|
temp_dir.mkdir(exist_ok=True)
|
|
uplugin_path: Path = src_plugin_dir / "OpenPype.uplugin"
|
|
|
|
# in order to successfully build the plugin,
|
|
# It must be built outside the Engine directory and then moved
|
|
build_plugin_cmd: List[str] = [f'{uat_path.as_posix()}',
|
|
'BuildPlugin',
|
|
f'-Plugin={uplugin_path.as_posix()}',
|
|
f'-Package={temp_dir.as_posix()}']
|
|
|
|
build_proc = subprocess.Popen(build_plugin_cmd,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.PIPE)
|
|
for line in build_proc.stdout:
|
|
decoded_line: str = line.decode(errors='replace')
|
|
print(decoded_line, end='')
|
|
self.log.emit(decoded_line)
|
|
parse_comp_progress(decoded_line, self.progress)
|
|
|
|
build_proc.stdout.close()
|
|
return_code = build_proc.wait()
|
|
|
|
if return_code and return_code != 0:
|
|
msg = 'Failed to build plugin' \
|
|
f' project! Exited with return code {return_code}'
|
|
self.failed.emit(msg, return_code)
|
|
raise RuntimeError(msg)
|
|
|
|
# Copy the contents of the 'Temp' dir into the
|
|
# 'OpenPype' directory in the engine
|
|
dir_util.copy_tree(temp_dir.as_posix(),
|
|
plugin_build_path.as_posix())
|
|
|
|
# We need to also copy the config folder.
|
|
# The UAT doesn't include the Config folder in the build
|
|
plugin_install_config_path: Path = plugin_build_path / "Config"
|
|
src_plugin_config_path = src_plugin_dir / "Config"
|
|
|
|
dir_util.copy_tree(src_plugin_config_path.as_posix(),
|
|
plugin_install_config_path.as_posix())
|
|
|
|
dir_util.remove_tree(temp_dir.as_posix())
|
|
|
|
def run(self):
|
|
src_plugin_dir = Path(self.env.get("OPENPYPE_UNREAL_PLUGIN", ""))
|
|
|
|
if not os.path.isdir(src_plugin_dir):
|
|
msg = "Path to the integration plugin is null!"
|
|
self.failed.emit(msg, 1)
|
|
raise RuntimeError(msg)
|
|
|
|
# Create a path to the plugin in the engine
|
|
op_plugin_path = self.engine_path / "Engine/Plugins/Marketplace" \
|
|
"/OpenPype"
|
|
|
|
if not op_plugin_path.is_dir():
|
|
self.installing.emit("Installing and building the plugin ...")
|
|
op_plugin_path.mkdir(parents=True, exist_ok=True)
|
|
|
|
engine_plugin_config_path = op_plugin_path / "Config"
|
|
engine_plugin_config_path.mkdir(exist_ok=True)
|
|
|
|
dir_util._path_created = {}
|
|
|
|
if not (op_plugin_path / "Binaries").is_dir() \
|
|
or not (op_plugin_path / "Intermediate").is_dir():
|
|
self.installing.emit("Building the plugin ...")
|
|
print("--- Building the plugin...")
|
|
|
|
self._build_and_move_plugin(op_plugin_path)
|
|
|
|
self.finished.emit("Plugin successfully installed")
|