View Issue Details

IDProjectCategoryView StatusLast Update
0004874NoesisGUIStudiopublic2026-03-23 17:20
ReporterJinFox Assigned Tosfernandez  
PrioritynormalSeverityminor 
Status feedbackResolutionopen 
Product VersionStudio_Beta 
Target VersionStudio_Beta 
Summary0004874: [Unreal] Material only appear in Studio when already manually used in XAML
Description

Hello,
Following our discussion of the issue on the forum here:
https://www.noesisengine.com/forums/viewtopic.php?t=3842

I am creating this ticket to track the issue.
Basically, the issue goes as follow:

  • Create a new material in unreal.
  • It doesn't show up in the dropdown in Noesis studio.
  • if you add it manually by adding a xaml, now it shows up properly in the dropdown and can be edited.

Thanks!

PlatformAny

Activities

sfernandez

sfernandez

2026-03-09 19:28

manager   ~0012011

The following patch should fix the issue, could you try it?

MaterialRegistration.patch (6,710 bytes)   
Index: Private/NoesisRuntimeModule.cpp
===================================================================
--- Private/NoesisRuntimeModule.cpp	(revision 16791)
+++ Private/NoesisRuntimeModule.cpp	(working copy)
@@ -308,6 +308,16 @@
 	NoesisDestroyTypeClassForBlueprint(Blueprint);
 }
 
+void OnAssetsAdded(TConstArrayView<FAssetData> Assets)
+{
+	NoesisRegisterMaterials(Assets);
+}
+
+void OnAssetsRemoved(TConstArrayView<FAssetData> Assets)
+{
+	NoesisUnregisterMaterials(Assets);
+}
+
 void OnAssetRenamed(const FAssetData& AssetData, const FString& OldPath)
 {
 	UObject* NewObject = AssetData.GetAsset();
@@ -973,6 +983,8 @@
 
 			FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>("AssetRegistry");
 			IAssetRegistry& AssetRegistry = AssetRegistryModule.Get();
+			AssetsAddedHandle = AssetRegistry.OnAssetsAdded().AddStatic(&OnAssetsAdded);
+			AssetsRemovedHandle = AssetRegistry.OnAssetsRemoved().AddStatic(&OnAssetsRemoved);
 			AssetRenamedHandle = AssetRegistry.OnAssetRenamed().AddStatic(&OnAssetRenamed);
 		}
 #endif
@@ -1009,6 +1021,8 @@
 		{
 			FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>("AssetRegistry");
 			IAssetRegistry& AssetRegistry = AssetRegistryModule.Get();
+			AssetRegistry.OnAssetsAdded().Remove(AssetsAddedHandle);
+			AssetRegistry.OnAssetsRemoved().Remove(AssetsRemovedHandle);
 			AssetRegistry.OnAssetRenamed().Remove(AssetRenamedHandle);
 
 			FEnumEditorUtils::FEnumEditorManager::Get().RemoveListener(NotifyEnumChangedListener);
@@ -1115,6 +1129,8 @@
 	FDelegateHandle BlueprintPreCompileHandle;
 	NotifyEnumChanged* NotifyEnumChangedListener;
 	NotifyStructChanged* NotifyStructChangedListener;
+	FDelegateHandle AssetsAddedHandle;
+	FDelegateHandle AssetsRemovedHandle;
 	FDelegateHandle AssetRenamedHandle;
 #endif
 	TSharedPtr<class IInputProcessor> InputPreProcessor;
Index: Private/NoesisTypeClass.cpp
===================================================================
--- Private/NoesisTypeClass.cpp	(revision 16791)
+++ Private/NoesisTypeClass.cpp	(working copy)
@@ -68,6 +68,7 @@
 #include "NoesisSDK.h"
 
 #ifdef WITH_EDITOR
+    #include "Interfaces/IPluginManager.h"
     #include "NoesisSettings.h"
 #endif
 
@@ -3804,6 +3805,109 @@
 	OnMaterialChanged(Material);
 }
 
