Material instances are now pretty polished. We can create them, and dump and modify all types of parameters. Factory-based creation is now more polished, and it opens new assets in the editor if it can.

This commit is contained in:
2026-04-07 17:43:54 -04:00
parent 12d924904d
commit 476b0057f5
8 changed files with 142 additions and 36 deletions

View File

@@ -296,8 +296,9 @@ public:
if (!Props[0].SetObject(ClassObj, WingOut::Stdout)) return; if (!Props[0].SetObject(ClassObj, WingOut::Stdout)) return;
// Create the asset using the factory. // Create the asset using the factory.
UObject *Blueprint = WingFactories::CreateAsset(Path, Factory, WingOut::Stdout); UObject *Object = WingFactories::CreateAsset(
if (Blueprint == nullptr) return; Path, Factory, nullptr, WingOut::Stdout);
if (Object == nullptr) return;
WingOut::Stdout.Printf(TEXT("Created.\n")); WingOut::Stdout.Printf(TEXT("Created.\n"));
} }
}; };

View File

@@ -0,0 +1,63 @@
#pragma once
#include "CoreMinimal.h"
#include "WingServer.h"
#include "WingHandler.h"
#include "WingFetcher.h"
#include "WingUtils.h"
#include "Materials/Material.h"
#include "Materials/MaterialInterface.h"
#include "Materials/MaterialInstanceConstant.h"
#include "Factories/MaterialInstanceConstantFactoryNew.h"
#include "WingFactories.h"
#include "Create_MaterialInstance.generated.h"
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
class UWing_Create_MaterialInstance : public UWingHandler
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, meta=(Description="Full asset path for the new Material Instance (e.g. '/Game/Materials/MI_GoldShiny')"))
FString AssetPath;
UPROPERTY(EditAnywhere, meta=(Description="Parent material package path (Material or Material Instance)"))
FString ParentMaterial;
virtual void Register() override
{
UWingServer::AddHandler(this,
TEXT("Create a new Material Instance Constant asset with a specified parent material."));
}
virtual void Handle() override
{
// Verify that the asset path is valid and available.
if (!WingFactories::CheckNewAssetPath(AssetPath, WingOut::Stdout)) return;
// Load parent material by package path.
WingFetcher F(WingOut::Stdout);
UMaterialInterface* ParentObj = F.Asset(ParentMaterial).Cast<UMaterialInterface>();
if (!ParentObj) return;
// Create a factory, then use it to create an asset.
UMaterialInstanceConstantFactoryNew* Factory =
NewObject<UMaterialInstanceConstantFactoryNew>();
if (!WingUtils::CheckNewObjectNotNull(
Factory, TEXT("factory"), WingOut::Stdout)) return;
UObject* MIO = WingFactories::CreateAsset(AssetPath,
Factory, UMaterialInstanceConstant::StaticClass(), WingOut::Stdout);
if (!MIO) return;
// Set parent.
UMaterialInstanceConstant* MI = Cast<UMaterialInstanceConstant>(MIO);
MI->Parent = ParentObj;
WingOut::Stdout.Printf(TEXT("Created\n"));
}
};

View File

@@ -24,6 +24,7 @@ public:
for (UClass* Class : FactoryClasses) for (UClass* Class : FactoryClasses)
{ {
if (!WingFactories::CanCreate(Class)) continue; if (!WingFactories::CanCreate(Class)) continue;
if (WingFactories::IsSpecialCase(Class)) continue;
if (WingFactories::GetParameterNames(Class).Num() > 0) continue; if (WingFactories::GetParameterNames(Class).Num() > 0) continue;
FString FactoryName = WingFactories::DeriveFactoryName(Class); FString FactoryName = WingFactories::DeriveFactoryName(Class);
@@ -49,7 +50,8 @@ public:
{ {
UClass* FactoryClass = Configuration->FactoryClass.Get(); UClass* FactoryClass = Configuration->FactoryClass.Get();
UFactory* Factory = NewObject<UFactory>(GetTransientPackage(), FactoryClass); UFactory* Factory = NewObject<UFactory>(GetTransientPackage(), FactoryClass);
WingFactories::CreateAsset(Path, Factory, WingOut::Stdout); UObject *Obj = WingFactories::CreateAsset(Path, Factory, nullptr, WingOut::Stdout);
if (!Obj) return;
WingOut::Stdout.Printf(TEXT("Created.\n")); WingOut::Stdout.Printf(TEXT("Created.\n"));
} }
}; };

