2026-03-08 22:17:14 -04:00
# pragma once
# include "CoreMinimal.h"
# include "MCPHandler.h"
2026-03-13 14:26:04 -04:00
# include "MCPAssets.h"
2026-03-08 22:17:14 -04:00
# include "MCPUtils.h"
# include "Materials/Material.h"
# include "Materials/MaterialFunction.h"
# include "Materials/MaterialExpression.h"
# include "MaterialGraph/MaterialGraph.h"
# include "MaterialGraph/MaterialGraphNode.h"
# include "UObject/UObjectIterator.h"
# include "UMCPHandler_AddMaterialExpression.generated.h"
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
2026-03-11 22:03:32 -04:00
UCLASS ( meta = ( Group = " Unclassified " ) )
2026-03-08 22:17:14 -04:00
class UMCPHandler_AddMaterialExpression : public UObject , public IMCPHandler
{
GENERATED_BODY ( )
public :
UPROPERTY ( meta = ( Optional , Description = " Material name or package path (specify this or materialFunction, not both) " ) )
FString Material ;
UPROPERTY ( meta = ( Optional , Description = " Material function name or package path (specify this or material, not both) " ) )
FString MaterialFunction ;
UPROPERTY ( meta = ( Description = " Expression class name without 'MaterialExpression' prefix (e.g. 'Constant', 'ScalarParameter', 'Add', 'Multiply', 'Lerp') " ) )
FString ExpressionClass ;
UPROPERTY ( meta = ( Optional , Description = " X position in the material graph editor " ) )
int32 PosX = 0 ;
UPROPERTY ( meta = ( Optional , Description = " Y position in the material graph editor " ) )
int32 PosY = 0 ;
virtual FString GetDescription ( ) const override
{
return TEXT ( " Add a new expression node to a material or material function graph. " ) ;
}
2026-03-10 07:17:42 -04:00
virtual void Handle ( const FJsonObject * Json , FStringBuilderBase & Result ) override
2026-03-08 22:17:14 -04:00
{
if ( Material . IsEmpty ( ) & & MaterialFunction . IsEmpty ( ) )
{
2026-03-10 07:17:42 -04:00
Result . Append ( TEXT ( " ERROR: Specify 'material' or 'materialFunction' \n " ) ) ;
return ;
2026-03-08 22:17:14 -04:00
}
2026-03-10 07:17:42 -04:00
if ( ! Material . IsEmpty ( ) & & ! MaterialFunction . IsEmpty ( ) )
2026-03-08 22:17:14 -04:00
{
2026-03-10 07:17:42 -04:00
Result . Append ( TEXT ( " ERROR: Specify 'material' or 'materialFunction', not both \n " ) ) ;
return ;
2026-03-08 22:17:14 -04:00
}
2026-03-10 07:17:42 -04:00
// Resolve the expression class
UClass * ExprClass = ResolveExpressionClass ( Result ) ;
if ( ! ExprClass ) return ;
2026-03-08 22:17:14 -04:00
// Load material or material function
UMaterial * MaterialObj = nullptr ;
UMaterialFunction * MatFunc = nullptr ;
UObject * Owner = nullptr ;
if ( ! MaterialFunction . IsEmpty ( ) )
{
2026-03-10 07:17:42 -04:00
MCPAssets < UMaterialFunction > Assets ;
if ( ! Assets . Exact ( MaterialFunction ) . Errors ( Result ) . ENone ( ) . ETwo ( ) . Load ( ) ) return ;
MatFunc = Assets . Object ( ) ;
2026-03-08 22:17:14 -04:00
Owner = MatFunc ;
}
else
{
2026-03-10 07:17:42 -04:00
MCPAssets < UMaterial > Assets ;
if ( ! Assets . Exact ( Material ) . Errors ( Result ) . ENone ( ) . ETwo ( ) . Load ( ) ) return ;
MaterialObj = Assets . Object ( ) ;
2026-03-08 22:17:14 -04:00
Owner = MaterialObj ;
}
// Ensure the MaterialGraph exists (commandlet mode doesn't auto-create it)
if ( MaterialObj ) MCPUtils : : EnsureMaterialGraph ( MaterialObj ) ;
2026-03-10 07:17:42 -04:00
// Create the expression
UMaterialExpression * NewExpr = NewObject < UMaterialExpression > ( Owner , ExprClass ) ;
2026-03-08 22:17:14 -04:00
if ( ! NewExpr )
{
2026-03-10 07:17:42 -04:00
Result . Append ( TEXT ( " ERROR: Failed to create material expression object \n " ) ) ;
return ;
2026-03-08 22:17:14 -04:00
}
NewExpr - > MaterialExpressionEditorX = PosX ;
NewExpr - > MaterialExpressionEditorY = PosY ;
2026-03-10 07:17:42 -04:00
2026-03-08 22:17:14 -04:00
if ( MaterialObj )
{
MaterialObj - > GetExpressionCollection ( ) . AddExpression ( NewExpr ) ;
if ( MaterialObj - > MaterialGraph )
MaterialObj - > MaterialGraph - > RebuildGraph ( ) ;
MaterialObj - > PreEditChange ( nullptr ) ;
MaterialObj - > PostEditChange ( ) ;
MaterialObj - > MarkPackageDirty ( ) ;
}
2026-03-10 07:17:42 -04:00
else
2026-03-08 22:17:14 -04:00
{
MatFunc - > GetExpressionCollection ( ) . AddExpression ( NewExpr ) ;
MatFunc - > PreEditChange ( nullptr ) ;
MatFunc - > PostEditChange ( ) ;
MatFunc - > MarkPackageDirty ( ) ;
}
// Save
2026-03-10 07:17:42 -04:00
bool bSaved = MaterialObj
? MCPUtils : : SaveMaterialPackage ( MaterialObj )
: MCPUtils : : SaveGenericPackage ( MatFunc ) ;
// Output
Result . Appendf ( TEXT ( " Added %s \n " ) , * MCPUtils : : FormatName ( NewExpr ) ) ;
2026-03-08 22:17:14 -04:00
2026-03-10 07:17:42 -04:00
// Find the graph node GUID (only for materials with a material graph)
2026-03-08 22:17:14 -04:00
if ( MaterialObj & & MaterialObj - > MaterialGraph )
{
for ( UEdGraphNode * Node : MaterialObj - > MaterialGraph - > Nodes )
{
UMaterialGraphNode * MatNode = Cast < UMaterialGraphNode > ( Node ) ;
if ( MatNode & & MatNode - > MaterialExpression = = NewExpr )
{
2026-03-10 07:17:42 -04:00
Result . Appendf ( TEXT ( " NodeId: %s \n " ) , * Node - > NodeGuid . ToString ( ) ) ;
2026-03-08 22:17:14 -04:00
break ;
}
}
}
2026-03-10 07:17:42 -04:00
if ( ! bSaved )
Result . Append ( TEXT ( " WARNING: Failed to save package \n " ) ) ;
}
private :
UClass * ResolveExpressionClass ( FStringBuilderBase & Result )
{
// Convenience aliases
static TMap < FString , FString > Aliases = {
{ TEXT ( " Lerp " ) , TEXT ( " LinearInterpolate " ) } ,
} ;
2026-03-08 22:17:14 -04:00
2026-03-10 07:17:42 -04:00
FString LookupName = ExpressionClass ;
if ( const FString * Alias = Aliases . Find ( ExpressionClass ) )
LookupName = * Alias ;
2026-03-08 22:17:14 -04:00
2026-03-10 07:17:42 -04:00
FString FullClassName = FString : : Printf ( TEXT ( " MaterialExpression%s " ) , * LookupName ) ;
for ( TObjectIterator < UClass > It ; It ; + + It )
2026-03-08 22:17:14 -04:00
{
2026-03-10 07:17:42 -04:00
if ( It - > GetName ( ) = = FullClassName & & It - > IsChildOf ( UMaterialExpression : : StaticClass ( ) ) )
{
if ( It - > HasAnyClassFlags ( CLASS_Abstract ) )
{
Result . Appendf ( TEXT ( " ERROR: Expression class '%s' is abstract \n " ) , * ExpressionClass ) ;
return nullptr ;
}
return * It ;
}
2026-03-08 22:17:14 -04:00
}
2026-03-10 07:17:42 -04:00
Result . Appendf ( TEXT ( " ERROR: Unknown expression class '%s'. Use the UMaterialExpression subclass name without the 'MaterialExpression' prefix (e.g. 'Constant', 'ScalarParameter', 'Add', 'Multiply', 'Lerp') \n " ) ,
* ExpressionClass ) ;
return nullptr ;
2026-03-08 22:17:14 -04:00
}
} ;