Merge pull request #3122 from pypeclub/feature/OP-3130_unreal-5-support

Support for Unreal 5
This commit is contained in:
Ondřej Samohel 2022-05-27 10:12:51 +02:00 committed by GitHub
commit 4b8e7e1ed0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
60 changed files with 984 additions and 74 deletions

View file

@ -1,15 +1,19 @@
import os
import openpype.hosts
from openpype.lib.applications import Application
def add_implementation_envs(env, _app):
def add_implementation_envs(env: dict, _app: Application) -> None:
"""Modify environments to contain all required for implementation."""
# Set OPENPYPE_UNREAL_PLUGIN required for Unreal implementation
ue_plugin = "UE_5.0" if _app.name[:1] == "5" else "UE_4.7"
unreal_plugin_path = os.path.join(
os.path.dirname(os.path.abspath(openpype.hosts.__file__)),
"unreal", "integration"
"unreal", "integration", ue_plugin
)
env["OPENPYPE_UNREAL_PLUGIN"] = unreal_plugin_path
if not env.get("OPENPYPE_UNREAL_PLUGIN"):
env["OPENPYPE_UNREAL_PLUGIN"] = unreal_plugin_path
# Set default environments if are not set via settings
defaults = {

View file

@ -25,7 +25,7 @@ class UnrealPrelaunchHook(PreLaunchHook):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.signature = "( {} )".format(self.__class__.__name__)
self.signature = f"( {self.__class__.__name__} )"
def _get_work_filename(self):
# Use last workfile if was found
@ -71,7 +71,7 @@ class UnrealPrelaunchHook(PreLaunchHook):
if int(engine_version.split(".")[0]) < 4 and \
int(engine_version.split(".")[1]) < 26:
raise ApplicationLaunchFailed((
f"{self.signature} Old unsupported version of UE4 "
f"{self.signature} Old unsupported version of UE "
f"detected - {engine_version}"))
except ValueError:
# there can be string in minor version and in that case
@ -99,18 +99,19 @@ class UnrealPrelaunchHook(PreLaunchHook):
f"character ({unreal_project_name}). Appending 'P'"
))
unreal_project_name = f"P{unreal_project_name}"
unreal_project_filename = f'{unreal_project_name}.uproject'
project_path = Path(os.path.join(workdir, unreal_project_name))
self.log.info((
f"{self.signature} requested UE4 version: "
f"{self.signature} requested UE version: "
f"[ {engine_version} ]"
))
detected = unreal_lib.get_engine_versions(self.launch_context.env)
detected_str = ', '.join(detected.keys()) or 'none'
self.log.info((
f"{self.signature} detected UE4 versions: "
f"{self.signature} detected UE versions: "
f"[ {detected_str} ]"
))
if not detected:
@ -123,10 +124,10 @@ class UnrealPrelaunchHook(PreLaunchHook):
f"detected [ {engine_version} ]"
))
ue4_path = unreal_lib.get_editor_executable_path(
Path(detected[engine_version]))
ue_path = unreal_lib.get_editor_executable_path(
Path(detected[engine_version]), engine_version)
self.launch_context.launch_args = [ue4_path.as_posix()]
self.launch_context.launch_args = [ue_path.as_posix()]
project_path.mkdir(parents=True, exist_ok=True)
project_file = project_path / unreal_project_filename
@ -138,6 +139,11 @@ class UnrealPrelaunchHook(PreLaunchHook):
))
# Set "OPENPYPE_UNREAL_PLUGIN" to current process environment for
# execution of `create_unreal_project`
if self.launch_context.env.get("OPENPYPE_UNREAL_PLUGIN"):
self.log.info((
f"{self.signature} using OpenPype plugin from "
f"{self.launch_context.env.get('OPENPYPE_UNREAL_PLUGIN')}"
))
env_key = "OPENPYPE_UNREAL_PLUGIN"
if self.launch_context.env.get(env_key):
os.environ[env_key] = self.launch_context.env[env_key]

View file