+static void GetProjectPaths(TArray<FString>& ProjectPaths)
+{
+	ProjectPaths.Add(TEXT("/Game"));
+
+	TArray<TSharedRef<IPlugin>> EnabledPlugins = IPluginManager::Get().GetEnabledPlugins();
+	for (const TSharedRef<IPlugin>& Plugin : EnabledPlugins)
+	{
+		if (Plugin->GetLoadedFrom() == EPluginLoadedFrom::Project)
+		{
+			ProjectPaths.Add(*Plugin->GetMountedAssetPath().LeftChop(1));
+		}
+	}
+}
+
+static bool IsProjectAsset(const FAssetData& AssetData, const TArray<FString>& ProjectPaths)
+{
+	bool Found = false;
+
+	FString Path = AssetData.PackagePath.ToString();
+	for (const FString& ProjectPath : ProjectPaths)
+	{
+		if (Path.StartsWith(ProjectPath))
+		{
+			Found = true;
+			break;
+		}
+	}
+
+	return Found;
+}
+
+void NoesisRegisterMaterials(TConstArrayView<FAssetData> Assets)
+{
+	Noesis::Studio::ClearTypeCache();
+
+	TArray<FString> ProjectPaths;
+	GetProjectPaths(ProjectPaths);
+
+	for (const FAssetData& AssetData : Assets)
+	{
+		if (AssetData.IsInstanceOf(UMaterialInterface::StaticClass()) &&
+			IsProjectAsset(AssetData, ProjectPaths))
+		{
+			if (UMaterialInterface* Material = Cast<UMaterialInterface>(AssetData.GetAsset()))
+			{
+				if (UMaterial* BaseMaterial = Material->GetBaseMaterial())
+				{
+					if (BaseMaterial->MaterialDomain == MD_UI ||
+						BaseMaterial->MaterialDomain == MD_PostProcess)
+					{
+						NoesisTypeClass** TypeClassPtr = MaterialMap.Find(Material);
+						if (TypeClassPtr == nullptr)
+						{
+							Noesis::TypeClass* Type = NoesisCreateTypeClassForUMaterial(Material);
+							Noesis::Reflection::RegisterType(Type);
+						}
+					}
+				}
+			}
+		}
+	}
+
+	Noesis::Studio::PopulateTypeCache();
+}
+
+void NoesisUnregisterMaterials(TConstArrayView<FAssetData> Assets)
+{
+	Noesis::Studio::ClearTypeCache();
+
+	TArray<FString> ProjectPaths;
+	GetProjectPaths(ProjectPaths);
+
+	for (const FAssetData& AssetData : Assets)
+	{
+		if (AssetData.IsInstanceOf(UMaterialInterface::StaticClass()) &&
+			IsProjectAsset(AssetData, ProjectPaths))
+		{
+			if (UMaterialInterface* Material = Cast<UMaterialInterface>(AssetData.GetAsset()))
+			{
+				if (UMaterial* BaseMaterial = Material->GetBaseMaterial())
+				{
+					if (BaseMaterial->MaterialDomain == MD_UI ||
+						BaseMaterial->MaterialDomain == MD_PostProcess)
+					{
+						NoesisTypeClass** TypeClassPtr = MaterialMap.Find(Material);
+						if (TypeClassPtr != nullptr)
+						{
+							NoesisTypeClass* TypeClass = *TypeClassPtr;
+							bool IsRegistered = IsTypeRegistered(TypeClass);
+							if (IsRegistered)
+							{
+								Noesis::Reflection::UnregisterNoDelete(TypeClass);
+								MaterialMap.Remove(Material);
+							}
+						}
+					}
+				}
+			}
+		}
+	}
+
+	Noesis::Studio::PopulateTypeCache();
+}
 void NoesisAssetRenamed(UObject* Object, FString OldPath)
 {
 	Noesis::Studio::ClearTypeCache();
@@ -3865,6 +3969,22 @@
 			}
 		}
 	}
+	else if (UMaterialInterface* Material = Cast<UMaterialInterface>(Object))
+	{
+		NoesisTypeClass** TypeClassPtr = MaterialMap.Find(Material);
+		if (TypeClassPtr != nullptr)
+		{
+			NoesisTypeClass* TypeClass = *TypeClassPtr;
+			bool IsRegistered = IsTypeRegistered(TypeClass);
+			if (IsRegistered)
+			{
+				Noesis::Reflection::UnregisterNoDelete(TypeClass);
+				MaterialMap.Remove(Material);
+			}
+			Noesis::TypeClass* Type = NoesisCreateTypeClassForUMaterial(Material);
+			Noesis::Reflection::RegisterType(Type);
+		}
+	}
 	Noesis::Studio::PopulateTypeCache();
 }
 #endif