View File

@@ -4,7 +4,9 @@
#include "PackageTools.h" #include "PackageTools.h"
#include "WingProperty.h" #include "WingProperty.h"
#include "AssetRegistry/AssetRegistryModule.h" #include "AssetRegistry/AssetRegistryModule.h"
#include "Editor.h"
#include "Kismet2/EnumEditorUtils.h" #include "Kismet2/EnumEditorUtils.h"
#include "Subsystems/AssetEditorSubsystem.h"
bool WingFactories::CheckNewAssetPath(const FString& Path, WingOut Errors) bool WingFactories::CheckNewAssetPath(const FString& Path, WingOut Errors)
{ {
@@ -39,6 +41,14 @@ bool WingFactories::IsBlacklisted(TSubclassOf<UFactory> FactoryClass)
return false; return false;
} }
bool WingFactories::IsSpecialCase(TSubclassOf<UFactory> FactoryClass)
{
if (FactoryClass == nullptr) return false;
FName Name = FactoryClass->GetFName();
if (Name == TEXT("MaterialInstanceConstantFactoryNew")) return true;
return false;
}
bool WingFactories::CanCreate(TSubclassOf<UFactory> FactoryClass) bool WingFactories::CanCreate(TSubclassOf<UFactory> FactoryClass)
{ {
if (FactoryClass == nullptr) return false; if (FactoryClass == nullptr) return false;
@@ -53,7 +63,8 @@ TArray<FName> WingFactories::GetParameterNames(TSubclassOf<UFactory> FactoryClas
return FWingProperty::GetVisibleNames(FactoryClass); return FWingProperty::GetVisibleNames(FactoryClass);
} }
UObject* WingFactories::CreateAsset(const FString& Path, UFactory* Factory, WingOut Errors) UObject* WingFactories::CreateAsset(
const FString& Path, UFactory* Factory, UClass *CheckIs, WingOut Errors)
{ {
// Check the blacklist. // Check the blacklist.
if (IsBlacklisted(Factory->GetClass())) if (IsBlacklisted(Factory->GetClass()))
@@ -84,9 +95,28 @@ UObject* WingFactories::CreateAsset(const FString& Path, UFactory* Factory, Wing
GWarn GWarn
); );
// If the factory created nothing, report the error.
if (NewAsset == nullptr)
{
Errors.Printf(TEXT("ERROR: Factory '%s' failed to create an object\n"),
*Factory->GetClass()->GetName());
}
// Verify the class of the created object.
if ((NewAsset != nullptr) && (CheckIs != nullptr))
{
if (!NewAsset->IsA(CheckIs))
{
Errors.Printf(TEXT("ERROR: factory did not create a '%s'\n"),
*CheckIs->GetName());
NewAsset = nullptr;
}
}
// If the asset was created, turn the package into a // If the asset was created, turn the package into a
// permanent package and encourge the editor to save it // permanent package and encourge the editor to save it
// to disk. Otherwise, mark the package for deletion. // to disk. Also, open the asset in the editor if possible.
// Otherwise, mark the package for deletion.
if (NewAsset) if (NewAsset)
{ {
Package->ClearFlags(RF_Transient); Package->ClearFlags(RF_Transient);
@@ -94,10 +124,11 @@ UObject* WingFactories::CreateAsset(const FString& Path, UFactory* Factory, Wing
Package->MarkPackageDirty(); Package->MarkPackageDirty();
FAssetRegistryModule::AssetCreated(NewAsset); FAssetRegistryModule::AssetCreated(NewAsset);
UWingServer::AddTouchedObject(NewAsset); UWingServer::AddTouchedObject(NewAsset);
UAssetEditorSubsystem* Sub = GEditor->GetEditorSubsystem<UAssetEditorSubsystem>();
if (Sub) Sub->OpenEditorForAsset(NewAsset);
} }
else else
{ {
Errors.Printf(TEXT("ERROR: Factory '%s' failed to create an object\n"), *Factory->GetClass()->GetName());
Package->ClearDirtyFlag(); Package->ClearDirtyFlag();
Package->MarkAsGarbage(); Package->MarkAsGarbage();
return nullptr; return nullptr;
@@ -129,4 +160,3 @@ UObject* UEnumFactoryWing::FactoryCreateNew(UClass* Class, UObject* InParent, FN
} }
return FEnumEditorUtils::CreateUserDefinedEnum(InParent, Name, Flags); return FEnumEditorUtils::CreateUserDefinedEnum(InParent, Name, Flags);
} }

View File

@@ -124,7 +124,7 @@ FWingParameterEditor::InfoMetaMap FWingParameterEditor::GetMaterialParameters(UM
bool FWingParameterEditor::ReportBadType(EMaterialParameterType Type, WingOut Errors) bool FWingParameterEditor::ReportBadType(EMaterialParameterType Type, WingOut Errors)
{ {
Errors.Printf(TEXT("Material parameter type #%d is not a valid type.\n")); Errors.Printf(TEXT("Material parameter type #%d is not a valid type.\n"), int(Type));
return false; return false;
} }
@@ -276,32 +276,21 @@ bool FWingParameterEditor::AddOverride(
bool FWingParameterEditor::RemoveOverride( bool FWingParameterEditor::RemoveOverride(
const Info &ID, UMaterialInstanceConstant *MI, WingOut Errors) const Info &ID, UMaterialInstanceConstant *MI, WingOut Errors)
{ {
// Remove the override from all parameter arrays. InfoMetaMap AllParams = GetMaterialParameters(MI);
auto RemoveFrom = [&](auto& Arr) { Metadata *Target = FindParameter(AllParams, ID, Errors);
return Arr.RemoveAll([&](auto& Entry) { return Entry.ParameterInfo == ID; }); if (Target == nullptr) return false;
}; if (!Target->bOverride)
int32 Removed = 0;
switch (ID.Type)
{ {
case EMaterialParameterType::Scalar: Removed += RemoveFrom(MI->ScalarParameterValues); break; Errors.Printf(TEXT("Parameter '%s' is inherited and not overridden"), *StringID(ID));
case EMaterialParameterType::Vector: Removed += RemoveFrom(MI->VectorParameterValues); break; return false;
case EMaterialParameterType::DoubleVector: Removed += RemoveFrom(MI->DoubleVectorParameterValues); break;
case EMaterialParameterType::Texture: Removed += RemoveFrom(MI->TextureParameterValues); break;
case EMaterialParameterType::TextureCollection: Removed += RemoveFrom(MI->TextureCollectionParameterValues); break;
case EMaterialParameterType::Font: Removed += RemoveFrom(MI->FontParameterValues); break;
case EMaterialParameterType::RuntimeVirtualTexture: Removed += RemoveFrom(MI->RuntimeVirtualTextureParameterValues); break;
case EMaterialParameterType::SparseVolumeTexture: Removed += RemoveFrom(MI->SparseVolumeTextureParameterValues); break;
case EMaterialParameterType::StaticSwitch: Errors.Printf(TEXT("Removal of static switches not implemented")); return false;
case EMaterialParameterType::StaticComponentMask: Errors.Printf(TEXT("Removal of static component masks not implemented")); return false;
case EMaterialParameterType::Num: Errors.Printf(TEXT("Parameter Type is not valid")); return false;
case EMaterialParameterType::None: Errors.Printf(TEXT("Parameter Type is not valid")); return false;
} }
if (Removed == 0) FMaterialInstanceParameterUpdateContext Update(MI, EMaterialInstanceClearParameterFlag::All);
for (const auto &[Info, Meta] : AllParams)
{ {
Errors.Printf(TEXT("No override found for parameter '%s'"), *StringID(ID)); if (!Meta.bOverride) continue;
return false; if (Info == ID) continue;
Update.SetParameterValueEditorOnly(Info, Meta);
} }
return true; return true;
} }

View File

@@ -354,7 +354,6 @@ bool WingUtils::StringToEnum(UEnum* Enum, const FString& Str, int64& OutValue, W
return true; return true;
} }
// ============================================================ // ============================================================
// Common Error Reporting // Common Error Reporting
// ============================================================ // ============================================================
@@ -396,6 +395,16 @@ bool WingUtils::CheckCanRename(UEdGraphNode* Node, const FString &Name, WingOut
return true; return true;
} }
bool WingUtils::CheckNewObjectNotNull(UObject *Obj, const TCHAR *Kind, WingOut Errors)
{
if (Obj == nullptr)
{
Errors.Printf(TEXT("NewObject failed to create a %s"), Kind);
return false;
}
return true;
}
// ============================================================ // ============================================================
// Reparent validation // Reparent validation
// ============================================================ // ============================================================

View File

@@ -13,8 +13,12 @@ class WingFactories
public: public:
// Create an asset on disk, using a factory instance. // Create an asset on disk, using a factory instance.
// Returns the main object. If there are problems, // Returns the main object. If there are problems,
// prints error messages and returns nullptr. // prints error messages and returns nullptr. If
static UObject *CreateAsset(const FString &Path, UFactory *Factory, WingOut Errors); // CheckIs is nonnull, this does a final check to verify
// that the created asset is a child of the specified
// class.
static UObject *CreateAsset(
const FString &Path, UFactory *Factory, UClass *CheckIs, WingOut Errors);
// Some factories are blacklisted, mainly because they // Some factories are blacklisted, mainly because they
// pop up dialog boxes. In those cases, we deal with it // pop up dialog boxes. In those cases, we deal with it
@@ -22,9 +26,16 @@ public:
// and then implementing replacement factories. // and then implementing replacement factories.
static bool IsBlacklisted(TSubclassOf<UFactory> FactoryClass); static bool IsBlacklisted(TSubclassOf<UFactory> FactoryClass);
// Some factories require special-case attention. These
// factories are valid factories that can be used by handlers,
// and are not blacklisted. However, they should not be
// auto-registered by handlers that do bulk registration
// of large numbers of factories.
static bool IsSpecialCase(TSubclassOf<UFactory> FactoryClass);
// Check if the factory class can be used to create assets. // Check if the factory class can be used to create assets.
// makes sure it's not abstract, calls CanCreateNew, // makes sure it's not abstract, checks CanCreateNew,
// calls ShouldShowInNewMenu, and verifies not blacklisted. // checks ShouldShowInNewMenu, and verifies not blacklisted.
static bool CanCreate(TSubclassOf<UFactory> FactoryClass); static bool CanCreate(TSubclassOf<UFactory> FactoryClass);
// Get the names of the editable properties for a factory class. // Get the names of the editable properties for a factory class.

View File

@@ -270,10 +270,11 @@ public:
// ----- Reparent validation ----- // ----- Reparent validation -----
static bool CanReparentBlueprint(UClass* CurrentGenerated, UClass* Proposed); static bool CanReparentBlueprint(UClass* CurrentGenerated, UClass* Proposed);
// ----- Common Error Reporting ----- // ----- Common Error Reporting -----
static bool CheckExactlyOneNamed(int Count, const TCHAR *Kind, const FString &Name, WingOut Errors); static bool CheckExactlyOneNamed(int Count, const TCHAR *Kind, const FString &Name, WingOut Errors);
static bool CheckExactlyOneNamed(int Count, const TCHAR *Kind, FName Name, WingOut Errors); static bool CheckExactlyOneNamed(int Count, const TCHAR *Kind, FName Name, WingOut Errors);
static bool CheckCanRename(UEdGraphNode* Node, const FString &Name, WingOut Errors); static bool CheckCanRename(UEdGraphNode* Node, const FString &Name, WingOut Errors);
static bool CheckNewObjectNotNull(UObject *Obj, const TCHAR *Kind, WingOut Errors);
}; };