@ -1,4 +1,4 @@
# OpenPype Unreal Integration plugin
# OpenPype Unreal Integration plugin - UE 4.x
This is plugin for Unreal Editor, creating menu for [OpenPype](https://github.com/getavalon) tools to run.

View file

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 84 KiB

After

Width:  |  Height:  |  Size: 84 KiB

Before After
Before After

View file

@ -0,0 +1,35 @@
# Prerequisites
*.d
# Compiled Object files
*.slo
*.lo
*.o
*.obj
# Precompiled Headers
*.gch
*.pch
# Compiled Dynamic libraries
*.so
*.dylib
*.dll
# Fortran module files
*.mod
*.smod
# Compiled Static libraries
*.lai
*.la
*.a
*.lib
# Executables
*.exe
*.out
*.app
/Binaries
/Intermediate

View file

@ -0,0 +1,28 @@
import unreal
openpype_detected = True
try:
from openpype.pipeline import install_host
from openpype.hosts.unreal import api as openpype_host
except ImportError as exc:
openpype_host = None
openpype_detected = False
unreal.log_error("OpenPype: cannot load OpenPype [ {} ]".format(exc))
if openpype_detected:
install_host(openpype_host)
@unreal.uclass()
class OpenPypeIntegration(unreal.OpenPypePythonBridge):
@unreal.ufunction(override=True)
def RunInPython_Popup(self):
unreal.log_warning("OpenPype: showing tools popup")
if openpype_detected:
openpype_host.show_tools_popup()
@unreal.ufunction(override=True)
def RunInPython_Dialog(self):
unreal.log_warning("OpenPype: showing tools dialog")
if openpype_detected:
openpype_host.show_tools_dialog()

View file

@ -0,0 +1,24 @@
{
"FileVersion": 3,
"Version": 1,
"VersionName": "1.0",
"FriendlyName": "OpenPype",
"Description": "OpenPype Integration",
"Category": "OpenPype.Integration",
"CreatedBy": "Ondrej Samohel",
"CreatedByURL": "https://openpype.io",
"DocsURL": "https://openpype.io/docs/artist_hosts_unreal",
"MarketplaceURL": "",
"SupportURL": "https://pype.club/",
"CanContainContent": true,
"IsBetaVersion": true,
"IsExperimentalVersion": false,
"Installed": false,
"Modules": [
{
"Name": "OpenPype",
"Type": "Editor",
"LoadingPhase": "Default"
}
]
}

View file

@ -0,0 +1,11 @@
# OpenPype Unreal Integration plugin - UE 5.x
This is plugin for Unreal Editor, creating menu for [OpenPype](https://github.com/getavalon) tools to run.
## How does this work
Plugin is creating basic menu items in **Window/OpenPype** section of Unreal Editor main menu and a button
on the main toolbar with associated menu. Clicking on those menu items is calling callbacks that are
declared in C++ but needs to be implemented during Unreal Editor
startup in `Plugins/OpenPype/Content/Python/init_unreal.py` - this should be executed by Unreal Editor
automatically.

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

View file

@ -0,0 +1,59 @@
// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
using UnrealBuildTool;
public class OpenPype : ModuleRules
{
public OpenPype(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
PublicIncludePaths.AddRange(
new string[] {
// ... add public include paths required here ...
}
);
PrivateIncludePaths.AddRange(
new string[] {
// ... add other private include paths required here ...
}
);
PublicDependencyModuleNames.AddRange(
new string[]
{
"Core",
// ... add other public dependencies that you statically link with here ...
}
);
PrivateDependencyModuleNames.AddRange(
new string[]
{
"Projects",
"InputCore",
"EditorFramework",
"UnrealEd",
"ToolMenus",
"LevelEditor",
"CoreUObject",
"Engine",
"Slate",
"SlateCore",
// ... add private dependencies that you statically link with here ...
}
);
DynamicallyLoadedModuleNames.AddRange(
new string[]
{
// ... add any modules that your module loads dynamically here ...
}
);
}
}

View file

@ -0,0 +1,115 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "AssetContainer.h"
#include "AssetRegistryModule.h"
#include "Misc/PackageName.h"
#include "Engine.h"
#include "Containers/UnrealString.h"
UAssetContainer::UAssetContainer(const FObjectInitializer& ObjectInitializer)
: UAssetUserData(ObjectInitializer)
{
FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>("AssetRegistry");
FString path = UAssetContainer::GetPathName();
UE_LOG(LogTemp, Warning, TEXT("UAssetContainer %s"), *path);
FARFilter Filter;
Filter.PackagePaths.Add(FName(*path));
AssetRegistryModule.Get().OnAssetAdded().AddUObject(this, &UAssetContainer::OnAssetAdded);
AssetRegistryModule.Get().OnAssetRemoved().AddUObject(this, &UAssetContainer::OnAssetRemoved);
AssetRegistryModule.Get().OnAssetRenamed().AddUObject(this, &UAssetContainer::OnAssetRenamed);
}
void UAssetContainer::OnAssetAdded(const FAssetData& AssetData)
{
TArray<FString> split;
// get directory of current container
FString selfFullPath = UAssetContainer::GetPathName();
FString selfDir = FPackageName::GetLongPackagePath(*selfFullPath);
// get asset path and class
FString assetPath = AssetData.GetFullName();
FString assetFName = AssetData.AssetClass.ToString();
// split path
assetPath.ParseIntoArray(split, TEXT(" "), true);
FString assetDir = FPackageName::GetLongPackagePath(*split[1]);
// take interest only in paths starting with path of current container
if (assetDir.StartsWith(*selfDir))
{
// exclude self
if (assetFName != "AssetContainer")
{
assets.Add(assetPath);
assetsData.Add(AssetData);
UE_LOG(LogTemp, Log, TEXT("%s: asset added to %s"), *selfFullPath, *selfDir);
}
}
}
void UAssetContainer::OnAssetRemoved(const FAssetData& AssetData)
{
TArray<FString> split;
// get directory of current container
FString selfFullPath = UAssetContainer::GetPathName();
FString selfDir = FPackageName::GetLongPackagePath(*selfFullPath);
// get asset path and class
FString assetPath = AssetData.GetFullName();
FString assetFName = AssetData.AssetClass.ToString();
// split path
assetPath.ParseIntoArray(split, TEXT(" "), true);
FString assetDir = FPackageName::GetLongPackagePath(*split[1]);
// take interest only in paths starting with path of current container
FString path = UAssetContainer::GetPathName();
FString lpp = FPackageName::GetLongPackagePath(*path);
if (assetDir.StartsWith(*selfDir))
{
// exclude self
if (assetFName != "AssetContainer")
{
// UE_LOG(LogTemp, Warning, TEXT("%s: asset removed"), *lpp);
assets.Remove(assetPath);
assetsData.Remove(AssetData);
}
}
}
void UAssetContainer::OnAssetRenamed(const FAssetData& AssetData, const FString& str)
{
TArray<FString> split;
// get directory of current container
FString selfFullPath = UAssetContainer::GetPathName();
FString selfDir = FPackageName::GetLongPackagePath(*selfFullPath);
// get asset path and class
FString assetPath = AssetData.GetFullName();
FString assetFName = AssetData.AssetClass.ToString();
// split path
assetPath.ParseIntoArray(split, TEXT(" "), true);
FString assetDir = FPackageName::GetLongPackagePath(*split[1]);
if (assetDir.StartsWith(*selfDir))
{
// exclude self
if (assetFName != "AssetContainer")
{
assets.Remove(str);
assets.Add(assetPath);
assetsData.Remove(AssetData);
// UE_LOG(LogTemp, Warning, TEXT("%s: asset renamed %s"), *lpp, *str);
}
}
}

View file

@ -0,0 +1,20 @@
#include "AssetContainerFactory.h"
#include "AssetContainer.h"
UAssetContainerFactory::UAssetContainerFactory(const FObjectInitializer& ObjectInitializer)
: UFactory(ObjectInitializer)
{
SupportedClass = UAssetContainer::StaticClass();
bCreateNew = false;
bEditorImport = true;
}
UObject* UAssetContainerFactory::FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn)
{
UAssetContainer* AssetContainer = NewObject<UAssetContainer>(InParent, Class, Name, Flags);
return AssetContainer;
}
bool UAssetContainerFactory::ShouldShowInNewMenu() const {
return false;
}

View file

@ -0,0 +1,85 @@
#include "OpenPype.h"
#include "OpenPypeStyle.h"
#include "OpenPypeCommands.h"
#include "OpenPypePythonBridge.h"
#include "LevelEditor.h"
#include "Misc/MessageDialog.h"
#include "ToolMenus.h"
static const FName OpenPypeTabName("OpenPype");
#define LOCTEXT_NAMESPACE "FOpenPypeModule"
// This function is triggered when the plugin is staring up
void FOpenPypeModule::StartupModule()
{
FOpenPypeStyle::Initialize();
FOpenPypeStyle::ReloadTextures();
FOpenPypeCommands::Register();
PluginCommands = MakeShareable(new FUICommandList);
PluginCommands->MapAction(
FOpenPypeCommands::Get().OpenPypeTools,
FExecuteAction::CreateRaw(this, &FOpenPypeModule::MenuPopup),
FCanExecuteAction());
PluginCommands->MapAction(
FOpenPypeCommands::Get().OpenPypeToolsDialog,
FExecuteAction::CreateRaw(this, &FOpenPypeModule::MenuDialog),
FCanExecuteAction());
UToolMenus::RegisterStartupCallback(FSimpleMulticastDelegate::FDelegate::CreateRaw(this, &FOpenPypeModule::RegisterMenus));
}
void FOpenPypeModule::ShutdownModule()
{
UToolMenus::UnRegisterStartupCallback(this);
UToolMenus::UnregisterOwner(this);
FOpenPypeStyle::Shutdown();
FOpenPypeCommands::Unregister();
}
void FOpenPypeModule::RegisterMenus()
{
// Owner will be used for cleanup in call to UToolMenus::UnregisterOwner
FToolMenuOwnerScoped OwnerScoped(this);
{
UToolMenu* Menu = UToolMenus::Get()->ExtendMenu("LevelEditor.MainMenu.Tools");
{
// FToolMenuSection& Section = Menu->FindOrAddSection("OpenPype");
FToolMenuSection& Section = Menu->AddSection(
"OpenPype",
TAttribute<FText>(FText::FromString("OpenPype")),
FToolMenuInsert("Programming", EToolMenuInsertType::Before)
);
Section.AddMenuEntryWithCommandList(FOpenPypeCommands::Get().OpenPypeTools, PluginCommands);
Section.AddMenuEntryWithCommandList(FOpenPypeCommands::Get().OpenPypeToolsDialog, PluginCommands);
}
UToolMenu* ToolbarMenu = UToolMenus::Get()->ExtendMenu("LevelEditor.LevelEditorToolBar.PlayToolBar");
{
FToolMenuSection& Section = ToolbarMenu->FindOrAddSection("PluginTools");
{
FToolMenuEntry& Entry = Section.AddEntry(FToolMenuEntry::InitToolBarButton(FOpenPypeCommands::Get().OpenPypeTools));
Entry.SetCommandList(PluginCommands);
}
}
}
}
void FOpenPypeModule::MenuPopup() {
UOpenPypePythonBridge* bridge = UOpenPypePythonBridge::Get();
bridge->RunInPython_Popup();
}
void FOpenPypeModule::MenuDialog() {
UOpenPypePythonBridge* bridge = UOpenPypePythonBridge::Get();
bridge->RunInPython_Dialog();
}
IMPLEMENT_MODULE(FOpenPypeModule, OpenPype)

View file

@ -0,0 +1,13 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "OpenPypeCommands.h"
#define LOCTEXT_NAMESPACE "FOpenPypeModule"
void FOpenPypeCommands::RegisterCommands()
{
UI_COMMAND(OpenPypeTools, "OpenPype Tools", "Pipeline tools", EUserInterfaceActionType::Button, FInputChord());
UI_COMMAND(OpenPypeToolsDialog, "OpenPype Tools Dialog", "Pipeline tools dialog", EUserInterfaceActionType::Button, FInputChord());
}
#undef LOCTEXT_NAMESPACE

View file

@ -0,0 +1,48 @@
#include "OpenPypeLib.h"
#include "Misc/Paths.h"
#include "Misc/ConfigCacheIni.h"
#include "UObject/UnrealType.h"
/**
* Sets color on folder icon on given path
* @param InPath - path to folder
* @param InFolderColor - color of the folder
* @warning This color will appear only after Editor restart. Is there a better way?
*/
void UOpenPypeLib::CSetFolderColor(FString FolderPath, FLinearColor FolderColor, bool bForceAdd)
{
auto SaveColorInternal = [](FString InPath, FLinearColor InFolderColor)
{
// Saves the color of the folder to the config
if (FPaths::FileExists(GEditorPerProjectIni))
{
GConfig->SetString(TEXT("PathColor"), *InPath, *InFolderColor.ToString(), GEditorPerProjectIni);
}
};
SaveColorInternal(FolderPath, FolderColor);
}
/**
* Returns all poperties on given object
* @param cls - class
* @return TArray of properties
*/
TArray<FString> UOpenPypeLib::GetAllProperties(UClass* cls)
{
TArray<FString> Ret;
if (cls != nullptr)
{
for (TFieldIterator<FProperty> It(cls); It; ++It)
{
FProperty* Property = *It;
if (Property->HasAnyPropertyFlags(EPropertyFlags::CPF_Edit))
{
Ret.Add(Property->GetName());
}
}
}
return Ret;
}

View file

@ -0,0 +1,108 @@
#pragma once
#include "OpenPypePublishInstance.h"
#include "AssetRegistryModule.h"
UOpenPypePublishInstance::UOpenPypePublishInstance(const FObjectInitializer& ObjectInitializer)
: UObject(ObjectInitializer)
{
FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>("AssetRegistry");
FString path = UOpenPypePublishInstance::GetPathName();
FARFilter Filter;
Filter.PackagePaths.Add(FName(*path));
AssetRegistryModule.Get().OnAssetAdded().AddUObject(this, &UOpenPypePublishInstance::OnAssetAdded);
AssetRegistryModule.Get().OnAssetRemoved().AddUObject(this, &UOpenPypePublishInstance::OnAssetRemoved);
AssetRegistryModule.Get().OnAssetRenamed().AddUObject(this, &UOpenPypePublishInstance::OnAssetRenamed);
}
void UOpenPypePublishInstance::OnAssetAdded(const FAssetData& AssetData)
{
TArray<FString> split;
// get directory of current container
FString selfFullPath = UOpenPypePublishInstance::GetPathName();
FString selfDir = FPackageName::GetLongPackagePath(*selfFullPath);
// get asset path and class
FString assetPath = AssetData.GetFullName();
FString assetFName = AssetData.AssetClass.ToString();
// split path
assetPath.ParseIntoArray(split, TEXT(" "), true);
FString assetDir = FPackageName::GetLongPackagePath(*split[1]);
// take interest only in paths starting with path of current container
if (assetDir.StartsWith(*selfDir))
{
// exclude self
if (assetFName != "OpenPypePublishInstance")
{
assets.Add(assetPath);
UE_LOG(LogTemp, Log, TEXT("%s: asset added to %s"), *selfFullPath, *selfDir);
}
}
}
void UOpenPypePublishInstance::OnAssetRemoved(const FAssetData& AssetData)
{
TArray<FString> split;
// get directory of current container
FString selfFullPath = UOpenPypePublishInstance::GetPathName();
FString selfDir = FPackageName::GetLongPackagePath(*selfFullPath);
// get asset path and class
FString assetPath = AssetData.GetFullName();
FString assetFName = AssetData.AssetClass.ToString();
// split path
assetPath.ParseIntoArray(split, TEXT(" "), true);
FString assetDir = FPackageName::GetLongPackagePath(*split[1]);
// take interest only in paths starting with path of current container
FString path = UOpenPypePublishInstance::GetPathName();
FString lpp = FPackageName::GetLongPackagePath(*path);
if (assetDir.StartsWith(*selfDir))
{
// exclude self
if (assetFName != "OpenPypePublishInstance")
{
// UE_LOG(LogTemp, Warning, TEXT("%s: asset removed"), *lpp);
assets.Remove(assetPath);
}
}
}
void UOpenPypePublishInstance::OnAssetRenamed(const FAssetData& AssetData, const FString& str)
{
TArray<FString> split;
// get directory of current container
FString selfFullPath = UOpenPypePublishInstance::GetPathName();
FString selfDir = FPackageName::GetLongPackagePath(*selfFullPath);
// get asset path and class
FString assetPath = AssetData.GetFullName();
FString assetFName = AssetData.AssetClass.ToString();
// split path
assetPath.ParseIntoArray(split, TEXT(" "), true);
FString assetDir = FPackageName::GetLongPackagePath(*split[1]);
if (assetDir.StartsWith(*selfDir))
{
// exclude self
if (assetFName != "AssetContainer")
{
assets.Remove(str);
assets.Add(assetPath);
// UE_LOG(LogTemp, Warning, TEXT("%s: asset renamed %s"), *lpp, *str);
}
}
}

View file

@ -0,0 +1,20 @@
#include "OpenPypePublishInstanceFactory.h"
#include "OpenPypePublishInstance.h"
UOpenPypePublishInstanceFactory::UOpenPypePublishInstanceFactory(const FObjectInitializer& ObjectInitializer)
: UFactory(ObjectInitializer)
{
SupportedClass = UOpenPypePublishInstance::StaticClass();
bCreateNew = false;
bEditorImport = true;
}
UObject* UOpenPypePublishInstanceFactory::FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn)
{
UOpenPypePublishInstance* OpenPypePublishInstance = NewObject<UOpenPypePublishInstance>(InParent, Class, Name, Flags);
return OpenPypePublishInstance;
}
bool UOpenPypePublishInstanceFactory::ShouldShowInNewMenu() const {
return false;
}

