Merge branch 'ynput:develop' into bugfix/OP-5224_Max-removing-an-instance

This commit is contained in:
Kayla Man 2023-03-14 10:20:50 +01:00 committed by GitHub
commit 06b7bc9d70
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
34 changed files with 2004 additions and 142 deletions

View file

@ -39,3 +39,5 @@ class CollectFusionCompFrameRanges(pyblish.api.ContextPlugin):
context.data["frameEnd"] = int(end)
context.data["frameStartHandle"] = int(global_start)
context.data["frameEndHandle"] = int(global_end)
context.data["handleStart"] = int(start) - int(global_start)
context.data["handleEnd"] = int(global_end) - int(end)

View file

@ -39,6 +39,8 @@ class CollectInstanceData(pyblish.api.InstancePlugin):
"frameEnd": context.data["frameEnd"],
"frameStartHandle": context.data["frameStartHandle"],
"frameEndHandle": context.data["frameStartHandle"],
"handleStart": context.data["handleStart"],
"handleEnd": context.data["handleEnd"],
"fps": context.data["fps"],
})

View file

@ -10,7 +10,7 @@
import hou
from openpype.tools.utils import host_tools
parent = hou.qt.mainWindow()
host_tools.show_creator(parent)
host_tools.show_publisher(parent, tab="create")
]]></scriptCode>
</scriptItem>
@ -30,7 +30,7 @@ host_tools.show_loader(parent=parent, use_context=True)
import hou
from openpype.tools.utils import host_tools
parent = hou.qt.mainWindow()
host_tools.show_publisher(parent)
host_tools.show_publisher(parent, tab="publish")
]]></scriptCode>
</scriptItem>

View file

