wip on integrating avalon functionality

This commit is contained in:
Ondřej Samohel 2022-02-21 18:42:48 +01:00
parent 9294a21635
commit aa9df7edd5
No known key found for this signature in database
GPG key ID: 02376E18990A97C6
29 changed files with 1282 additions and 14 deletions

View file

@ -3,11 +3,12 @@ import os
def add_implementation_envs(env, _app):
"""Modify environments to contain all required for implementation."""
# Set AVALON_UNREAL_PLUGIN required for Unreal implementation
# Set OPENPYPE_UNREAL_PLUGIN required for Unreal implementation
unreal_plugin_path = os.path.join(
os.environ["OPENPYPE_REPOS_ROOT"], "repos", "avalon-unreal-integration"
os.environ["OPENPYPE_ROOT"], "openpype", "hosts",
"unreal", "integration"
)
env["AVALON_UNREAL_PLUGIN"] = unreal_plugin_path
env["OPENPYPE_UNREAL_PLUGIN"] = unreal_plugin_path
# Set default environments if are not set via settings
defaults = {

View file

@ -0,0 +1,44 @@
# -*- coding: utf-8 -*-
import unreal # noqa
class OpenPypeUnrealException(Exception):
pass
@unreal.uclass()
class OpenPypeHelpers(unreal.OpenPypeLib):
"""Class wrapping some useful functions for OpenPype.
This class is extending native BP class in OpenPype Integration Plugin.
"""
@unreal.ufunction(params=[str, unreal.LinearColor, bool])
def set_folder_color(self, path: str, color: unreal.LinearColor) -> Bool:
"""Set color on folder in Content Browser.
This method sets color on folder in Content Browser. Unfortunately
there is no way to refresh Content Browser so new color isn't applied
immediately. They are saved to config file and appears correctly
only after Editor is restarted.
Args:
path (str): Path to folder
color (:class:`unreal.LinearColor`): Color of the folder
Example:
AvalonHelpers().set_folder_color(
"/Game/Path", unreal.LinearColor(a=1.0, r=1.0, g=0.5, b=0)
)
Note:
This will take effect only after Editor is restarted. I couldn't
find a way to refresh it. Also this saves the color definition
into the project config, binding this path with color. So if you
delete this path and later re-create, it will set this color
again.
"""
self.c_set_folder_color(path, color, False)

View file

@ -169,11 +169,11 @@ def create_unreal_project(project_name: str,
env: dict = None) -> None:
"""This will create `.uproject` file at specified location.
As there is no way I know to create project via command line, this is
easiest option. Unreal project file is basically JSON file. If we find
`AVALON_UNREAL_PLUGIN` environment variable we assume this is location
of Avalon Integration Plugin and we copy its content to project folder
and enable this plugin.
As there is no way I know to create a project via command line, this is
easiest option. Unreal project file is basically a JSON file. If we find
the `OPENPYPE_UNREAL_PLUGIN` environment variable we assume this is the
location of the Integration Plugin and we copy its content to the project
folder and enable this plugin.
Args:
project_name (str): Name of the project.
@ -254,14 +254,14 @@ def create_unreal_project(project_name: str,
{"Name": "PythonScriptPlugin", "Enabled": True},
{"Name": "EditorScriptingUtilities", "Enabled": True},
{"Name": "SequencerScripting", "Enabled": True},
{"Name": "Avalon", "Enabled": True}
{"Name": "OpenPype", "Enabled": True}
]
}
if dev_mode or preset["dev_mode"]:
# this will add project module and necessary source file to make it
# C++ project and to (hopefully) make Unreal Editor to compile all
# sources at start
# 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
data["Modules"] = [{
"Name": project_name,

View file

@ -0,0 +1,388 @@
# -*- coding: utf-8 -*-
import sys
import pyblish.api
from avalon.pipeline import AVALON_CONTAINER_ID
import unreal # noqa
from typing import List
from openpype.tools.utils import host_tools
from avalon import api
AVALON_CONTAINERS = "OpenPypeContainers"
def install():
pyblish.api.register_host("unreal")
_register_callbacks()
_register_events()
def _register_callbacks():
"""
TODO: Implement callbacks if supported by UE4
"""
pass
def _register_events():
"""
TODO: Implement callbacks if supported by UE4
"""
pass
def uninstall():
pyblish.api.deregister_host("unreal")
class Creator(api.Creator):
hosts = ["unreal"]
asset_types = []
def process(self):
nodes = list()
with unreal.ScopedEditorTransaction("Avalon Creating Instance"):
if (self.options or {}).get("useSelection"):
self.log.info("setting ...")
print("settings ...")
nodes = unreal.EditorUtilityLibrary.get_selected_assets()
asset_paths = [a.get_path_name() for a in nodes]
self.name = move_assets_to_path(
"/Game", self.name, asset_paths
)
instance = create_publish_instance("/Game", self.name)
imprint(instance, self.data)
return instance
class Loader(api.Loader):
hosts = ["unreal"]
def ls():
"""
List all containers found in *Content Manager* of Unreal and return
metadata from them. Adding `objectName` to set.
"""
ar = unreal.AssetRegistryHelpers.get_asset_registry()
avalon_containers = ar.get_assets_by_class("AssetContainer", True)
# get_asset_by_class returns AssetData. To get all metadata we need to
# load asset. get_tag_values() work only on metadata registered in
# Asset Registy Project settings (and there is no way to set it with
# python short of editing ini configuration file).
for asset_data in avalon_containers:
asset = asset_data.get_asset()
data = unreal.EditorAssetLibrary.get_metadata_tag_values(asset)
data["objectName"] = asset_data.asset_name
data = cast_map_to_str_dict(data)
yield data
def parse_container(container):
"""
To get data from container, AssetContainer must be loaded.
Args:
container(str): path to container
Returns:
dict: metadata stored on container
"""
asset = unreal.EditorAssetLibrary.load_asset(container)
data = unreal.EditorAssetLibrary.get_metadata_tag_values(asset)
data["objectName"] = asset.get_name()
data = cast_map_to_str_dict(data)
return data
def publish():
"""Shorthand to publish from within host"""
import pyblish.util
return pyblish.util.publish()
def containerise(name, namespace, nodes, context, loader=None, suffix="_CON"):
"""Bundles *nodes* (assets) into a *container* and add metadata to it.
Unreal doesn't support *groups* of assets that you can add metadata to.
But it does support folders that helps to organize asset. Unfortunately
those folders are just that - you cannot add any additional information
to them. `Avalon Integration Plugin`_ is providing way out - Implementing
`AssetContainer` Blueprint class. This class when added to folder can
handle metadata on it using standard
:func:`unreal.EditorAssetLibrary.set_metadata_tag()` and
:func:`unreal.EditorAssetLibrary.get_metadata_tag_values()`. It also
stores and monitor all changes in assets in path where it resides. List of
those assets is available as `assets` property.
This is list of strings starting with asset type and ending with its path:
`Material /Game/Avalon/Test/TestMaterial.TestMaterial`
.. _Avalon Integration Plugin:
https://github.com/pypeclub/avalon-unreal-integration
"""
# 1 - create directory for container
root = "/Game"
container_name = "{}{}".format(name, suffix)
new_name = move_assets_to_path(root, container_name, nodes)
# 2 - create Asset Container there
path = "{}/{}".format(root, new_name)
create_container(container=container_name, path=path)
namespace = path
data = {
"schema": "openpype:container-2.0",
"id": AVALON_CONTAINER_ID,
"name": new_name,
"namespace": namespace,
"loader": str(loader),
"representation": context["representation"]["_id"],
}
# 3 - imprint data
imprint("{}/{}".format(path, container_name), data)
return path
def instantiate(root, name, data, assets=None, suffix="_INS"):
"""
Bundles *nodes* into *container* marking it with metadata as publishable
instance. If assets are provided, they are moved to new path where
`AvalonPublishInstance` class asset is created and imprinted with metadata.
This can then be collected for publishing by Pyblish for example.
Args:
root (str): root path where to create instance container
name (str): name of the container
data (dict): data to imprint on container
assets (list of str): list of asset paths to include in publish
instance
suffix (str): suffix string to append to instance name
"""
container_name = "{}{}".format(name, suffix)
# if we specify assets, create new folder and move them there. If not,
# just create empty folder
if assets:
new_name = move_assets_to_path(root, container_name, assets)
else:
new_name = create_folder(root, name)
path = "{}/{}".format(root, new_name)
create_publish_instance(instance=container_name, path=path)
imprint("{}/{}".format(path, container_name), data)
def imprint(node, data):
loaded_asset = unreal.EditorAssetLibrary.load_asset(node)
for key, value in data.items():
# Support values evaluated at imprint
if callable(value):
value = value()
# Unreal doesn't support NoneType in metadata values
if value is None:
value = ""
unreal.EditorAssetLibrary.set_metadata_tag(
loaded_asset, key, str(value)
)
with unreal.ScopedEditorTransaction("Avalon containerising"):
unreal.EditorAssetLibrary.save_asset(node)
def show_tools_popup():
"""Show popup with tools.
Popup will disappear on click or loosing focus.
"""
from openpype.hosts.unreal.api import tools_ui
tools_ui.show_tools_popup()
def show_tools_dialog():
"""Show dialog with tools.
Dialog will stay visible.
"""
from openpype.hosts.unreal.api import tools_ui
tools_ui.show_tools_dialog()
def show_creator():
host_tools.show_creator()
def show_loader():
host_tools.show_loader(use_context=True)
def show_publisher():
host_tools.show_publish()
def show_manager():
host_tools.show_scene_inventory()
def show_experimental_tools():
host_tools.show_experimental_tools_dialog()
def create_folder(root: str, name: str) -> str:
"""Create new folder
If folder exists, append number at the end and try again, incrementing
if needed.
Args:
root (str): path root
name (str): folder name
Returns:
str: folder name
Example:
>>> create_folder("/Game/Foo")
/Game/Foo
>>> create_folder("/Game/Foo")
/Game/Foo1
"""
eal = unreal.EditorAssetLibrary
index = 1
while True:
if eal.does_directory_exist("{}/{}".format(root, name)):
name = "{}{}".format(name, index)
index += 1
else:
eal.make_directory("{}/{}".format(root, name))
break
return name
def move_assets_to_path(root: str, name: str, assets: List[str]) -> str:
"""
Moving (renaming) list of asset paths to new destination.
Args:
root (str): root of the path (eg. `/Game`)
name (str): name of destination directory (eg. `Foo` )
assets (list of str): list of asset paths
Returns:
str: folder name
Example:
This will get paths of all assets under `/Game/Test` and move them
to `/Game/NewTest`. If `/Game/NewTest` already exists, then resulting
path will be `/Game/NewTest1`
>>> assets = unreal.EditorAssetLibrary.list_assets("/Game/Test")
>>> move_assets_to_path("/Game", "NewTest", assets)
NewTest
"""
eal = unreal.EditorAssetLibrary
name = create_folder(root, name)
unreal.log(assets)
for asset in assets:
loaded = eal.load_asset(asset)
eal.rename_asset(
asset, "{}/{}/{}".format(root, name, loaded.get_name())
)
return name
def create_container(container: str, path: str) -> unreal.Object:
"""
Helper function to create Asset Container class on given path.
This Asset Class helps to mark given path as Container
and enable asset version control on it.
Args:
container (str): Asset Container name
path (str): Path where to create Asset Container. This path should
point into container folder
Returns:
:class:`unreal.Object`: instance of created asset
Example:
create_avalon_container(
"/Game/modelingFooCharacter_CON",
"modelingFooCharacter_CON"
)
"""
factory = unreal.AssetContainerFactory()
tools = unreal.AssetToolsHelpers().get_asset_tools()
asset = tools.create_asset(container, path, None, factory)
return asset
def create_publish_instance(instance: str, path: str) -> unreal.Object:
"""
Helper function to create Avalon Publish Instance on given path.
This behaves similary as :func:`create_avalon_container`.
Args:
path (str): Path where to create Publish Instance.
This path should point into container folder
instance (str): Publish Instance name
Returns:
:class:`unreal.Object`: instance of created asset
Example:
create_publish_instance(
"/Game/modelingFooCharacter_INST",
"modelingFooCharacter_INST"
)
"""
factory = unreal.AvalonPublishInstanceFactory()
tools = unreal.AssetToolsHelpers().get_asset_tools()
asset = tools.create_asset(instance, path, None, factory)
return asset
def cast_map_to_str_dict(map) -> dict:
"""Cast Unreal Map to dict.
Helper function to cast Unreal Map object to plain old python
dict. This will also cast values and keys to str. Useful for
metadata dicts.
Args:
map: Unreal Map object
Returns:
dict
"""
return {str(key): str(value) for (key, value) in map.items()}

View file

@ -1,5 +1,8 @@
from avalon import api
# -*- coding: utf-8 -*-
from abc import ABC
import openpype.api
import avalon.api
class Creator(openpype.api.Creator):
@ -7,6 +10,6 @@ class Creator(openpype.api.Creator):
defaults = ['Main']
class Loader(api.Loader):
class Loader(avalon.api.Loader, ABC):
"""This serves as skeleton for future OpenPype specific functionality"""
pass

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,27 @@
import unreal
avalon_detected = True
try:
from avalon import api
from avalon import unreal as avalon_unreal
except ImportError as exc:
avalon_detected = False
unreal.log_error("Avalon: cannot load avalon [ {} ]".format(exc))
if avalon_detected:
api.install(avalon_unreal)
@unreal.uclass()
class AvalonIntegration(unreal.AvalonPythonBridge):
@unreal.ufunction(override=True)
def RunInPython_Popup(self):
unreal.log_warning("Avalon: showing tools popup")
if avalon_detected:
avalon_unreal.show_tools_popup()
@unreal.ufunction(override=True)
def RunInPython_Dialog(self):
unreal.log_warning("Avalon: showing tools dialog")
if avalon_detected:
avalon_unreal.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
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,57 @@
// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
using UnrealBuildTool;
public class Avalon : ModuleRules
{
public Avalon(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",
"UnrealEd",
"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,103 @@
#include "Avalon.h"
#include "LevelEditor.h"
#include "AvalonPythonBridge.h"
#include "AvalonStyle.h"
static const FName AvalonTabName("Avalon");
#define LOCTEXT_NAMESPACE "FAvalonModule"
// This function is triggered when the plugin is staring up
void FAvalonModule::StartupModule()
{
FAvalonStyle::Initialize();
FAvalonStyle::SetIcon("Logo", "openpype40");
// 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());
MenuExtender->AddMenuExtension(
"LevelEditor",
EExtensionHook::After,
NULL,
FMenuExtensionDelegate::CreateRaw(this, &FAvalonModule::AddMenuEntry)
);
ToolbarExtender->AddToolBarExtension(
"Settings",
EExtensionHook::After,
NULL,
FToolBarExtensionDelegate::CreateRaw(this, &FAvalonModule::AddToobarEntry));
LevelEditorModule.GetMenuExtensibilityManager()->AddExtender(MenuExtender);
LevelEditorModule.GetToolBarExtensibilityManager()->AddExtender(ToolbarExtender);
}
void FAvalonModule::ShutdownModule()
{
FAvalonStyle::Shutdown();
}
void FAvalonModule::AddMenuEntry(FMenuBuilder& MenuBuilder)
{
// Create Section
MenuBuilder.BeginSection("OpenPype", TAttribute<FText>(FText::FromString("OpenPype")));
{
// Create a Submenu inside of the Section
MenuBuilder.AddMenuEntry(
FText::FromString("Tools..."),
FText::FromString("Pipeline tools"),
FSlateIcon(FAvalonStyle::GetStyleSetName(), "OpenPype.Logo"),
FUIAction(FExecuteAction::CreateRaw(this, &FAvalonModule::MenuPopup))
);
MenuBuilder.AddMenuEntry(
FText::FromString("Tools dialog..."),
FText::FromString("Pipeline tools dialog"),
FSlateIcon(FAvalonStyle::GetStyleSetName(), "OpenPype.Logo"),
FUIAction(FExecuteAction::CreateRaw(this, &FAvalonModule::MenuDialog))
);
}
MenuBuilder.EndSection();
}
void FAvalonModule::AddToobarEntry(FToolBarBuilder& ToolbarBuilder)
{
ToolbarBuilder.BeginSection(TEXT("OpenPype"));
{
ToolbarBuilder.AddToolBarButton(
FUIAction(
FExecuteAction::CreateRaw(this, &FAvalonModule::MenuPopup),
NULL,
FIsActionChecked()
),
NAME_None,
LOCTEXT("OpenPype_label", "OpenPype"),
LOCTEXT("OpenPype_tooltip", "OpenPype Tools"),
FSlateIcon(FAvalonStyle::GetStyleSetName(), "OpenPype.Logo")
);
}
ToolbarBuilder.EndSection();
}
void FAvalonModule::MenuPopup() {
UAvalonPythonBridge* bridge = UAvalonPythonBridge::Get();
bridge->RunInPython_Popup();
}
void FAvalonModule::MenuDialog() {
UAvalonPythonBridge* bridge = UAvalonPythonBridge::Get();
bridge->RunInPython_Dialog();
}
IMPLEMENT_MODULE(FAvalonModule, Avalon)

View file

@ -0,0 +1,48 @@
#include "AvalonLib.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 UAvalonLib::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> UAvalonLib::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 "AvalonPublishInstance.h"
#include "AssetRegistryModule.h"
UAvalonPublishInstance::UAvalonPublishInstance(const FObjectInitializer& ObjectInitializer)
: UObject(ObjectInitializer)
{
FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>("AssetRegistry");
FString path = UAvalonPublishInstance::GetPathName();
FARFilter Filter;
Filter.PackagePaths.Add(FName(*path));
AssetRegistryModule.Get().OnAssetAdded().AddUObject(this, &UAvalonPublishInstance::OnAssetAdded);
AssetRegistryModule.Get().OnAssetRemoved().AddUObject(this, &UAvalonPublishInstance::OnAssetRemoved);
AssetRegistryModule.Get().OnAssetRenamed().AddUObject(this, &UAvalonPublishInstance::OnAssetRenamed);
}
void UAvalonPublishInstance::OnAssetAdded(const FAssetData& AssetData)
{
TArray<FString> split;
// get directory of current container
FString selfFullPath = UAvalonPublishInstance::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 != "AvalonPublishInstance")
{
assets.Add(assetPath);
UE_LOG(LogTemp, Log, TEXT("%s: asset added to %s"), *selfFullPath, *selfDir);
}
}
}
void UAvalonPublishInstance::OnAssetRemoved(const FAssetData& AssetData)
{
TArray<FString> split;
// get directory of current container
FString selfFullPath = UAvalonPublishInstance::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 = UAvalonPublishInstance::GetPathName();
FString lpp = FPackageName::GetLongPackagePath(*path);
if (assetDir.StartsWith(*selfDir))
{
// exclude self
if (assetFName != "AvalonPublishInstance")
{
// UE_LOG(LogTemp, Warning, TEXT("%s: asset removed"), *lpp);
assets.Remove(assetPath);
}
}
}
void UAvalonPublishInstance::OnAssetRenamed(const FAssetData& AssetData, const FString& str)
{
TArray<FString> split;
// get directory of current container
FString selfFullPath = UAvalonPublishInstance::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 "AvalonPublishInstanceFactory.h"
#include "AvalonPublishInstance.h"
UAvalonPublishInstanceFactory::UAvalonPublishInstanceFactory(const FObjectInitializer& ObjectInitializer)
: UFactory(ObjectInitializer)
{
SupportedClass = UAvalonPublishInstance::StaticClass();
bCreateNew = false;
bEditorImport = true;
}
UObject* UAvalonPublishInstanceFactory::FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn)
{
UAvalonPublishInstance* AvalonPublishInstance = NewObject<UAvalonPublishInstance>(InParent, Class, Name, Flags);
return AvalonPublishInstance;
}
bool UAvalonPublishInstanceFactory::ShouldShowInNewMenu() const {
return false;
}

View file

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

View file

@ -0,0 +1,69 @@
#include "AvalonStyle.h"
#include "Framework/Application/SlateApplication.h"
#include "Styling/SlateStyle.h"
#include "Styling/SlateStyleRegistry.h"
TUniquePtr< FSlateStyleSet > FAvalonStyle::AvalonStyleInstance = nullptr;
void FAvalonStyle::Initialize()
{
if (!AvalonStyleInstance.IsValid())
{
AvalonStyleInstance = Create();
FSlateStyleRegistry::RegisterSlateStyle(*AvalonStyleInstance);
}
}
void FAvalonStyle::Shutdown()
{
if (AvalonStyleInstance.IsValid())
{
FSlateStyleRegistry::UnRegisterSlateStyle(*AvalonStyleInstance);
AvalonStyleInstance.Reset();
}
}
FName FAvalonStyle::GetStyleSetName()
{
static FName StyleSetName(TEXT("AvalonStyle"));
return StyleSetName;
}
FName FAvalonStyle::GetContextName()
{
static FName ContextName(TEXT("OpenPype"));
return ContextName;
}
#define IMAGE_BRUSH(RelativePath, ...) FSlateImageBrush( Style->RootToContentDir( RelativePath, TEXT(".png") ), __VA_ARGS__ )
const FVector2D Icon40x40(40.0f, 40.0f);
TUniquePtr< FSlateStyleSet > FAvalonStyle::Create()
{
TUniquePtr< FSlateStyleSet > Style = MakeUnique<FSlateStyleSet>(GetStyleSetName());
Style->SetContentRoot(FPaths::ProjectPluginsDir() / TEXT("Avalon/Resources"));
return Style;
}
void FAvalonStyle::SetIcon(const FString& StyleName, const FString& ResourcePath)
{
FSlateStyleSet* Style = AvalonStyleInstance.Get();
FString Name(GetContextName().ToString());
Name = Name + "." + StyleName;
Style->Set(*Name, new FSlateImageBrush(Style->RootToContentDir(ResourcePath, TEXT(".png")), Icon40x40));
FSlateApplication::Get().GetRenderer()->ReloadTextureResources();
}
#undef IMAGE_BRUSH
const ISlateStyle& FAvalonStyle::Get()
{
check(AvalonStyleInstance);
return *AvalonStyleInstance;
}

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 AVALON_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 AVALON_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,21 @@
// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
#pragma once
#include "Engine.h"
class FAvalonModule : public IModuleInterface
{
public:
virtual void StartupModule() override;
virtual void ShutdownModule() override;
private:
void AddMenuEntry(FMenuBuilder& MenuBuilder);
void AddToobarEntry(FToolBarBuilder& ToolbarBuilder);
void MenuPopup();
void MenuDialog();
};

View file

@ -0,0 +1,19 @@
#pragma once
#include "Engine.h"
#include "AvalonLib.generated.h"
UCLASS(Blueprintable)
class AVALON_API UAvalonLib : 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 "AvalonPublishInstance.generated.h"
UCLASS(Blueprintable)
class AVALON_API UAvalonPublishInstance : public UObject
{
GENERATED_BODY()
public:
UAvalonPublishInstance(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 "AvalonPublishInstanceFactory.generated.h"
/**
*
*/
UCLASS()
class AVALON_API UAvalonPublishInstanceFactory : public UFactory
{
GENERATED_BODY()
public:
UAvalonPublishInstanceFactory(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 "AvalonPythonBridge.generated.h"
UCLASS(Blueprintable)
class UAvalonPythonBridge : public UObject
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable, Category = Python)
static UAvalonPythonBridge* Get();
UFUNCTION(BlueprintImplementableEvent, Category = Python)
void RunInPython_Popup() const;
UFUNCTION(BlueprintImplementableEvent, Category = Python)
void RunInPython_Dialog() const;
};

View file

@ -0,0 +1,22 @@
#pragma once
#include "CoreMinimal.h"
class FSlateStyleSet;
class ISlateStyle;
class FAvalonStyle
{
public:
static void Initialize();
static void Shutdown();
static const ISlateStyle& Get();
static FName GetStyleSetName();
static FName GetContextName();
static void SetIcon(const FString& StyleName, const FString& ResourcePath);
private:
static TUniquePtr< FSlateStyleSet > Create();
static TUniquePtr< FSlateStyleSet > AvalonStyleInstance;
};