View file

@ -0,0 +1,13 @@
#include "OpenPypePythonBridge.h"
UOpenPypePythonBridge* UOpenPypePythonBridge::Get()
{
TArray<UClass*> OpenPypePythonBridgeClasses;
GetDerivedClasses(UOpenPypePythonBridge::StaticClass(), OpenPypePythonBridgeClasses);
int32 NumClasses = OpenPypePythonBridgeClasses.Num();
if (NumClasses > 0)
{
return Cast<UOpenPypePythonBridge>(OpenPypePythonBridgeClasses[NumClasses - 1]->GetDefaultObject());
}
return nullptr;
};

View file

@ -0,0 +1,61 @@
#include "OpenPype.h"
#include "OpenPypeStyle.h"
#include "Framework/Application/SlateApplication.h"
#include "Styling/SlateStyleRegistry.h"
#include "Slate/SlateGameResources.h"
#include "Interfaces/IPluginManager.h"
#include "Styling/SlateStyleMacros.h"
#define RootToContentDir Style->RootToContentDir
TSharedPtr<FSlateStyleSet> FOpenPypeStyle::OpenPypeStyleInstance = nullptr;
void FOpenPypeStyle::Initialize()
{
if (!OpenPypeStyleInstance.IsValid())
{
OpenPypeStyleInstance = Create();
FSlateStyleRegistry::RegisterSlateStyle(*OpenPypeStyleInstance);
}
}
void FOpenPypeStyle::Shutdown()
{
FSlateStyleRegistry::UnRegisterSlateStyle(*OpenPypeStyleInstance);
ensure(OpenPypeStyleInstance.IsUnique());
OpenPypeStyleInstance.Reset();
}
FName FOpenPypeStyle::GetStyleSetName()
{
static FName StyleSetName(TEXT("OpenPypeStyle"));
return StyleSetName;
}
const FVector2D Icon16x16(16.0f, 16.0f);
const FVector2D Icon20x20(20.0f, 20.0f);
const FVector2D Icon40x40(40.0f, 40.0f);
TSharedRef< FSlateStyleSet > FOpenPypeStyle::Create()
{
TSharedRef< FSlateStyleSet > Style = MakeShareable(new FSlateStyleSet("OpenPypeStyle"));
Style->SetContentRoot(IPluginManager::Get().FindPlugin("OpenPype")->GetBaseDir() / TEXT("Resources"));
Style->Set("OpenPype.OpenPypeTools", new IMAGE_BRUSH(TEXT("openpype40"), Icon40x40));
Style->Set("OpenPype.OpenPypeToolsDialog", new IMAGE_BRUSH(TEXT("openpype40"), Icon40x40));
return Style;
}
void FOpenPypeStyle::ReloadTextures()
{
if (FSlateApplication::IsInitialized())
{
FSlateApplication::Get().GetRenderer()->ReloadTextureResources();
}
}
const ISlateStyle& FOpenPypeStyle::Get()
{
return *OpenPypeStyleInstance;
}