@ -3576,6 +3576,65 @@ def get_color_management_output_transform():
return colorspace
def image_info(file_path):
# type: (str) -> dict
"""Based on tha texture path, get its bit depth and format information.
Take reference from makeTx.py in Arnold:
ImageInfo(filename): Get Image Information for colorspace
AiTextureGetFormat(filename): Get Texture Format
AiTextureGetBitDepth(filename): Get Texture bit depth
Args:
file_path (str): Path to the texture file.
Returns:
dict: Dictionary with the information about the texture file.
"""
from arnold import (
AiTextureGetBitDepth,
AiTextureGetFormat
)
# Get Texture Information
img_info = {'filename': file_path}
if os.path.isfile(file_path):
img_info['bit_depth'] = AiTextureGetBitDepth(file_path) # noqa
img_info['format'] = AiTextureGetFormat(file_path) # noqa
else:
img_info['bit_depth'] = 8
img_info['format'] = "unknown"
return img_info
def guess_colorspace(img_info):
# type: (dict) -> str
"""Guess the colorspace of the input image filename.
Note:
Reference from makeTx.py
Args:
img_info (dict): Image info generated by :func:`image_info`
Returns:
str: color space name use in the `--colorconvert`
option of maketx.
"""
from arnold import (
AiTextureInvalidate,
# types
AI_TYPE_BYTE,
AI_TYPE_INT,
AI_TYPE_UINT
)
try:
if img_info['bit_depth'] <= 16:
if img_info['format'] in (AI_TYPE_BYTE, AI_TYPE_INT, AI_TYPE_UINT): # noqa
return 'sRGB'
else:
return 'linear'
# now discard the image file as AiTextureGetFormat has loaded it
AiTextureInvalidate(img_info['filename']) # noqa
except ValueError:
print(("[maketx] Error: Could not guess"
"colorspace for {}").format(img_info["filename"]))
return "linear"
def len_flattened(components):
"""Return the length of the list as if it was flattened.

View file

@ -336,7 +336,8 @@ class RenderSettings(object):
)
# Set render file format to exr
cmds.setAttr("{}.imageFormatStr".format(node), "exr", type="string")
ext = vray_render_presets["image_format"]
cmds.setAttr("{}.imageFormatStr".format(node), ext, type="string")
# animType
cmds.setAttr("{}.animType".format(node), 1)

View file

@ -16,6 +16,7 @@ import pyblish.api
from openpype.lib import source_hash, run_subprocess
from openpype.pipeline import legacy_io, publish
from openpype.hosts.maya.api import lib
from openpype.hosts.maya.api.lib import image_info, guess_colorspace
# Modes for transfer
COPY = 1
@ -367,16 +368,25 @@ class ExtractLook(publish.Extractor):
for filepath in files_metadata:
linearize = False
if do_maketx and files_metadata[filepath]["color_space"].lower() == "srgb": # noqa: E501
linearize = True
# set its file node to 'raw' as tx will be linearized
files_metadata[filepath]["color_space"] = "Raw"
# if OCIO color management enabled
# it won't take the condition of the files_metadata
ocio_maya = cmds.colorManagementPrefs(q=True,
cmConfigFileEnabled=True,
cmEnabled=True)
if do_maketx and not ocio_maya:
if files_metadata[filepath]["color_space"].lower() == "srgb": # noqa: E501
linearize = True
# set its file node to 'raw' as tx will be linearized
files_metadata[filepath]["color_space"] = "Raw"
# if do_maketx:
# color_space = "Raw"
source, mode, texture_hash = self._process_texture(
filepath,
resource,
do_maketx,
staging=staging_dir,
linearize=linearize,
@ -482,7 +492,8 @@ class ExtractLook(publish.Extractor):
resources_dir, basename + ext
)
def _process_texture(self, filepath, do_maketx, staging, linearize, force):
def _process_texture(self, filepath, resource,
do_maketx, staging, linearize, force):
"""Process a single texture file on disk for publishing.
This will:
1. Check whether it's already published, if so it will do hardlink
@ -524,10 +535,47 @@ class ExtractLook(publish.Extractor):
texture_hash
]
if linearize:
self.log.info("tx: converting sRGB -> linear")
additional_args.extend(["--colorconvert", "sRGB", "linear"])
if cmds.colorManagementPrefs(query=True, cmEnabled=True):
render_colorspace = cmds.colorManagementPrefs(query=True,
renderingSpaceName=True) # noqa
config_path = cmds.colorManagementPrefs(query=True,
configFilePath=True) # noqa
if not os.path.exists(config_path):
raise RuntimeError("No OCIO config path found!")
color_space_attr = resource["node"] + ".colorSpace"
try:
color_space = cmds.getAttr(color_space_attr)
except ValueError:
# node doesn't have color space attribute
if cmds.loadPlugin("mtoa", quiet=True):
img_info = image_info(filepath)
color_space = guess_colorspace(img_info)
else:
color_space = "Raw"
self.log.info("tx: converting {0} -> {1}".format(color_space, render_colorspace)) # noqa
additional_args.extend(["--colorconvert",
color_space,
render_colorspace])
else:
if cmds.loadPlugin("mtoa", quiet=True):
img_info = image_info(filepath)
color_space = guess_colorspace(img_info)
if color_space == "sRGB":
self.log.info("tx: converting sRGB -> linear")
additional_args.extend(["--colorconvert",
"sRGB",
"Raw"])
else:
self.log.info("tx: texture's colorspace "
"is already linear")
else:
self.log.warning("cannot guess the colorspace"
"color conversion won't be available!") # noqa
config_path = get_ocio_config_path("nuke-default")
additional_args.extend(["--colorconfig", config_path])
# Ensure folder exists
if not os.path.exists(os.path.dirname(converted)):

View file

@ -0,0 +1,26 @@
from maya import cmds
import pyblish.api
from openpype.pipeline.publish import ValidateContentsOrder
from openpype.pipeline import PublishValidationError
class ValidateMayaColorSpace(pyblish.api.InstancePlugin):
"""
Check if the OCIO Color Management and maketx options
enabled at the same time
"""
order = ValidateContentsOrder
families = ['look']
hosts = ['maya']
label = 'Color Management with maketx'
def process(self, instance):
ocio_maya = cmds.colorManagementPrefs(q=True,
cmConfigFileEnabled=True,
cmEnabled=True)
maketx = instance.data["maketx"]
if ocio_maya and maketx:
raise PublishValidationError("Maya is color managed and maketx option is on. OpenPype doesn't support this combination yet.") # noqa

View file