Index: Public/NoesisTypeClass.h
===================================================================
--- Public/NoesisTypeClass.h	(revision 16791)
+++ Public/NoesisTypeClass.h	(working copy)
@@ -135,6 +135,8 @@
 NOESISRUNTIME_API void NoesisDestroyTypeClassForStruct(class UUserDefinedStruct*);
 NOESISRUNTIME_API void NoesisDestroyTypeClassForEnum(class UEnum*);
 NOESISRUNTIME_API void NoesisDestroyTypeClassForMaterial(class UMaterialInterface*);
+NOESISRUNTIME_API void NoesisRegisterMaterials(TConstArrayView<FAssetData> Assets);
+NOESISRUNTIME_API void NoesisUnregisterMaterials(TConstArrayView<FAssetData> Assets);
 NOESISRUNTIME_API void NoesisAssetRenamed(UObject* Object, FString OldPath);
 #endif
 
MaterialRegistration.patch (6,710 bytes)   
JinFox

JinFox

2026-03-18 16:43

reporter   ~0012060

Hi Sergio, Sorry I wasn't able to come back to you sooner. I was only able to test quickly today and it seems that if I create a new Material (by duplicating an existing one, for example) it does load indefinitely. I wasn't able to look further today though so I will try again tomorrow hopefully.

sfernandez

sfernandez

2026-03-20 10:12

manager   ~0012074

Last edited: 2026-03-20 10:12

it does load indefinitely

What do you mean? does Unreal get stuck/frozen or something similar?

JinFox

JinFox

2026-03-23 15:19

reporter   ~0012089

Hello Sergio,

I just tested again applying the patch on beta 12 and the issue didn't occur (duplicating an existing material or deleting a material does feel a bit sluggish but it does work after a couple second of editor freeze).

I just saw that however the following include was missing from the patch in NoesisTypeClass.cpp
#include "AssetRegistry/AssetData.h"

sfernandez

sfernandez

2026-03-23 17:19

manager   ~0012090

Last edited: 2026-03-23 17:20

I commited the changes in r16873.

duplicating or deleting a material does feel a bit sluggish

It could be related to Studio recalculating the internal cache of known objects (interactivity behaviors, triggers and actions, effects, brush shaders...). Are you using a debug version of our Noesis.dll? because it shouldn't take that much, unless you have tons of custom classes.
Just to confirm this... could you comment the lines in the patch about Noesis::Studio::PopulateTypeCache();, Studio won't work as expected but I just want to see if this gets rid of the 1-2 secs of freezing.

Issue History

Date Modified Username Field Change
2026-03-09 13:18 JinFox New Issue
2026-03-09 17:04 sfernandez Assigned To => sfernandez
2026-03-09 17:04 sfernandez Status new => assigned
2026-03-09 17:04 sfernandez Target Version => Studio_Beta
2026-03-09 19:28 sfernandez Note Added: 0012011
2026-03-09 19:28 sfernandez File Added: MaterialRegistration.patch
2026-03-09 19:28 sfernandez Status assigned => feedback
2026-03-18 16:43 JinFox Note Added: 0012060
2026-03-18 16:43 JinFox Status feedback => assigned
2026-03-20 10:12 sfernandez Status assigned => feedback
2026-03-20 10:12 sfernandez Note Added: 0012074
2026-03-20 10:12 sfernandez Note Edited: 0012074
2026-03-20 10:12 sfernandez Note Edited: 0012074
2026-03-23 15:19 JinFox Note Added: 0012089
2026-03-23 15:19 JinFox Status feedback => assigned
2026-03-23 17:19 sfernandez Status assigned => feedback
2026-03-23 17:19 sfernandez Note Added: 0012090
2026-03-23 17:20 sfernandez Note Edited: 0012090
2026-03-23 17:20 sfernandez Note Edited: 0012090