mirror of
https://github.com/ynput/ayon-core.git
synced 2026-01-01 16:34:53 +01:00
Merge branch 'ynput:develop' into bugfix/OP-5224_Max-removing-an-instance
This commit is contained in:
commit
06b7bc9d70
34 changed files with 2004 additions and 142 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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"],
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)):
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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."""
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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'):
|
||||
|
|
|
|||
335
openpype/hosts/unreal/ue_workers.py
Normal file
335
openpype/hosts/unreal/ue_workers.py
Normal 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")
|
||||
Loading…
Add table
Add a link
Reference in a new issue