@ -2861,10 +2861,10 @@ class NukeDirmap(HostDirmap):
pass
def dirmap_routine(self, source_path, destination_path):
log.debug("{}: {}->{}".format(self.file_name,
source_path, destination_path))
source_path = source_path.lower().replace(os.sep, '/')
destination_path = destination_path.lower().replace(os.sep, '/')
log.debug("Map: {} with: {}->{}".format(self.file_name,
source_path, destination_path))
if platform.system().lower() == "windows":
self.file_name = self.file_name.lower().replace(
source_path, destination_path)
@ -2878,6 +2878,7 @@ class DirmapCache:
_project_name = None
_project_settings = None
_sync_module = None
_mapping = None
@classmethod
def project_name(cls):
@ -2897,6 +2898,36 @@ class DirmapCache:
cls._sync_module = ModulesManager().modules_by_name["sync_server"]
return cls._sync_module
@classmethod
def mapping(cls):
return cls._mapping
@classmethod
def set_mapping(cls, mapping):
cls._mapping = mapping
def dirmap_file_name_filter(file_name):
"""Nuke callback function with single full path argument.
Checks project settings for potential mapping from source to dest.
"""
dirmap_processor = NukeDirmap(
file_name,
"nuke",
DirmapCache.project_name(),
DirmapCache.project_settings(),
DirmapCache.sync_module(),
)
if not DirmapCache.mapping():
DirmapCache.set_mapping(dirmap_processor.get_mappings())
dirmap_processor.process_dirmap(DirmapCache.mapping())
if os.path.exists(dirmap_processor.file_name):
return dirmap_processor.file_name
return file_name
@contextlib.contextmanager
def node_tempfile():
@ -2942,25 +2973,6 @@ def duplicate_node(node):
return dupli_node
def dirmap_file_name_filter(file_name):
"""Nuke callback function with single full path argument.
Checks project settings for potential mapping from source to dest.
"""
dirmap_processor = NukeDirmap(
file_name,
"nuke",
DirmapCache.project_name(),
DirmapCache.project_settings(),
DirmapCache.sync_module(),
)
dirmap_processor.process_dirmap()
if os.path.exists(dirmap_processor.file_name):
return dirmap_processor.file_name
return file_name
def get_group_io_nodes(nodes):
"""Get the input and the output of a group of nodes."""

View file

