From 476b0057f5e6392932402641d411bd9197e0e21f Mon Sep 17 00:00:00 2001 From: jyelon Date: Tue, 7 Apr 2026 17:43:54 -0400 Subject: [PATCH] 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. --- .../UEWingman/Handlers/Create_ClassArg.h | 5 +- .../Handlers/Create_MaterialInstance.h | 63 +++++++++++++++++++ .../Source/UEWingman/Handlers/Create_NoArgs.h | 4 +- .../UEWingman/Private/WingFactories.cpp | 38 +++++++++-- .../UEWingman/Private/WingParameterEditor.cpp | 35 ++++------- .../Source/UEWingman/Private/WingUtils.cpp | 11 +++- .../Source/UEWingman/Public/WingFactories.h | 19 ++++-- .../Source/UEWingman/Public/WingUtils.h | 3 +- 8 files changed, 142 insertions(+), 36 deletions(-) create mode 100644 Plugins/UEWingman/Source/UEWingman/Handlers/Create_MaterialInstance.h diff --git a/Plugins/UEWingman/Source/UEWingman/Handlers/Create_ClassArg.h b/Plugins/UEWingman/Source/UEWingman/Handlers/Create_ClassArg.h index d6f97bd2..da03678c 100644 --- a/Plugins/UEWingman/Source/UEWingman/Handlers/Create_ClassArg.h +++ b/Plugins/UEWingman/Source/UEWingman/Handlers/Create_ClassArg.h @@ -296,8 +296,9 @@ public: if (!Props[0].SetObject(ClassObj, WingOut::Stdout)) return; // Create the asset using the factory. - UObject *Blueprint = WingFactories::CreateAsset(Path, Factory, WingOut::Stdout); - if (Blueprint == nullptr) return; + UObject *Object = WingFactories::CreateAsset( + Path, Factory, nullptr, WingOut::Stdout); + if (Object == nullptr) return; WingOut::Stdout.Printf(TEXT("Created.\n")); } }; diff --git a/Plugins/UEWingman/Source/UEWingman/Handlers/Create_MaterialInstance.h b/Plugins/UEWingman/Source/UEWingman/Handlers/Create_MaterialInstance.h new file mode 100644 index 00000000..379e6a57 --- /dev/null +++ b/Plugins/UEWingman/Source/UEWingman/Handlers/Create_MaterialInstance.h @@ -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(); + if (!ParentObj) return; + + // Create a factory, then use it to create an asset. + UMaterialInstanceConstantFactoryNew* Factory = + NewObject(); + 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(MIO); + MI->Parent = ParentObj; + + WingOut::Stdout.Printf(TEXT("Created\n")); + } +}; diff --git a/Plugins/UEWingman/Source/UEWingman/Handlers/Create_NoArgs.h b/Plugins/UEWingman/Source/UEWingman/Handlers/Create_NoArgs.h index b831f9b0..8ce3d747 100644 --- a/Plugins/UEWingman/Source/UEWingman/Handlers/Create_NoArgs.h +++ b/Plugins/UEWingman/Source/UEWingman/Handlers/Create_NoArgs.h @@ -24,6 +24,7 @@ public: for (UClass* Class : FactoryClasses) { if (!WingFactories::CanCreate(Class)) continue; + if (WingFactories::IsSpecialCase(Class)) continue; if (WingFactories::GetParameterNames(Class).Num() > 0) continue; FString FactoryName = WingFactories::DeriveFactoryName(Class); @@ -49,7 +50,8 @@ public: { UClass* FactoryClass = Configuration->FactoryClass.Get(); UFactory* Factory = NewObject(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")); } }; diff --git a/Plugins/UEWingman/Source/UEWingman/Private/WingFactories.cpp b/Plugins/UEWingman/Source/UEWingman/Private/WingFactories.cpp index 49278031..8eb43802 100644 --- a/Plugins/UEWingman/Source/UEWingman/Private/WingFactories.cpp +++ b/Plugins/UEWingman/Source/UEWingman/Private/WingFactories.cpp @@ -4,7 +4,9 @@ #include "PackageTools.h" #include "WingProperty.h" #include "AssetRegistry/AssetRegistryModule.h" +#include "Editor.h" #include "Kismet2/EnumEditorUtils.h" +#include "Subsystems/AssetEditorSubsystem.h" bool WingFactories::CheckNewAssetPath(const FString& Path, WingOut Errors) { @@ -39,6 +41,14 @@ bool WingFactories::IsBlacklisted(TSubclassOf FactoryClass) return false; } +bool WingFactories::IsSpecialCase(TSubclassOf FactoryClass) +{ + if (FactoryClass == nullptr) return false; + FName Name = FactoryClass->GetFName(); + if (Name == TEXT("MaterialInstanceConstantFactoryNew")) return true; + return false; +} + bool WingFactories::CanCreate(TSubclassOf FactoryClass) { if (FactoryClass == nullptr) return false; @@ -53,7 +63,8 @@ TArray WingFactories::GetParameterNames(TSubclassOf FactoryClas 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. if (IsBlacklisted(Factory->GetClass())) @@ -84,9 +95,28 @@ UObject* WingFactories::CreateAsset(const FString& Path, UFactory* Factory, Wing 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 // 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) { Package->ClearFlags(RF_Transient); @@ -94,10 +124,11 @@ UObject* WingFactories::CreateAsset(const FString& Path, UFactory* Factory, Wing Package->MarkPackageDirty(); FAssetRegistryModule::AssetCreated(NewAsset); UWingServer::AddTouchedObject(NewAsset); + UAssetEditorSubsystem* Sub = GEditor->GetEditorSubsystem(); + if (Sub) Sub->OpenEditorForAsset(NewAsset); } else { - Errors.Printf(TEXT("ERROR: Factory '%s' failed to create an object\n"), *Factory->GetClass()->GetName()); Package->ClearDirtyFlag(); Package->MarkAsGarbage(); return nullptr; @@ -129,4 +160,3 @@ UObject* UEnumFactoryWing::FactoryCreateNew(UClass* Class, UObject* InParent, FN } return FEnumEditorUtils::CreateUserDefinedEnum(InParent, Name, Flags); } - diff --git a/Plugins/UEWingman/Source/UEWingman/Private/WingParameterEditor.cpp b/Plugins/UEWingman/Source/UEWingman/Private/WingParameterEditor.cpp index b1c8b2ba..ebefdcf7 100644 --- a/Plugins/UEWingman/Source/UEWingman/Private/WingParameterEditor.cpp +++ b/Plugins/UEWingman/Source/UEWingman/Private/WingParameterEditor.cpp @@ -124,7 +124,7 @@ FWingParameterEditor::InfoMetaMap FWingParameterEditor::GetMaterialParameters(UM 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; } @@ -276,32 +276,21 @@ bool FWingParameterEditor::AddOverride( bool FWingParameterEditor::RemoveOverride( const Info &ID, UMaterialInstanceConstant *MI, WingOut Errors) { - // Remove the override from all parameter arrays. - auto RemoveFrom = [&](auto& Arr) { - return Arr.RemoveAll([&](auto& Entry) { return Entry.ParameterInfo == ID; }); - }; - - int32 Removed = 0; - switch (ID.Type) + InfoMetaMap AllParams = GetMaterialParameters(MI); + Metadata *Target = FindParameter(AllParams, ID, Errors); + if (Target == nullptr) return false; + if (!Target->bOverride) { - case EMaterialParameterType::Scalar: Removed += RemoveFrom(MI->ScalarParameterValues); break; - case EMaterialParameterType::Vector: Removed += RemoveFrom(MI->VectorParameterValues); break; - 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; + Errors.Printf(TEXT("Parameter '%s' is inherited and not overridden"), *StringID(ID)); + 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)); - return false; + if (!Meta.bOverride) continue; + if (Info == ID) continue; + Update.SetParameterValueEditorOnly(Info, Meta); } return true; } diff --git a/Plugins/UEWingman/Source/UEWingman/Private/WingUtils.cpp b/Plugins/UEWingman/Source/UEWingman/Private/WingUtils.cpp index 068ef773..5a3d41d6 100644 --- a/Plugins/UEWingman/Source/UEWingman/Private/WingUtils.cpp +++ b/Plugins/UEWingman/Source/UEWingman/Private/WingUtils.cpp @@ -354,7 +354,6 @@ bool WingUtils::StringToEnum(UEnum* Enum, const FString& Str, int64& OutValue, W return true; } - // ============================================================ // Common Error Reporting // ============================================================ @@ -396,6 +395,16 @@ bool WingUtils::CheckCanRename(UEdGraphNode* Node, const FString &Name, WingOut 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 // ============================================================ diff --git a/Plugins/UEWingman/Source/UEWingman/Public/WingFactories.h b/Plugins/UEWingman/Source/UEWingman/Public/WingFactories.h index a89f9ea9..bd6f2a87 100644 --- a/Plugins/UEWingman/Source/UEWingman/Public/WingFactories.h +++ b/Plugins/UEWingman/Source/UEWingman/Public/WingFactories.h @@ -13,8 +13,12 @@ class WingFactories public: // Create an asset on disk, using a factory instance. // Returns the main object. If there are problems, - // prints error messages and returns nullptr. - static UObject *CreateAsset(const FString &Path, UFactory *Factory, WingOut Errors); + // prints error messages and returns nullptr. If + // 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 // pop up dialog boxes. In those cases, we deal with it @@ -22,9 +26,16 @@ public: // and then implementing replacement factories. static bool IsBlacklisted(TSubclassOf 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 FactoryClass); + // Check if the factory class can be used to create assets. - // makes sure it's not abstract, calls CanCreateNew, - // calls ShouldShowInNewMenu, and verifies not blacklisted. + // makes sure it's not abstract, checks CanCreateNew, + // checks ShouldShowInNewMenu, and verifies not blacklisted. static bool CanCreate(TSubclassOf FactoryClass); // Get the names of the editable properties for a factory class. diff --git a/Plugins/UEWingman/Source/UEWingman/Public/WingUtils.h b/Plugins/UEWingman/Source/UEWingman/Public/WingUtils.h index 143d2626..20d19ba3 100644 --- a/Plugins/UEWingman/Source/UEWingman/Public/WingUtils.h +++ b/Plugins/UEWingman/Source/UEWingman/Public/WingUtils.h @@ -270,10 +270,11 @@ public: // ----- Reparent validation ----- static bool CanReparentBlueprint(UClass* CurrentGenerated, UClass* Proposed); - + // ----- Common Error Reporting ----- 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 CheckCanRename(UEdGraphNode* Node, const FString &Name, WingOut Errors); + static bool CheckNewObjectNotNull(UObject *Obj, const TCHAR *Kind, WingOut Errors); };