Refactored the generation of UE projects, plugin is now being installed in the engine.

This commit is contained in:
Joseff 2023-01-25 11:31:27 +01:00 committed by Ondřej Samohel
parent 30768de850
commit 90955e5779
85 changed files with 1029 additions and 509 deletions

View file

@ -17,7 +17,7 @@ class UnrealAddon(OpenPypeModule, IHostAddon):
ue_plugin = "UE_5.0" if app.name[:1] == "5" else "UE_4.7"
unreal_plugin_path = os.path.join(
UNREAL_ROOT_DIR, "integration", ue_plugin
UNREAL_ROOT_DIR, "integration", ue_plugin, "OpenPype"
)
if not env.get("OPENPYPE_UNREAL_PLUGIN"):
env["OPENPYPE_UNREAL_PLUGIN"] = unreal_plugin_path

View file

@ -119,29 +119,33 @@ class UnrealPrelaunchHook(PreLaunchHook):
f"detected [ {engine_version} ]"
))
ue_path = unreal_lib.get_editor_executable_path(
ue_path = unreal_lib.get_editor_exe_path(
Path(detected[engine_version]), engine_version)
self.launch_context.launch_args = [ue_path.as_posix()]
project_path.mkdir(parents=True, exist_ok=True)
# 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]
engine_path = detected[engine_version]
unreal_lib.try_installing_plugin(Path(engine_path), engine_version)
project_file = project_path / unreal_project_filename
if not project_file.is_file():
engine_path = detected[engine_version]
self.log.info((
f"{self.signature} creating unreal "
f"project [ {unreal_project_name} ]"
))
# 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]
unreal_lib.create_unreal_project(
unreal_project_name,

View file

@ -0,0 +1,6 @@
/Saved
/DerivedDataCache
/Intermediate
/Binaries
/.idea
/.vs

View file

@ -0,0 +1,12 @@
{
"FileVersion": 3,
"EngineAssociation": "4.27",
"Category": "",
"Description": "",
"Plugins": [
{
"Name": "OpenPype",
"Enabled": true
}
]
}

View file

@ -0,0 +1,16 @@
[/Script/EngineSettings.GameMapsSettings]
GameDefaultMap=/Engine/Maps/Templates/Template_Default.Template_Default
[/Script/HardwareTargeting.HardwareTargetingSettings]
TargetedHardwareClass=Desktop
AppliedTargetedHardwareClass=Desktop
DefaultGraphicsPerformance=Maximum
AppliedDefaultGraphicsPerformance=Maximum
[/Script/Engine.Engine]
+ActiveGameNameRedirects=(OldGameName="TP_BlankBP",NewGameName="/Script/CommandletProject")
+ActiveGameNameRedirects=(OldGameName="/Script/TP_BlankBP",NewGameName="/Script/CommandletProject")

View file

@ -0,0 +1,4 @@
[/Script/EngineSettings.GeneralProjectSettings]
ProjectID=95AED0BF45A918DF73ABB3BB27D25356

View file

@ -0,0 +1,8 @@
[FilterPlugin]
; This section lists additional files which will be packaged along with your plugin. Paths should be listed relative to the root plugin directory, and
; may include "...", "*", and "?" wildcards to match directories, files, and individual characters respectively.
;
; Examples:
; /README.txt
; /Extras/...
; /Binaries/ThirdParty/*.dll

View file

@ -10,10 +10,10 @@
"DocsURL": "https://openpype.io/docs/artist_hosts_unreal",
"MarketplaceURL": "",
"SupportURL": "https://pype.club/",
"EngineVersion": "4.27",
"CanContainContent": true,
"IsBetaVersion": true,
"IsExperimentalVersion": false,
"Installed": false,
"Installed": true,
"Modules": [
{
"Name": "OpenPype",

View file

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

Before After
Before After

View file

@ -6,8 +6,8 @@ public class OpenPype : ModuleRules
{
public OpenPype(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
PublicIncludePaths.AddRange(
new string[] {
// ... add public include paths required here ...
@ -34,6 +34,7 @@ public class OpenPype : ModuleRules
PrivateDependencyModuleNames.AddRange(
new string[]
{
"GameProjectGeneration",
"Projects",
"InputCore",
"UnrealEd",

View file

@ -0,0 +1,140 @@
#include "Commandlets/Implementations/OPGenerateProjectCommandlet.h"
#include "Editor.h"
#include "GameProjectUtils.h"
#include "OPConstants.h"
#include "Commandlets/OPActionResult.h"
#include "ProjectDescriptor.h"
int32 UOPGenerateProjectCommandlet::Main(const FString& CommandLineParams)
{
//Parses command line parameters & creates structure FProjectInformation
const FOPGenerateProjectParams ParsedParams = FOPGenerateProjectParams(CommandLineParams);
ProjectInformation = ParsedParams.GenerateUEProjectInformation();
//Creates .uproject & other UE files
EVALUATE_OP_ACTION_RESULT(TryCreateProject());
//Loads created .uproject
EVALUATE_OP_ACTION_RESULT(TryLoadProjectDescriptor());
//Adds needed plugin to .uproject
AttachPluginsToProjectDescriptor();
//Saves .uproject
EVALUATE_OP_ACTION_RESULT(TrySave());
//When we are here, there should not be problems in generating Unreal Project for OpenPype
return 0;
}
FOPGenerateProjectParams::FOPGenerateProjectParams(): FOPGenerateProjectParams("")
{
}
FOPGenerateProjectParams::FOPGenerateProjectParams(const FString& CommandLineParams): CommandLineParams(
CommandLineParams)
{
UCommandlet::ParseCommandLine(*CommandLineParams, Tokens, Switches);
}
FProjectInformation FOPGenerateProjectParams::GenerateUEProjectInformation() const
{
FProjectInformation ProjectInformation = FProjectInformation();
ProjectInformation.ProjectFilename = GetProjectFileName();
ProjectInformation.bShouldGenerateCode = IsSwitchPresent("GenerateCode");
return ProjectInformation;
}
FString FOPGenerateProjectParams::TryGetToken(const int32 Index) const
{
return Tokens.IsValidIndex(Index) ? Tokens[Index] : "";
}
FString FOPGenerateProjectParams::GetProjectFileName() const
{
return TryGetToken(0);
}
bool FOPGenerateProjectParams::IsSwitchPresent(const FString& Switch) const
{
return INDEX_NONE != Switches.IndexOfByPredicate([&Switch](const FString& Item) -> bool
{
return Item.Equals(Switch);
}
);
}
UOPGenerateProjectCommandlet::UOPGenerateProjectCommandlet()
{
LogToConsole = true;
}
FOP_ActionResult UOPGenerateProjectCommandlet::TryCreateProject() const
{
FText FailReason;
FText FailLog;
TArray<FString> OutCreatedFiles;
if (!GameProjectUtils::CreateProject(ProjectInformation, FailReason, FailLog, &OutCreatedFiles))
return FOP_ActionResult(EOP_ActionResult::ProjectNotCreated, FailReason);
return FOP_ActionResult();
}
FOP_ActionResult UOPGenerateProjectCommandlet::TryLoadProjectDescriptor()
{
FText FailReason;
const bool bLoaded = ProjectDescriptor.Load(ProjectInformation.ProjectFilename, FailReason);
return FOP_ActionResult(bLoaded ? EOP_ActionResult::Ok : EOP_ActionResult::ProjectNotLoaded, FailReason);
}
void UOPGenerateProjectCommandlet::AttachPluginsToProjectDescriptor()
{
FPluginReferenceDescriptor OPPluginDescriptor;
OPPluginDescriptor.bEnabled = true;
OPPluginDescriptor.Name = OPConstants::OP_PluginName;
ProjectDescriptor.Plugins.Add(OPPluginDescriptor);
FPluginReferenceDescriptor PythonPluginDescriptor;
PythonPluginDescriptor.bEnabled = true;
PythonPluginDescriptor.Name = OPConstants::PythonScript_PluginName;
ProjectDescriptor.Plugins.Add(PythonPluginDescriptor);
FPluginReferenceDescriptor SequencerScriptingPluginDescriptor;
SequencerScriptingPluginDescriptor.bEnabled = true;
SequencerScriptingPluginDescriptor.Name = OPConstants::SequencerScripting_PluginName;
ProjectDescriptor.Plugins.Add(SequencerScriptingPluginDescriptor);
FPluginReferenceDescriptor MovieRenderPipelinePluginDescriptor;
MovieRenderPipelinePluginDescriptor.bEnabled = true;
MovieRenderPipelinePluginDescriptor.Name = OPConstants::MovieRenderPipeline_PluginName;
ProjectDescriptor.Plugins.Add(MovieRenderPipelinePluginDescriptor);
FPluginReferenceDescriptor EditorScriptingPluginDescriptor;
EditorScriptingPluginDescriptor.bEnabled = true;
EditorScriptingPluginDescriptor.Name = OPConstants::EditorScriptingUtils_PluginName;
ProjectDescriptor.Plugins.Add(EditorScriptingPluginDescriptor);
}
FOP_ActionResult UOPGenerateProjectCommandlet::TrySave()
{
FText FailReason;
const bool bSaved = ProjectDescriptor.Save(ProjectInformation.ProjectFilename, FailReason);
return FOP_ActionResult(bSaved ? EOP_ActionResult::Ok : EOP_ActionResult::ProjectNotSaved, FailReason);
}
FOPGenerateProjectParams UOPGenerateProjectCommandlet::ParseParameters(const FString& Params) const
{
FOPGenerateProjectParams ParamsResult;
TArray<FString> Tokens, Switches;
ParseCommandLine(*Params, Tokens, Switches);
return ParamsResult;
}

View file

@ -0,0 +1,41 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "Commandlets/OPActionResult.h"
#include "Logging/OP_Log.h"
EOP_ActionResult::Type& FOP_ActionResult::GetStatus()
{
return Status;
}
FText& FOP_ActionResult::GetReason()
{
return Reason;
}
FOP_ActionResult::FOP_ActionResult():Status(EOP_ActionResult::Type::Ok)
{
}
FOP_ActionResult::FOP_ActionResult(const EOP_ActionResult::Type& InEnum):Status(InEnum)
{
TryLog();
}
FOP_ActionResult::FOP_ActionResult(const EOP_ActionResult::Type& InEnum, const FText& InReason):Status(InEnum), Reason(InReason)
{
TryLog();
};
bool FOP_ActionResult::IsProblem() const
{
return Status != EOP_ActionResult::Ok;
}
void FOP_ActionResult::TryLog() const
{
if(IsProblem())
UE_LOG(LogCommandletOPGenerateProject, Error, TEXT("%s"), *Reason.ToString());
}

View file

@ -0,0 +1 @@
#include "Logging/OP_Log.h"

View file

@ -16,32 +16,34 @@ static const FName OpenPypeTabName("OpenPype");
// This function is triggered when the plugin is staring up
void FOpenPypeModule::StartupModule()
{
FOpenPypeStyle::Initialize();
FOpenPypeStyle::SetIcon("Logo", "openpype40");
if (!IsRunningCommandlet()) {
FOpenPypeStyle::Initialize();
FOpenPypeStyle::SetIcon("Logo", "openpype40");
// Create the Extender that will add content to the menu
FLevelEditorModule& LevelEditorModule = FModuleManager::LoadModuleChecked<FLevelEditorModule>("LevelEditor");
// Create the Extender that will add content to the menu
FLevelEditorModule& LevelEditorModule = FModuleManager::LoadModuleChecked<FLevelEditorModule>("LevelEditor");
TSharedPtr<FExtender> MenuExtender = MakeShareable(new FExtender());
TSharedPtr<FExtender> ToolbarExtender = MakeShareable(new FExtender());
TSharedPtr<FExtender> MenuExtender = MakeShareable(new FExtender());
TSharedPtr<FExtender> ToolbarExtender = MakeShareable(new FExtender());
MenuExtender->AddMenuExtension(
"LevelEditor",
EExtensionHook::After,
NULL,
FMenuExtensionDelegate::CreateRaw(this, &FOpenPypeModule::AddMenuEntry)
);
ToolbarExtender->AddToolBarExtension(
"Settings",
EExtensionHook::After,
NULL,
FToolBarExtensionDelegate::CreateRaw(this, &FOpenPypeModule::AddToobarEntry));
MenuExtender->AddMenuExtension(
"LevelEditor",
EExtensionHook::After,
NULL,
FMenuExtensionDelegate::CreateRaw(this, &FOpenPypeModule::AddMenuEntry)
);
ToolbarExtender->AddToolBarExtension(
"Settings",
EExtensionHook::After,
NULL,
FToolBarExtensionDelegate::CreateRaw(this, &FOpenPypeModule::AddToobarEntry));
LevelEditorModule.GetMenuExtensibilityManager()->AddExtender(MenuExtender);
LevelEditorModule.GetToolBarExtensibilityManager()->AddExtender(ToolbarExtender);
LevelEditorModule.GetMenuExtensibilityManager()->AddExtender(MenuExtender);
LevelEditorModule.GetToolBarExtensibilityManager()->AddExtender(ToolbarExtender);
RegisterSettings();
RegisterSettings();
}
}
void FOpenPypeModule::ShutdownModule()

View file

@ -2,10 +2,10 @@
#include "OpenPypePublishInstance.h"
#include "AssetRegistryModule.h"
#include "NotificationManager.h"
#include "OpenPypeLib.h"
#include "OpenPypeSettings.h"
#include "SNotificationList.h"
#include "Framework/Notifications/NotificationManager.h"
#include "Widgets/Notifications/SNotificationList.h"
//Moves all the invalid pointers to the end to prepare them for the shrinking
#define REMOVE_INVALID_ENTRIES(VAR) VAR.CompactStable(); \

View file

@ -2,8 +2,7 @@
#include "OpenPypeSettings.h"
#include "IPluginManager.h"
#include "UObjectGlobals.h"
#include "Interfaces/IPluginManager.h"
/**
* Mainly is used for initializing default values if the DefaultOpenPypeSettings.ini file does not exist in the saved config

View file

@ -43,7 +43,7 @@ const FVector2D Icon40x40(40.0f, 40.0f);
TUniquePtr< FSlateStyleSet > FOpenPypeStyle::Create()
{
TUniquePtr< FSlateStyleSet > Style = MakeUnique<FSlateStyleSet>(GetStyleSetName());
Style->SetContentRoot(FPaths::ProjectPluginsDir() / TEXT("OpenPype/Resources"));
Style->SetContentRoot(FPaths::EnginePluginsDir() / TEXT("Marketplace/OpenPype/Resources"));
return Style;
}
@ -66,5 +66,4 @@ const ISlateStyle& FOpenPypeStyle::Get()
{
check(OpenPypeStyleInstance);
return *OpenPypeStyleInstance;
return *OpenPypeStyleInstance;
}

View file

@ -0,0 +1,60 @@
#pragma once
#include "GameProjectUtils.h"
#include "Commandlets/OPActionResult.h"
#include "ProjectDescriptor.h"
#include "Commandlets/Commandlet.h"
#include "OPGenerateProjectCommandlet.generated.h"
struct FProjectDescriptor;
struct FProjectInformation;
/**
* @brief Structure which parses command line parameters and generates FProjectInformation
*/
USTRUCT()
struct FOPGenerateProjectParams
{
GENERATED_BODY()
private:
FString CommandLineParams;
TArray<FString> Tokens;
TArray<FString> Switches;
public:
FOPGenerateProjectParams();
FOPGenerateProjectParams(const FString& CommandLineParams);
FProjectInformation GenerateUEProjectInformation() const;
private:
FString TryGetToken(const int32 Index) const;
FString GetProjectFileName() const;
bool IsSwitchPresent(const FString& Switch) const;
};
UCLASS()
class OPENPYPE_API UOPGenerateProjectCommandlet : public UCommandlet
{
GENERATED_BODY()
private:
FProjectInformation ProjectInformation;
FProjectDescriptor ProjectDescriptor;
public:
UOPGenerateProjectCommandlet();
virtual int32 Main(const FString& CommandLineParams) override;
private:
FOPGenerateProjectParams ParseParameters(const FString& Params) const;
FOP_ActionResult TryCreateProject() const;
FOP_ActionResult TryLoadProjectDescriptor();
void AttachPluginsToProjectDescriptor();
FOP_ActionResult TrySave();
};

View file

@ -0,0 +1,83 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "OPActionResult.generated.h"
/**
* @brief This macro returns error code when is problem or does nothing when there is no problem.
* @param ActionResult FOP_ActionResult structure
*/
#define EVALUATE_OP_ACTION_RESULT(ActionResult) \
if(ActionResult.IsProblem()) \
return ActionResult.GetStatus();
/**
* @brief This enum values are humanly readable mapping of error codes.
* Here should be all error codes to be possible find what went wrong.
* TODO: In the future should exists an web document where is mapped error code & what problem occured & how to repair it...
*/
UENUM()
namespace EOP_ActionResult
{
enum Type
{
Ok,
ProjectNotCreated,
ProjectNotLoaded,
ProjectNotSaved,
//....Here insert another values
//Do not remove!
//Usable for looping through enum values
__Last UMETA(Hidden)
};
}
/**
* @brief This struct holds action result enum and optionally reason of fail
*/
USTRUCT()
struct FOP_ActionResult
{
GENERATED_BODY()
public:
/** @brief Default constructor usable when there is no problem */
FOP_ActionResult();
/**
* @brief This constructor initializes variables & attempts to log when is error
* @param InEnum Status
*/
FOP_ActionResult(const EOP_ActionResult::Type& InEnum);
/**
* @brief This constructor initializes variables & attempts to log when is error
* @param InEnum Status
* @param InReason Reason of potential fail
*/
FOP_ActionResult(const EOP_ActionResult::Type& InEnum, const FText& InReason);
private:
/** @brief Action status */
EOP_ActionResult::Type Status;
/** @brief Optional reason of fail */
FText Reason;
public:
/**
* @brief Checks if there is problematic state
* @return true when status is not equal to EOP_ActionResult::Ok
*/
bool IsProblem() const;
EOP_ActionResult::Type& GetStatus();
FText& GetReason();
private:
void TryLog() const;
};

View file

@ -0,0 +1,3 @@
#pragma once
DEFINE_LOG_CATEGORY_STATIC(LogCommandletOPGenerateProject, Log, All);

View file

@ -0,0 +1,12 @@
#pragma once
namespace OPConstants
{
const FString OP_PluginName = "OpenPype";
const FString PythonScript_PluginName = "PythonScriptPlugin";
const FString SequencerScripting_PluginName = "SequencerScripting";
const FString MovieRenderPipeline_PluginName = "MovieRenderPipeline";
const FString EditorScriptingUtils_PluginName = "EditorScriptingUtilities";
}

View file

@ -16,7 +16,7 @@ public:
*
* @return - Set of UObjects. Careful! They are returning raw pointers. Seems like an issue in UE5
*/
UFUNCTION(BlueprintCallable, BlueprintPure)
UFUNCTION(BlueprintCallable, BlueprintPure, Category="Python")
TSet<UObject*> GetInternalAssets() const
{
//For some reason it can only return Raw Pointers? Seems like an issue which they haven't fixed.
@ -33,7 +33,7 @@ public:
*
* @return - TSet of assets (UObjects). Careful! They are returning raw pointers. Seems like an issue in UE5
*/
UFUNCTION(BlueprintCallable, BlueprintPure)
UFUNCTION(BlueprintCallable, BlueprintPure, Category="Python")
TSet<UObject*> GetExternalAssets() const
{
//For some reason it can only return Raw Pointers? Seems like an issue which they haven't fixed.
@ -53,7 +53,7 @@ public:
*
* @attention If the bAddExternalAssets variable is false, external assets won't be included!
*/
UFUNCTION(BlueprintCallable, BlueprintPure)
UFUNCTION(BlueprintCallable, BlueprintPure, Category="Python")
TSet<UObject*> GetAllAssets() const
{
const TSet<TSoftObjectPtr<UObject>>& IteratedSet = bAddExternalAssets

View file

@ -3,7 +3,6 @@
#pragma once
#include "CoreMinimal.h"
#include "Object.h"
#include "OpenPypeSettings.generated.h"
#define OPENPYPE_SETTINGS_FILEPATH IPluginManager::Get().FindPlugin("OpenPype")->GetBaseDir() / TEXT("Config") / TEXT("DefaultOpenPypeSettings.ini")

View file

@ -1,115 +0,0 @@
// 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

@ -1,20 +0,0 @@
#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

@ -1,39 +0,0 @@
// 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

@ -1,21 +0,0 @@
// 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,6 @@
/Saved
/DerivedDataCache
/Intermediate
/Binaries
/.idea
/.vs

View file

@ -0,0 +1,20 @@
{
"FileVersion": 3,
"EngineAssociation": "5.0",
"Category": "",
"Description": "",
"Plugins": [
{
"Name": "ModelingToolsEditorMode",
"Enabled": true,
"TargetAllowList": [
"Editor"
]
},
{
"Name": "OpenPype",
"Enabled": true,
"Type": "Editor"
}
]
}

View file

@ -0,0 +1,42 @@
[/Script/EngineSettings.GameMapsSettings]
GameDefaultMap=/Engine/Maps/Templates/OpenWorld
[/Script/HardwareTargeting.HardwareTargetingSettings]
TargetedHardwareClass=Desktop
AppliedTargetedHardwareClass=Desktop
DefaultGraphicsPerformance=Maximum
AppliedDefaultGraphicsPerformance=Maximum
[/Script/WindowsTargetPlatform.WindowsTargetSettings]
DefaultGraphicsRHI=DefaultGraphicsRHI_DX12
[/Script/Engine.RendererSettings]
r.GenerateMeshDistanceFields=True
r.DynamicGlobalIlluminationMethod=1
r.ReflectionMethod=1
r.Shadow.Virtual.Enable=1
[/Script/WorldPartitionEditor.WorldPartitionEditorSettings]
CommandletClass=Class'/Script/UnrealEd.WorldPartitionConvertCommandlet'
[/Script/Engine.Engine]
+ActiveGameNameRedirects=(OldGameName="TP_BlankBP",NewGameName="/Script/CommandletProject")
+ActiveGameNameRedirects=(OldGameName="/Script/TP_BlankBP",NewGameName="/Script/CommandletProject")
[/Script/AndroidFileServerEditor.AndroidFileServerRuntimeSettings]
bEnablePlugin=True
bAllowNetworkConnection=True
SecurityToken=684C16AF4BD96F1D6828A6B067693175
bIncludeInShipping=False
bAllowExternalStartInShipping=False
bCompileAFSProject=False
bUseCompression=False
bLogFiles=False
bReportStats=False
ConnectionType=USBOnly
bUseManualIPAddress=False
ManualIPAddress=

View file

@ -0,0 +1,4 @@
[/Script/EngineSettings.GeneralProjectSettings]
ProjectID=D528076140C577E5807BA5BA135366BB

View file

@ -0,0 +1,8 @@
[FilterPlugin]
; This section lists additional files which will be packaged along with your plugin. Paths should be listed relative to the root plugin directory, and
; may include "...", "*", and "?" wildcards to match directories, files, and individual characters respectively.
;
; Examples:
; /README.txt
; /Extras/...
; /Binaries/ThirdParty/*.dll

View file

@ -11,6 +11,7 @@
"MarketplaceURL": "",
"SupportURL": "https://pype.club/",
"CanContainContent": true,
"EngineVersion": "5.0",
"IsBetaVersion": true,
"IsExperimentalVersion": false,
"Installed": false,

View file

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

Before After
Before After

View file

@ -10,7 +10,7 @@ public class OpenPype : ModuleRules
bLegacyPublicIncludePaths = false;
ShadowVariableWarningLevel = WarningLevel.Error;
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
IncludeOrderVersion = EngineIncludeOrderVersion.Unreal5_0;
//IncludeOrderVersion = EngineIncludeOrderVersion.Unreal5_0;
PublicIncludePaths.AddRange(
new string[] {
@ -30,14 +30,15 @@ public class OpenPype : ModuleRules
new string[]
{
"Core",
"CoreUObject"
// ... add other public dependencies that you statically link with here ...
}
);
PrivateDependencyModuleNames.AddRange(
new string[]
{
"GameProjectGeneration",
"Projects",
"InputCore",
"EditorFramework",

View file

@ -0,0 +1,140 @@
#include "Commandlets/Implementations/OPGenerateProjectCommandlet.h"
#include "Editor.h"
#include "GameProjectUtils.h"
#include "OPConstants.h"
#include "Commandlets/OPActionResult.h"
#include "ProjectDescriptor.h"
int32 UOPGenerateProjectCommandlet::Main(const FString& CommandLineParams)
{
//Parses command line parameters & creates structure FProjectInformation
const FOPGenerateProjectParams ParsedParams = FOPGenerateProjectParams(CommandLineParams);
ProjectInformation = ParsedParams.GenerateUEProjectInformation();
//Creates .uproject & other UE files
EVALUATE_OP_ACTION_RESULT(TryCreateProject());
//Loads created .uproject
EVALUATE_OP_ACTION_RESULT(TryLoadProjectDescriptor());
//Adds needed plugin to .uproject
AttachPluginsToProjectDescriptor();
//Saves .uproject
EVALUATE_OP_ACTION_RESULT(TrySave());
//When we are here, there should not be problems in generating Unreal Project for OpenPype
return 0;
}
FOPGenerateProjectParams::FOPGenerateProjectParams(): FOPGenerateProjectParams("")
{
}
FOPGenerateProjectParams::FOPGenerateProjectParams(const FString& CommandLineParams): CommandLineParams(
CommandLineParams)
{
UCommandlet::ParseCommandLine(*CommandLineParams, Tokens, Switches);
}
FProjectInformation FOPGenerateProjectParams::GenerateUEProjectInformation() const
{
FProjectInformation ProjectInformation = FProjectInformation();
ProjectInformation.ProjectFilename = GetProjectFileName();
ProjectInformation.bShouldGenerateCode = IsSwitchPresent("GenerateCode");
return ProjectInformation;
}
FString FOPGenerateProjectParams::TryGetToken(const int32 Index) const
{
return Tokens.IsValidIndex(Index) ? Tokens[Index] : "";
}
FString FOPGenerateProjectParams::GetProjectFileName() const
{
return TryGetToken(0);
}
bool FOPGenerateProjectParams::IsSwitchPresent(const FString& Switch) const
{
return INDEX_NONE != Switches.IndexOfByPredicate([&Switch](const FString& Item) -> bool
{
return Item.Equals(Switch);
}
);
}
UOPGenerateProjectCommandlet::UOPGenerateProjectCommandlet()
{
LogToConsole = true;
}
FOP_ActionResult UOPGenerateProjectCommandlet::TryCreateProject() const
{
FText FailReason;
FText FailLog;
TArray<FString> OutCreatedFiles;
if (!GameProjectUtils::CreateProject(ProjectInformation, FailReason, FailLog, &OutCreatedFiles))
return FOP_ActionResult(EOP_ActionResult::ProjectNotCreated, FailReason);
return FOP_ActionResult();
}
FOP_ActionResult UOPGenerateProjectCommandlet::TryLoadProjectDescriptor()
{
FText FailReason;
const bool bLoaded = ProjectDescriptor.Load(ProjectInformation.ProjectFilename, FailReason);
return FOP_ActionResult(bLoaded ? EOP_ActionResult::Ok : EOP_ActionResult::ProjectNotLoaded, FailReason);
}
void UOPGenerateProjectCommandlet::AttachPluginsToProjectDescriptor()
{
FPluginReferenceDescriptor OPPluginDescriptor;
OPPluginDescriptor.bEnabled = true;
OPPluginDescriptor.Name = OPConstants::OP_PluginName;
ProjectDescriptor.Plugins.Add(OPPluginDescriptor);
FPluginReferenceDescriptor PythonPluginDescriptor;
PythonPluginDescriptor.bEnabled = true;
PythonPluginDescriptor.Name = OPConstants::PythonScript_PluginName;
ProjectDescriptor.Plugins.Add(PythonPluginDescriptor);
FPluginReferenceDescriptor SequencerScriptingPluginDescriptor;
SequencerScriptingPluginDescriptor.bEnabled = true;
SequencerScriptingPluginDescriptor.Name = OPConstants::SequencerScripting_PluginName;
ProjectDescriptor.Plugins.Add(SequencerScriptingPluginDescriptor);
FPluginReferenceDescriptor MovieRenderPipelinePluginDescriptor;
MovieRenderPipelinePluginDescriptor.bEnabled = true;
MovieRenderPipelinePluginDescriptor.Name = OPConstants::MovieRenderPipeline_PluginName;
ProjectDescriptor.Plugins.Add(MovieRenderPipelinePluginDescriptor);
FPluginReferenceDescriptor EditorScriptingPluginDescriptor;
EditorScriptingPluginDescriptor.bEnabled = true;
EditorScriptingPluginDescriptor.Name = OPConstants::EditorScriptingUtils_PluginName;
ProjectDescriptor.Plugins.Add(EditorScriptingPluginDescriptor);
}
FOP_ActionResult UOPGenerateProjectCommandlet::TrySave()
{
FText FailReason;
const bool bSaved = ProjectDescriptor.Save(ProjectInformation.ProjectFilename, FailReason);
return FOP_ActionResult(bSaved ? EOP_ActionResult::Ok : EOP_ActionResult::ProjectNotSaved, FailReason);
}
FOPGenerateProjectParams UOPGenerateProjectCommandlet::ParseParameters(const FString& Params) const
{
FOPGenerateProjectParams ParamsResult;
TArray<FString> Tokens, Switches;
ParseCommandLine(*Params, Tokens, Switches);
return ParamsResult;
}

View file

@ -0,0 +1,41 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "Commandlets/OPActionResult.h"
#include "Logging/OP_Log.h"
EOP_ActionResult::Type& FOP_ActionResult::GetStatus()
{
return Status;
}
FText& FOP_ActionResult::GetReason()
{
return Reason;
}
FOP_ActionResult::FOP_ActionResult():Status(EOP_ActionResult::Type::Ok)
{
}
FOP_ActionResult::FOP_ActionResult(const EOP_ActionResult::Type& InEnum):Status(InEnum)
{
TryLog();
}
FOP_ActionResult::FOP_ActionResult(const EOP_ActionResult::Type& InEnum, const FText& InReason):Status(InEnum), Reason(InReason)
{
TryLog();
};
bool FOP_ActionResult::IsProblem() const
{
return Status != EOP_ActionResult::Ok;
}
void FOP_ActionResult::TryLog() const
{
if(IsProblem())
UE_LOG(LogCommandletOPGenerateProject, Error, TEXT("%s"), *Reason.ToString());
}

View file

@ -0,0 +1 @@
#include "Logging/OP_Log.h"

View file

@ -55,7 +55,7 @@ void UOpenPypePublishInstance::OnAssetCreated(const FAssetData& InAssetData)
if (!IsValid(Asset))
{
UE_LOG(LogAssetData, Warning, TEXT("Asset \"%s\" is not valid! Skipping the addition."),
*InAssetData.GetObjectPathString());
*InAssetData.ObjectPath.ToString());
return;
}

View file

@ -0,0 +1,60 @@
#pragma once
#include "GameProjectUtils.h"
#include "Commandlets/OPActionResult.h"
#include "ProjectDescriptor.h"
#include "Commandlets/Commandlet.h"
#include "OPGenerateProjectCommandlet.generated.h"
struct FProjectDescriptor;
struct FProjectInformation;
/**
* @brief Structure which parses command line parameters and generates FProjectInformation
*/
USTRUCT()
struct FOPGenerateProjectParams
{
GENERATED_BODY()
private:
FString CommandLineParams;
TArray<FString> Tokens;
TArray<FString> Switches;
public:
FOPGenerateProjectParams();
FOPGenerateProjectParams(const FString& CommandLineParams);
FProjectInformation GenerateUEProjectInformation() const;
private:
FString TryGetToken(const int32 Index) const;
FString GetProjectFileName() const;
bool IsSwitchPresent(const FString& Switch) const;
};
UCLASS()
class OPENPYPE_API UOPGenerateProjectCommandlet : public UCommandlet
{
GENERATED_BODY()
private:
FProjectInformation ProjectInformation;
FProjectDescriptor ProjectDescriptor;
public:
UOPGenerateProjectCommandlet();
virtual int32 Main(const FString& CommandLineParams) override;
private:
FOPGenerateProjectParams ParseParameters(const FString& Params) const;
FOP_ActionResult TryCreateProject() const;
FOP_ActionResult TryLoadProjectDescriptor();
void AttachPluginsToProjectDescriptor();
FOP_ActionResult TrySave();
};

View file

@ -0,0 +1,83 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "OPActionResult.generated.h"
/**
* @brief This macro returns error code when is problem or does nothing when there is no problem.
* @param ActionResult FOP_ActionResult structure
*/
#define EVALUATE_OP_ACTION_RESULT(ActionResult) \
if(ActionResult.IsProblem()) \
return ActionResult.GetStatus();
/**
* @brief This enum values are humanly readable mapping of error codes.
* Here should be all error codes to be possible find what went wrong.
* TODO: In the future should exists an web document where is mapped error code & what problem occured & how to repair it...
*/
UENUM()
namespace EOP_ActionResult
{
enum Type
{
Ok,
ProjectNotCreated,
ProjectNotLoaded,
ProjectNotSaved,
//....Here insert another values
//Do not remove!
//Usable for looping through enum values
__Last UMETA(Hidden)
};
}
/**
* @brief This struct holds action result enum and optionally reason of fail
*/
USTRUCT()
struct FOP_ActionResult
{
GENERATED_BODY()
public:
/** @brief Default constructor usable when there is no problem */
FOP_ActionResult();
/**
* @brief This constructor initializes variables & attempts to log when is error
* @param InEnum Status
*/
FOP_ActionResult(const EOP_ActionResult::Type& InEnum);
/**
* @brief This constructor initializes variables & attempts to log when is error
* @param InEnum Status
* @param InReason Reason of potential fail
*/
FOP_ActionResult(const EOP_ActionResult::Type& InEnum, const FText& InReason);
private:
/** @brief Action status */
EOP_ActionResult::Type Status;
/** @brief Optional reason of fail */
FText Reason;
public:
/**
* @brief Checks if there is problematic state
* @return true when status is not equal to EOP_ActionResult::Ok
*/
bool IsProblem() const;
EOP_ActionResult::Type& GetStatus();
FText& GetReason();
private:
void TryLog() const;
};

View file

@ -0,0 +1,3 @@
#pragma once
DEFINE_LOG_CATEGORY_STATIC(LogCommandletOPGenerateProject, Log, All);

View file

@ -0,0 +1,12 @@
#pragma once
namespace OPConstants
{
const FString OP_PluginName = "OpenPype";
const FString PythonScript_PluginName = "PythonScriptPlugin";
const FString SequencerScripting_PluginName = "SequencerScripting";
const FString MovieRenderPipeline_PluginName = "MovieRenderPipeline";
const FString EditorScriptingUtils_PluginName = "EditorScriptingUtilities";
}

View file

@ -17,7 +17,7 @@ public:
*
* @return - Set of UObjects. Careful! They are returning raw pointers. Seems like an issue in UE5
*/
UFUNCTION(BlueprintCallable, BlueprintPure)
UFUNCTION(BlueprintCallable, BlueprintPure, Category="Python")
TSet<UObject*> GetInternalAssets() const
{
//For some reason it can only return Raw Pointers? Seems like an issue which they haven't fixed.
@ -34,7 +34,7 @@ public:
*
* @return - TSet of assets (UObjects). Careful! They are returning raw pointers. Seems like an issue in UE5
*/
UFUNCTION(BlueprintCallable, BlueprintPure)
UFUNCTION(BlueprintCallable, BlueprintPure, Category="Python")
TSet<UObject*> GetExternalAssets() const
{
//For some reason it can only return Raw Pointers? Seems like an issue which they haven't fixed.
@ -54,7 +54,7 @@ public:
*
* @attention If the bAddExternalAssets variable is false, external assets won't be included!
*/
UFUNCTION(BlueprintCallable, BlueprintPure)
UFUNCTION(BlueprintCallable, BlueprintPure, Category="Python")
TSet<UObject*> GetAllAssets() const
{
const TSet<TSoftObjectPtr<UObject>>& IteratedSet = bAddExternalAssets

View file

@ -1,115 +0,0 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "AssetContainer.h"
#include "AssetRegistry/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.AssetClassPath.ToString();
UE_LOG(LogTemp, Log, TEXT("asset name %s"), *assetFName);
// 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.AssetClassPath.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.AssetClassPath.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

@ -1,20 +0,0 @@
#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

@ -1,39 +0,0 @@
// 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 "AssetRegistry/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

@ -1,21 +0,0 @@
// 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

@ -4,6 +4,10 @@
import os
import platform
import json
from typing import List
import openpype
from distutils import dir_util
import subprocess
import re
@ -73,7 +77,7 @@ def get_engine_versions(env=None):
return OrderedDict()
def get_editor_executable_path(engine_path: Path, engine_version: str) -> Path:
def get_editor_exe_path(engine_path: Path, engine_version: str) -> Path:
"""Get UE Editor executable path."""
ue_path = engine_path / "Engine/Binaries"
if platform.system().lower() == "windows":
@ -214,77 +218,58 @@ 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.
ue_modules = Path()
if platform.system().lower() == "windows":
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":
ue_modules = Path(os.path.join(engine_path, "Engine", "Binaries",
"Linux", "UE4Editor.modules"))
# engine_path should be the location of UE_X.X folder
if platform.system().lower() == "darwin":
ue_modules = Path(os.path.join(engine_path, "Engine", "Binaries",
"Mac", "UE4Editor.modules"))
ue_editor_exe_path: Path = get_editor_exe_path(engine_path, ue_version)
cmdlet_project_path = get_path_to_cmdlet_project(ue_version)
if ue_modules.exists():
print("--- Loading Engine ID from modules file ...")
with open(ue_modules, "r") as mp:
loaded_modules = json.load(mp)
project_file = pr_dir / f"{project_name}.uproject"
if loaded_modules.get("BuildId"):
ue_id = "{" + loaded_modules.get("BuildId") + "}"
plugins_path = None
if os.path.isdir(env.get("OPENPYPE_UNREAL_PLUGIN", "")):
# copy plugin to correct path under project
plugins_path = pr_dir / "Plugins"
openpype_plugin_path = plugins_path / "OpenPype"
if not openpype_plugin_path.is_dir():
openpype_plugin_path.mkdir(parents=True, exist_ok=True)
dir_util._path_created = {}
dir_util.copy_tree(os.environ.get("OPENPYPE_UNREAL_PLUGIN"),
openpype_plugin_path.as_posix())
if not (openpype_plugin_path / "Binaries").is_dir() \
or not (openpype_plugin_path / "Intermediate").is_dir():
dev_mode = True
# data for project file
data = {
"FileVersion": 3,
"EngineAssociation": ue_id,
"Category": "",
"Description": "",
"Plugins": [
{"Name": "PythonScriptPlugin", "Enabled": True},
{"Name": "EditorScriptingUtilities", "Enabled": True},
{"Name": "SequencerScripting", "Enabled": True},
{"Name": "MovieRenderPipeline", "Enabled": True},
{"Name": "OpenPype", "Enabled": True}
]
}
print("--- Generating a new project ...")
commandlet_cmd = [f'{ue_editor_exe_path.as_posix()}',
f'{cmdlet_project_path.as_posix()}',
f'-run=OPGenerateProject',
f'{project_file.resolve().as_posix()}']
if dev_mode or preset["dev_mode"]:
# this will add the project module and necessary source file to
# make it a C++ project and to (hopefully) make Unreal Editor to
# compile all # sources at start
commandlet_cmd.append('-GenerateCode')
data["Modules"] = [{
"Name": project_name,
"Type": "Runtime",
"LoadingPhase": "Default",
"AdditionalDependencies": ["Engine"],
}]
subprocess.run(commandlet_cmd)
# write project file
project_file = pr_dir / f"{project_name}.uproject"
with open(project_file, mode="w") as pf:
json.dump(data, pf, indent=4)
with open(project_file, mode="r+") as pf:
pf_json = json.load(pf)
pf_json["EngineAssociation"] = _get_build_id(engine_path, ue_version)
pf.seek(0)
json.dump(pf_json, pf, indent=4)
pf.truncate()
print(f'--- Engine ID has been writen into the project file')
if dev_mode or preset["dev_mode"]:
u_build_tool = get_path_to_ubt(engine_path, 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"
command1 = [u_build_tool.as_posix(), "-projectfiles",
f"-project={project_file}", "-progress"]
subprocess.run(command1)
command2 = [u_build_tool.as_posix(),
f"-ModuleWithSuffix={project_name},3555", arch,
"Development", "-TargetType=Editor",
f'-Project={project_file}',
f'{project_file}',
"-IgnoreJunk"]
subprocess.run(command2)
# ensure we have PySide2 installed in engine
python_path = None
@ -307,8 +292,121 @@ def create_unreal_project(project_name: str,
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, ue_version)
def get_path_to_uat(engine_path: Path) -> Path:
if platform.system().lower() == "windows":
return engine_path / "Engine/Build/BatchFiles/RunUAT.bat"
if platform.system().lower() == "linux" or platform.system().lower() == "darwin":
return engine_path / "Engine/Build/BatchFiles/RunUAT.sh"
def get_path_to_cmdlet_project(ue_version: str) -> Path:
commandlet_project_path: Path = Path(os.path.dirname(os.path.abspath(openpype.__file__)))
# For now, only tested on Windows (For Linux and Mac it has to be implemented)
if ue_version.split(".")[0] == "4":
return commandlet_project_path / "hosts/unreal/integration/UE_4.7/CommandletProject/CommandletProject.uproject"
elif ue_version.split(".")[0] == "5":
return commandlet_project_path / "hosts/unreal/integration/UE_5.0/CommandletProject/CommandletProject.uproject"
def get_path_to_ubt(engine_path: Path, ue_version: str) -> Path:
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"
return Path(u_build_tool_path)
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"
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":
ue_modules = Path(os.path.join(engine_path, "Engine", "Binaries",
"Linux", "UE4Editor.modules"))
if platform.system().lower() == "darwin":
ue_modules = Path(os.path.join(engine_path, "Engine", "Binaries",
"Mac", "UE4Editor.modules"))
if ue_modules.exists():
print("--- Loading Engine ID from modules file ...")
with open(ue_modules, "r") as mp:
loaded_modules = json.load(mp)
if loaded_modules.get("BuildId"):
return "{" + loaded_modules.get("BuildId") + "}"
def try_installing_plugin(engine_path: Path,
ue_version: str,
env: dict = None) -> None:
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
openpype_plugin_path: Path = engine_path / "Engine/Plugins/Marketplace/OpenPype"
if not openpype_plugin_path.is_dir():
print("--- OpenPype Plugin is not present. Creating a new plugin directory ...")
openpype_plugin_path.mkdir(parents=True, exist_ok=True)
engine_plugin_config_path: Path = openpype_plugin_path / "Config"
engine_plugin_config_path.mkdir(exist_ok=True)
dir_util._path_created = {}
if not (openpype_plugin_path / "Binaries").is_dir() \
or not (openpype_plugin_path / "Intermediate").is_dir():
print("--- Binaries are not present. Building the plugin ...")
_build_and_move_integration_plugin(engine_path, openpype_plugin_path, env)
def _build_and_move_integration_plugin(engine_path: Path,
plugin_build_path: Path,
env: dict = None) -> None:
uat_path: Path = get_path_to_uat(engine_path)
env = env or os.environ
integration_plugin_path: Path = Path(env.get("OPENPYPE_UNREAL_PLUGIN", ""))
if uat_path.is_file():
temp_dir: Path = integration_plugin_path.parent / "Temp"
temp_dir.mkdir(exist_ok=True)
uplugin_path: Path = integration_plugin_path / "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()}']
subprocess.run(build_plugin_cmd)
# 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"
integration_plugin_config_path = integration_plugin_path / "Config"
dir_util.copy_tree(integration_plugin_config_path.as_posix(), plugin_install_config_path.as_posix())
dir_util.remove_tree(temp_dir.as_posix())
def _prepare_cpp_project(