View file

@ -0,0 +1,39 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "UObject/NoExportTypes.h"
#include "Engine/AssetUserData.h"
#include "AssetData.h"
#include "AssetContainer.generated.h"
/**
*
*/
UCLASS(Blueprintable)
class OPENPYPE_API UAssetContainer : public UAssetUserData
{
GENERATED_BODY()
public:
UAssetContainer(const FObjectInitializer& ObjectInitalizer);
// ~UAssetContainer();
UPROPERTY(EditAnywhere, BlueprintReadOnly)
TArray<FString> assets;
// There seems to be no reflection option to expose array of FAssetData
/*
UPROPERTY(Transient, BlueprintReadOnly, Category = "Python", meta=(DisplayName="Assets Data"))
TArray<FAssetData> assetsData;
*/
private:
TArray<FAssetData> assetsData;
void OnAssetAdded(const FAssetData& AssetData);
void OnAssetRemoved(const FAssetData& AssetData);
void OnAssetRenamed(const FAssetData& AssetData, const FString& str);
};

View file

@ -0,0 +1,21 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Factories/Factory.h"
#include "AssetContainerFactory.generated.h"
/**
*
*/
UCLASS()
class OPENPYPE_API UAssetContainerFactory : public UFactory
{
GENERATED_BODY()
public:
UAssetContainerFactory(const FObjectInitializer& ObjectInitializer);
virtual UObject* FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) override;
virtual bool ShouldShowInNewMenu() const override;
};