@ -3,7 +3,14 @@
import os
import copy
from pathlib import Path
from openpype.widgets.splash_screen import SplashScreen
from qtpy import QtCore
from openpype.hosts.unreal.ue_workers import (
UEProjectGenerationWorker,
UEPluginInstallWorker
)
from openpype import resources
from openpype.lib import (
PreLaunchHook,
ApplicationLaunchFailed,
@ -22,6 +29,7 @@ class UnrealPrelaunchHook(PreLaunchHook):
shell script.
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@ -58,6 +66,78 @@ class UnrealPrelaunchHook(PreLaunchHook):
# Return filename
return filled_anatomy[workfile_template_key]["file"]
def exec_plugin_install(self, engine_path: Path, env: dict = None):
# set up the QThread and worker with necessary signals
env = env or os.environ
q_thread = QtCore.QThread()
ue_plugin_worker = UEPluginInstallWorker()
q_thread.started.connect(ue_plugin_worker.run)
ue_plugin_worker.setup(engine_path, env)
ue_plugin_worker.moveToThread(q_thread)
splash_screen = SplashScreen(
"Installing plugin",
resources.get_resource("app_icons", "ue4.png")
)
# set up the splash screen with necessary triggers
ue_plugin_worker.installing.connect(
splash_screen.update_top_label_text
)
ue_plugin_worker.progress.connect(splash_screen.update_progress)
ue_plugin_worker.log.connect(splash_screen.append_log)
ue_plugin_worker.finished.connect(splash_screen.quit_and_close)
ue_plugin_worker.failed.connect(splash_screen.fail)
splash_screen.start_thread(q_thread)
splash_screen.show_ui()
if not splash_screen.was_proc_successful():
raise ApplicationLaunchFailed("Couldn't run the application! "
"Plugin failed to install!")
def exec_ue_project_gen(self,
engine_version: str,
unreal_project_name: str,
engine_path: Path,
project_dir: Path):
self.log.info((
f"{self.signature} Creating unreal "
f"project [ {unreal_project_name} ]"
))
q_thread = QtCore.QThread()
ue_project_worker = UEProjectGenerationWorker()
ue_project_worker.setup(
engine_version,
unreal_project_name,
engine_path,
project_dir
)
ue_project_worker.moveToThread(q_thread)
q_thread.started.connect(ue_project_worker.run)
splash_screen = SplashScreen(
"Initializing UE project",
resources.get_resource("app_icons", "ue4.png")
)
ue_project_worker.stage_begin.connect(
splash_screen.update_top_label_text
)
ue_project_worker.progress.connect(splash_screen.update_progress)
ue_project_worker.log.connect(splash_screen.append_log)
ue_project_worker.finished.connect(splash_screen.quit_and_close)
ue_project_worker.failed.connect(splash_screen.fail)
splash_screen.start_thread(q_thread)
splash_screen.show_ui()
if not splash_screen.was_proc_successful():
raise ApplicationLaunchFailed("Couldn't run the application! "
"Failed to generate the project!")
def execute(self):
"""Hook entry method."""
workdir = self.launch_context.env["AVALON_WORKDIR"]
@ -137,23 +217,18 @@ class UnrealPrelaunchHook(PreLaunchHook):
if self.launch_context.env.get(env_key):
os.environ[env_key] = self.launch_context.env[env_key]
engine_path = detected[engine_version]
engine_path: Path = Path(detected[engine_version])
unreal_lib.try_installing_plugin(Path(engine_path), os.environ)
if not unreal_lib.check_plugin_existence(engine_path):
self.exec_plugin_install(engine_path)
project_file = project_path / unreal_project_filename
if not project_file.is_file():
self.log.info((
f"{self.signature} creating unreal "
f"project [ {unreal_project_name} ]"
))
unreal_lib.create_unreal_project(
unreal_project_name,
engine_version,
project_path,
engine_path=Path(engine_path)
)
if not project_file.is_file():
self.exec_ue_project_gen(engine_version,
unreal_project_name,
engine_path,
project_path)
self.launch_context.env["OPENPYPE_UNREAL_VERSION"] = engine_version
# Append project file to launch arguments

View file

@ -30,7 +30,7 @@ void UAssetContainer::OnAssetAdded(const FAssetData& AssetData)
// get asset path and class
FString assetPath = AssetData.GetFullName();
FString assetFName = AssetData.AssetClassPath.ToString();
FString assetFName = AssetData.ObjectPath.ToString();
UE_LOG(LogTemp, Log, TEXT("asset name %s"), *assetFName);
// split path
assetPath.ParseIntoArray(split, TEXT(" "), true);
@ -60,7 +60,7 @@ void UAssetContainer::OnAssetRemoved(const FAssetData& AssetData)
// get asset path and class
FString assetPath = AssetData.GetFullName();
FString assetFName = AssetData.AssetClassPath.ToString();
FString assetFName = AssetData.ObjectPath.ToString();
// split path
assetPath.ParseIntoArray(split, TEXT(" "), true);
@ -93,7 +93,7 @@ void UAssetContainer::OnAssetRenamed(const FAssetData& AssetData, const FString&
// get asset path and class
FString assetPath = AssetData.GetFullName();
FString assetFName = AssetData.AssetClassPath.ToString();
FString assetFName = AssetData.ObjectPath.ToString();
// split path
assetPath.ParseIntoArray(split, TEXT(" "), true);

View file

@ -252,7 +252,7 @@ def create_unreal_project(project_name: str,
with open(project_file.as_posix(), mode="r+") as pf:
pf_json = json.load(pf)
pf_json["EngineAssociation"] = _get_build_id(engine_path, ue_version)
pf_json["EngineAssociation"] = get_build_id(engine_path, ue_version)
pf.seek(0)
json.dump(pf_json, pf, indent=4)
pf.truncate()
@ -338,7 +338,7 @@ def get_path_to_ubt(engine_path: Path, ue_version: str) -> Path:
return Path(u_build_tool_path)
def _get_build_id(engine_path: Path, ue_version: str) -> str:
def get_build_id(engine_path: Path, ue_version: str) -> str:
ue_modules = Path()
if platform.system().lower() == "windows":
ue_modules_path = engine_path / "Engine/Binaries/Win64"
@ -365,6 +365,26 @@ def _get_build_id(engine_path: Path, ue_version: str) -> str:
return "{" + loaded_modules.get("BuildId") + "}"
def check_plugin_existence(engine_path: Path, env: dict = None) -> bool:
env = env or os.environ
integration_plugin_path: Path = Path(env.get("OPENPYPE_UNREAL_PLUGIN", ""))
if not os.path.isdir(integration_plugin_path):
raise RuntimeError("Path to the integration plugin is null!")
# Create a path to the plugin in the engine
op_plugin_path: Path = engine_path / "Engine/Plugins/Marketplace/OpenPype"
if not op_plugin_path.is_dir():
return False
if not (op_plugin_path / "Binaries").is_dir() \
or not (op_plugin_path / "Intermediate").is_dir():
return False
return True
def try_installing_plugin(engine_path: Path, env: dict = None) -> None:
env = env or os.environ
@ -377,7 +397,6 @@ def try_installing_plugin(engine_path: Path, env: dict = None) -> None:
op_plugin_path: Path = engine_path / "Engine/Plugins/Marketplace/OpenPype"
if not op_plugin_path.is_dir():
print("--- OpenPype Plugin is not present. Installing ...")
op_plugin_path.mkdir(parents=True, exist_ok=True)
engine_plugin_config_path: Path = op_plugin_path / "Config"
@ -387,7 +406,6 @@ def try_installing_plugin(engine_path: Path, env: dict = None) -> None:
if not (op_plugin_path / "Binaries").is_dir() \
or not (op_plugin_path / "Intermediate").is_dir():
print("--- Binaries are not present. Building the plugin ...")
_build_and_move_plugin(engine_path, op_plugin_path, env)

View file

@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
"""Loader for layouts."""
import json
import collections
from pathlib import Path
import unreal
@ -12,9 +13,7 @@ from unreal import FBXImportType
from unreal import MovieSceneLevelVisibilityTrack
from unreal import MovieSceneSubTrack
from bson.objectid import ObjectId
from openpype.client import get_asset_by_name, get_assets
from openpype.client import get_asset_by_name, get_assets, get_representations
from openpype.pipeline import (
discover_loader_plugins,
loaders_from_representation,
@ -410,6 +409,30 @@ class LayoutLoader(plugin.Loader):
return sequence, (min_frame, max_frame)
def _get_repre_docs_by_version_id(self, data):
version_ids = {
element.get("version")
for element in data
if element.get("representation")
}
version_ids.discard(None)
output = collections.defaultdict(list)
if not version_ids:
return output
project_name = legacy_io.active_project()
repre_docs = get_representations(
project_name,
representation_names=["fbx", "abc"],
version_ids=version_ids,
fields=["_id", "parent", "name"]
)
for repre_doc in repre_docs:
version_id = str(repre_doc["parent"])
output[version_id].append(repre_doc)
return output
def _process(self, lib_path, asset_dir, sequence, repr_loaded=None):
ar = unreal.AssetRegistryHelpers.get_asset_registry()
@ -429,31 +452,21 @@ class LayoutLoader(plugin.Loader):
loaded_assets = []
repre_docs_by_version_id = self._get_repre_docs_by_version_id(data)
for element in data:
representation = None
repr_format = None
if element.get('representation'):
# representation = element.get('representation')
self.log.info(element.get("version"))
valid_formats = ['fbx', 'abc']
repr_data = legacy_io.find_one({
"type": "representation",
"parent": ObjectId(element.get("version")),
"name": {"$in": valid_formats}
})
repr_format = repr_data.get('name')
if not repr_data:
repre_docs = repre_docs_by_version_id[element.get("version")]
if not repre_docs:
self.log.error(
f"No valid representation found for version "
f"{element.get('version')}")
continue
repre_doc = repre_docs[0]
representation = str(repre_doc["_id"])
repr_format = repre_doc["name"]
representation = str(repr_data.get('_id'))
print(representation)
# This is to keep compatibility with old versions of the
# json format.
elif element.get('reference_fbx'):

View file

@ -0,0 +1,335 @@
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'
f' out 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")