View file

@ -0,0 +1,23 @@
// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Modules/ModuleManager.h"
class FOpenPypeModule : public IModuleInterface
{
public:
virtual void StartupModule() override;
virtual void ShutdownModule() override;
private:
void RegisterMenus();
void MenuPopup();
void MenuDialog();
private:
TSharedPtr<class FUICommandList> PluginCommands;
};

View file

@ -0,0 +1,24 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Framework/Commands/Commands.h"
#include "OpenPypeStyle.h"
class FOpenPypeCommands : public TCommands<FOpenPypeCommands>
{
public:
FOpenPypeCommands()
: TCommands<FOpenPypeCommands>(TEXT("OpenPype"), NSLOCTEXT("Contexts", "OpenPype", "OpenPype Tools"), NAME_None, FOpenPypeStyle::GetStyleSetName())
{
}
// TCommands<> interface
virtual void RegisterCommands() override;
public:
TSharedPtr< FUICommandInfo > OpenPypeTools;
TSharedPtr< FUICommandInfo > OpenPypeToolsDialog;
};

View file

@ -0,0 +1,19 @@
#pragma once
#include "Engine.h"
#include "OpenPypeLib.generated.h"
UCLASS(Blueprintable)
class OPENPYPE_API UOpenPypeLib : public UObject
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable, Category = Python)
static void CSetFolderColor(FString FolderPath, FLinearColor FolderColor, bool bForceAdd);
UFUNCTION(BlueprintCallable, Category = Python)
static TArray<FString> GetAllProperties(UClass* cls);
};

View file

@ -0,0 +1,21 @@
#pragma once
#include "Engine.h"
#include "OpenPypePublishInstance.generated.h"
UCLASS(Blueprintable)
class OPENPYPE_API UOpenPypePublishInstance : public UObject
{
GENERATED_BODY()
public:
UOpenPypePublishInstance(const FObjectInitializer& ObjectInitalizer);
UPROPERTY(EditAnywhere, BlueprintReadOnly)
TArray<FString> assets;
private:
void OnAssetAdded(const FAssetData& AssetData);
void OnAssetRemoved(const FAssetData& AssetData);
void OnAssetRenamed(const FAssetData& AssetData, const FString& str);
};

View file

@ -0,0 +1,19 @@
#pragma once
#include "CoreMinimal.h"
#include "Factories/Factory.h"
#include "OpenPypePublishInstanceFactory.generated.h"
/**
*
*/
UCLASS()
class OPENPYPE_API UOpenPypePublishInstanceFactory : public UFactory
{
GENERATED_BODY()
public:
UOpenPypePublishInstanceFactory(const FObjectInitializer& ObjectInitializer);
virtual UObject* FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) override;
virtual bool ShouldShowInNewMenu() const override;
};

View file

@ -0,0 +1,20 @@
#pragma once
#include "Engine.h"
#include "OpenPypePythonBridge.generated.h"
UCLASS(Blueprintable)
class UOpenPypePythonBridge : public UObject
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable, Category = Python)
static UOpenPypePythonBridge* Get();
UFUNCTION(BlueprintImplementableEvent, Category = Python)
void RunInPython_Popup() const;
UFUNCTION(BlueprintImplementableEvent, Category = Python)
void RunInPython_Dialog() const;
};

View file

@ -0,0 +1,18 @@
#pragma once
#include "CoreMinimal.h"
#include "Styling/SlateStyle.h"
class FOpenPypeStyle
{
public:
static void Initialize();
static void Shutdown();
static void ReloadTextures();
static const ISlateStyle& Get();
static FName GetStyleSetName();
private:
static TSharedRef< class FSlateStyleSet > Create();
static TSharedPtr< class FSlateStyleSet > OpenPypeStyleInstance;
};

View file

@ -70,19 +70,22 @@ def get_engine_versions(env=None):
return OrderedDict()
def get_editor_executable_path(engine_path: Path) -> Path:
"""Get UE4 Editor executable path."""
ue4_path = engine_path / "Engine/Binaries"
def get_editor_executable_path(engine_path: Path, engine_version: str) -> Path:
"""Get UE Editor executable path."""
ue_path = engine_path / "Engine/Binaries"
if platform.system().lower() == "windows":
ue4_path /= "Win64/UE4Editor.exe"
if engine_version.split(".")[0] == "4":
ue_path /= "Win64/UE4Editor.exe"
elif engine_version.split(".")[0] == "5":
ue_path /= "Win64/UnrealEditor.exe"
elif platform.system().lower() == "linux":
ue4_path /= "Linux/UE4Editor"
ue_path /= "Linux/UE4Editor"
elif platform.system().lower() == "darwin":
ue4_path /= "Mac/UE4Editor"
ue_path /= "Mac/UE4Editor"
return ue4_path
return ue_path
def _win_get_engine_versions():
@ -208,22 +211,26 @@ def create_unreal_project(project_name: str,
# created in different UE4 version. When user convert such project
# to his UE4 version, Engine ID is replaced in uproject file. If some
# other user tries to open it, it will present him with similar error.
ue4_modules = Path()
ue_modules = Path()
if platform.system().lower() == "windows":
ue4_modules = Path(os.path.join(engine_path, "Engine", "Binaries",
"Win64", "UE4Editor.modules"))
ue_modules_path = engine_path / "Engine/Binaries/Win64"
if ue_version.split(".")[0] == "4":
ue_modules_path /= "UE4Editor.modules"
elif ue_version.split(".")[0] == "5":
ue_modules_path /= "UnrealEditor.modules"
ue_modules = Path(ue_modules_path)
if platform.system().lower() == "linux":
ue4_modules = Path(os.path.join(engine_path, "Engine", "Binaries",
ue_modules = Path(os.path.join(engine_path, "Engine", "Binaries",
"Linux", "UE4Editor.modules"))
if platform.system().lower() == "darwin":
ue4_modules = Path(os.path.join(engine_path, "Engine", "Binaries",
ue_modules = Path(os.path.join(engine_path, "Engine", "Binaries",
"Mac", "UE4Editor.modules"))
if ue4_modules.exists():
if ue_modules.exists():
print("--- Loading Engine ID from modules file ...")
with open(ue4_modules, "r") as mp:
with open(ue_modules, "r") as mp:
loaded_modules = json.load(mp)
if loaded_modules.get("BuildId"):
@ -280,7 +287,7 @@ def create_unreal_project(project_name: str,
python_path = None
if platform.system().lower() == "windows":
python_path = engine_path / ("Engine/Binaries/ThirdParty/"
"Python3/Win64/pythonw.exe")
"Python3/Win64/python.exe")
if platform.system().lower() == "linux":
python_path = engine_path / ("Engine/Binaries/ThirdParty/"
@ -294,14 +301,15 @@ def create_unreal_project(project_name: str,
raise NotImplementedError("Unsupported platform")
if not python_path.exists():
raise RuntimeError(f"Unreal Python not found at {python_path}")
subprocess.run(
subprocess.check_call(
[python_path.as_posix(), "-m", "pip", "install", "pyside2"])
if dev_mode or preset["dev_mode"]:
_prepare_cpp_project(project_file, engine_path)
_prepare_cpp_project(project_file, engine_path, ue_version)
def _prepare_cpp_project(project_file: Path, engine_path: Path) -> None:
def _prepare_cpp_project(
project_file: Path, engine_path: Path, ue_version: str) -> None:
"""Prepare CPP Unreal Project.
This function will add source files needed for project to be
@ -420,8 +428,12 @@ class {1}_API A{0}GameModeBase : public AGameModeBase
with open(sources_dir / f"{project_name}GameModeBase.h", mode="w") as f:
f.write(game_mode_h)
u_build_tool = Path(
engine_path / "Engine/Binaries/DotNET/UnrealBuildTool.exe")
u_build_tool_path = engine_path / "Engine/Binaries/DotNET"
if ue_version.split(".")[0] == "4":
u_build_tool_path /= "UnrealBuildTool.exe"
elif ue_version.split(".")[0] == "5":
u_build_tool_path /= "UnrealBuildTool/UnrealBuildTool.exe"
u_build_tool = Path(u_build_tool_path)
u_header_tool = None
arch = "Win64"

View file

@ -22,17 +22,24 @@ class CreateRender(Creator):
ar = unreal.AssetRegistryHelpers.get_asset_registry()
# The asset name is the the third element of the path which contains
# the map.
# The index of the split path is 3 because the first element is an
# empty string, as the path begins with "/Content".
a = unreal.EditorUtilityLibrary.get_selected_assets()[0]
asset_name = a.get_path_name().split("/")[3]
# Get the master sequence and the master level.
# There should be only one sequence and one level in the directory.
filter = unreal.ARFilter(
class_names=["LevelSequence"],
package_paths=[f"/Game/OpenPype/{self.data['asset']}"],
package_paths=[f"/Game/OpenPype/{asset_name}"],
recursive_paths=False)
sequences = ar.get_assets(filter)
ms = sequences[0].get_editor_property('object_path')
filter = unreal.ARFilter(
class_names=["World"],
package_paths=[f"/Game/OpenPype/{self.data['asset']}"],
package_paths=[f"/Game/OpenPype/{asset_name}"],
recursive_paths=False)
levels = ar.get_assets(filter)
ml = levels[0].get_editor_property('object_path')

View file

@ -14,6 +14,7 @@ from openpype.pipeline import (
)
from openpype.hosts.unreal.api import plugin
from openpype.hosts.unreal.api import pipeline as unreal_pipeline
from openpype.api import get_asset
class AnimationFBXLoader(plugin.Loader):
@ -77,13 +78,15 @@ class AnimationFBXLoader(plugin.Loader):
task.options.anim_sequence_import_data.set_editor_property(
'import_meshes_in_bone_hierarchy', False)
task.options.anim_sequence_import_data.set_editor_property(
'use_default_sample_rate', True)
'use_default_sample_rate', False)
task.options.anim_sequence_import_data.set_editor_property(
'custom_sample_rate', get_asset()["data"].get("fps"))
task.options.anim_sequence_import_data.set_editor_property(
'import_custom_attribute', True)
task.options.anim_sequence_import_data.set_editor_property(
'import_bone_tracks', True)
task.options.anim_sequence_import_data.set_editor_property(
'remove_redundant_keys', True)
'remove_redundant_keys', False)
task.options.anim_sequence_import_data.set_editor_property(
'convert_scene', True)
@ -139,22 +142,18 @@ class AnimationFBXLoader(plugin.Loader):
root = "/Game/OpenPype"
asset = context.get('asset').get('name')
suffix = "_CON"
if asset:
asset_name = "{}_{}".format(asset, name)
else:
asset_name = "{}".format(name)
asset_name = f"{asset}_{name}" if asset else f"{name}"
tools = unreal.AssetToolsHelpers().get_asset_tools()
asset_dir, container_name = tools.create_unique_asset_name(
f"{root}/Animations/{asset}/{name}", suffix="")
ar = unreal.AssetRegistryHelpers.get_asset_registry()
filter = unreal.ARFilter(
_filter = unreal.ARFilter(
class_names=["World"],
package_paths=[f"{root}/{hierarchy[0]}"],
recursive_paths=False)
levels = ar.get_assets(filter)
levels = ar.get_assets(_filter)
master_level = levels[0].get_editor_property('object_path')
hierarchy_dir = root
@ -162,11 +161,11 @@ class AnimationFBXLoader(plugin.Loader):
hierarchy_dir = f"{hierarchy_dir}/{h}"
hierarchy_dir = f"{hierarchy_dir}/{asset}"
filter = unreal.ARFilter(
_filter = unreal.ARFilter(
class_names=["World"],
package_paths=[f"{hierarchy_dir}/"],
recursive_paths=True)
levels = ar.get_assets(filter)
levels = ar.get_assets(_filter)
level = levels[0].get_editor_property('object_path')
unreal.EditorLevelLibrary.save_all_dirty_levels()
@ -233,8 +232,7 @@ class AnimationFBXLoader(plugin.Loader):
"parent": context["representation"]["parent"],
"family": context["representation"]["context"]["family"]
}
unreal_pipeline.imprint(
"{}/{}".format(asset_dir, container_name), data)
unreal_pipeline.imprint(f"{asset_dir}/{container_name}", data)
imported_content = EditorAssetLibrary.list_assets(
asset_dir, recursive=True, include_folder=False)
@ -279,13 +277,15 @@ class AnimationFBXLoader(plugin.Loader):
task.options.anim_sequence_import_data.set_editor_property(
'import_meshes_in_bone_hierarchy', False)
task.options.anim_sequence_import_data.set_editor_property(
'use_default_sample_rate', True)
'use_default_sample_rate', False)
task.options.anim_sequence_import_data.set_editor_property(
'custom_sample_rate', get_asset()["data"].get("fps"))
task.options.anim_sequence_import_data.set_editor_property(
'import_custom_attribute', True)
task.options.anim_sequence_import_data.set_editor_property(
'import_bone_tracks', True)
task.options.anim_sequence_import_data.set_editor_property(
'remove_redundant_keys', True)
'remove_redundant_keys', False)
task.options.anim_sequence_import_data.set_editor_property(
'convert_scene', True)
@ -296,8 +296,7 @@ class AnimationFBXLoader(plugin.Loader):
# do import fbx and replace existing data
unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([task])
container_path = "{}/{}".format(container["namespace"],
container["objectName"])
container_path = f'{container["namespace"]}/{container["objectName"]}'
# update metadata
unreal_pipeline.imprint(
container_path,

View file

@ -58,6 +58,33 @@ class CameraLoader(plugin.Loader):
min_frame_j,
max_frame_j + 1)
def _import_camera(
self, world, sequence, bindings, import_fbx_settings, import_filename
):
ue_version = unreal.SystemLibrary.get_engine_version().split('.')
ue_major = int(ue_version[0])
ue_minor = int(ue_version[1])
if ue_major == 4 and ue_minor <= 26:
unreal.SequencerTools.import_fbx(
world,
sequence,
bindings,
import_fbx_settings,
import_filename
)
elif (ue_major == 4 and ue_minor >= 27) or ue_major == 5:
unreal.SequencerTools.import_level_sequence_fbx(
world,
sequence,
bindings,
import_fbx_settings,
import_filename
)
else:
raise NotImplementedError(
f"Unreal version {ue_major} not supported")
def load(self, context, name, namespace, data):
"""
Load and containerise representation into Content Browser.
@ -242,7 +269,7 @@ class CameraLoader(plugin.Loader):
settings.set_editor_property('reduce_keys', False)
if cam_seq:
unreal.SequencerTools.import_fbx(
self._import_camera(
EditorLevelLibrary.get_editor_world(),
cam_seq,
cam_seq.get_bindings(),
@ -402,7 +429,7 @@ class CameraLoader(plugin.Loader):
sub_scene.set_sequence(new_sequence)
unreal.SequencerTools.import_fbx(
self._import_camera(
EditorLevelLibrary.get_editor_world(),
new_sequence,
new_sequence.get_bindings(),

View file

@ -20,6 +20,7 @@ from openpype.pipeline import (
AVALON_CONTAINER_ID,
legacy_io,
)
from openpype.api import get_asset
from openpype.hosts.unreal.api import plugin
from openpype.hosts.unreal.api import pipeline as unreal_pipeline
@ -87,7 +88,8 @@ class LayoutLoader(plugin.Loader):
return None
def _get_data(self, asset_name):
@staticmethod
def _get_data(asset_name):
asset_doc = legacy_io.find_one({
"type": "asset",
"name": asset_name
@ -95,8 +97,9 @@ class LayoutLoader(plugin.Loader):
return asset_doc.get("data")
@staticmethod
def _set_sequence_hierarchy(
self, seq_i, seq_j, max_frame_i, min_frame_j, max_frame_j, map_paths
seq_i, seq_j, max_frame_i, min_frame_j, max_frame_j, map_paths
):
# Get existing sequencer tracks or create them if they don't exist
tracks = seq_i.get_master_tracks()
@ -165,8 +168,9 @@ class LayoutLoader(plugin.Loader):
hid_section.set_row_index(index)
hid_section.set_level_names(maps)
@staticmethod
def _process_family(
self, assets, class_name, transform, sequence, inst_name=None
assets, class_name, transform, sequence, inst_name=None
):
ar = unreal.AssetRegistryHelpers.get_asset_registry()
@ -262,13 +266,15 @@ class LayoutLoader(plugin.Loader):
task.options.anim_sequence_import_data.set_editor_property(
'import_meshes_in_bone_hierarchy', False)
task.options.anim_sequence_import_data.set_editor_property(
'use_default_sample_rate', True)
'use_default_sample_rate', False)
task.options.anim_sequence_import_data.set_editor_property(
'custom_sample_rate', get_asset()["data"].get("fps"))
task.options.anim_sequence_import_data.set_editor_property(
'import_custom_attribute', True)
task.options.anim_sequence_import_data.set_editor_property(
'import_bone_tracks', True)
task.options.anim_sequence_import_data.set_editor_property(
'remove_redundant_keys', True)
'remove_redundant_keys', False)
task.options.anim_sequence_import_data.set_editor_property(
'convert_scene', True)
@ -311,11 +317,8 @@ class LayoutLoader(plugin.Loader):
for binding in bindings:
tracks = binding.get_tracks()
track = None
if not tracks:
track = binding.add_track(
unreal.MovieSceneSkeletalAnimationTrack)
else:
track = tracks[0]
track = tracks[0] if tracks else binding.add_track(
unreal.MovieSceneSkeletalAnimationTrack)
sections = track.get_sections()
section = None
@ -335,11 +338,11 @@ class LayoutLoader(plugin.Loader):
curr_anim.get_path_name()).parent
).replace('\\', '/')
filter = unreal.ARFilter(
_filter = unreal.ARFilter(
class_names=["AssetContainer"],
package_paths=[anim_path],
recursive_paths=False)
containers = ar.get_assets(filter)
containers = ar.get_assets(_filter)
if len(containers) > 0:
return
@ -350,6 +353,7 @@ class LayoutLoader(plugin.Loader):
sec_params = section.get_editor_property('params')
sec_params.set_editor_property('animation', animation)
@staticmethod
def _generate_sequence(self, h, h_dir):
tools = unreal.AssetToolsHelpers().get_asset_tools()
@ -583,10 +587,7 @@ class LayoutLoader(plugin.Loader):
hierarchy_dir_list.append(hierarchy_dir)
asset = context.get('asset').get('name')
suffix = "_CON"
if asset:
asset_name = "{}_{}".format(asset, name)
else:
asset_name = "{}".format(name)
asset_name = f"{asset}_{name}" if asset else name
tools = unreal.AssetToolsHelpers().get_asset_tools()
asset_dir, container_name = tools.create_unique_asset_name(
@ -800,7 +801,7 @@ class LayoutLoader(plugin.Loader):
lc for lc in layout_containers
if asset in lc.get('loaded_assets')]
if len(layouts) == 0:
if not layouts:
EditorAssetLibrary.delete_directory(str(Path(asset).parent))
# Remove the Level Sequence from the parent.
@ -810,17 +811,17 @@ class LayoutLoader(plugin.Loader):
namespace = container.get('namespace').replace(f"{root}/", "")
ms_asset = namespace.split('/')[0]
ar = unreal.AssetRegistryHelpers.get_asset_registry()
filter = unreal.ARFilter(
_filter = unreal.ARFilter(
class_names=["LevelSequence"],
package_paths=[f"{root}/{ms_asset}"],
recursive_paths=False)
sequences = ar.get_assets(filter)
sequences = ar.get_assets(_filter)
master_sequence = sequences[0].get_asset()
filter = unreal.ARFilter(
_filter = unreal.ARFilter(
class_names=["World"],
package_paths=[f"{root}/{ms_asset}"],
recursive_paths=False)
levels = ar.get_assets(filter)
levels = ar.get_assets(_filter)
master_level = levels[0].get_editor_property('object_path')
sequences = [master_sequence]

View file

@ -1243,9 +1243,19 @@
"host_name": "unreal",
"environment": {},
"variants": {
"4-26": {
"4-27": {
"use_python_2": false,
"environment": {}
},
"5-0": {
"use_python_2": false,
"environment": {
"UE_PYTHONPATH": "{PYTHONPATH}"
}
},
"__dynamic_keys_labels__": {
"4-27": "4.27",
"5-0": "5.0"
}
}
},