Download

Material expression graph traversal in python

I’m procedurally generating material graphs in python which works ok, however I want to update changes made to the material. What I can’t figure out is which output pin of a MaterialExpression connects to the next Expression. I would really appreciate any help.

Simple Example:

def testGraphTraversalBackwardTrace(mat, prop = unreal.MaterialProperty.MP_EMISSIVE_COLOR):
    output_node = unreal.MaterialEditingLibrary.get_material_property_input_node(mat, prop)
    assert output_node != None, "No Output in selected property "+ str(prop) +" in material "+mat.get_name()

    inputs = unreal.MaterialEditingLibrary.get_inputs_for_material_expression(mat, output_node)
    if len(inputs) == 0:
        print("Found input node")
    else:
        for i, node in enumerate(inputs):
            if node == None:
                print("input not connected")
            else:
                print("Node " +node.get_name() +" connected to pin"+str(i) + " of node " + output_node.get_name())
                # WHICH output pin of node is connected?               
            

material_path = "/Game/MatGraph/TestMat"
if unreal.EditorAssetLibrary.does_asset_exist(material_path):
    matasset = unreal.EditorAssetLibrary.find_asset_data(material_path).get_asset()
    testGraphTraversalBackwardTrace(matasset)
LogPython: Node MaterialExpressionVectorParameter_0 connected to pin0 of node MaterialExpressionMultiply_0
LogPython: Node MaterialExpressionVectorParameter_1 connected to pin1 of node MaterialExpressionMultiply_0

I’m not sure this will help, but the “output node” in the material is basically the whole material.

Change to use material attributes in the settings, and it will be represented as one pin.

You then have to hook up noodles into a make material attributes node.

Meaning that finding individual inputs of a material the way you are currently going about is not a good idea.

Simulate or copy the code off of a BreakMaterialAttributes node.
That way no matter how materials are set up the code for it will always work and allow you to get the results of individual pins.

And since I have never had a look at it before, its totally possible that break material attributes uses similar code to what you already used above.

1 Like

Thank you for your reply. I can actually iterate over all MaterialProperty elements and find out if there is something connected. The problem is that I don’t know the output pins of MaterialExpressions, e.g. the VectorParameter Param_1 could output R or B and I don’t know which.

If it’s a material expression there’s kind of no way to tell is there?
It could be a function that’s 5000 nodes long or 1 node. So solving it backwards recursively while possible is a nightmare.

What about just testing R G and B separately for values?
Usually when making materials the editor puts 0 in pins that aren’t connected.

So in the event that only one channel is used for Diffuse, the reading of R G and B would contain different parameters ?

Backtracing is simply convenient in my case since I’m not interested in nodes that float around without doing anything. I could also just get a flat list of MaterialExpressions like this:

filter = ( unreal.MaterialExpression )
it = unreal.ObjectIterator()
for x in it:
    if x.get_outer() == material:
        if isinstance(x, filter):
            #append to list of expressions in material

I can’t find any method to access the output and so can’t check their value. There is only the default value (in many MaterialExpressions not even that).

if type(node) == unreal.MaterialExpressionVectorParameter:
    print_properties(node)
    print(node.get_editor_property("default_value"))
LogPython: Warning: ----------<class 'MaterialExpressionVectorParameter'>-----------------
LogPython: Warning: Property: channel_names                  type: ParameterChannelNames
LogPython: Warning: Property: default_value                  type: LinearColor
LogPython: Warning: Property: desc                           type: str
LogPython: Warning: Property: group                          type: Name
LogPython: Warning: Property: parameter_name                 type: Name
LogPython: Warning: Property: primitive_data_index           type: uint8
LogPython: Warning: Property: sort_priority                  type: int32
LogPython: Warning: Property: use_custom_primitive_data      type: bool
LogPython: <Struct 'LinearColor' (0x00000A035FA44768) {r: 0.000000, g: 0.000000, b: 0.000000, a: 0.000000}

isn’t that just it?
There’s got to be another one for non linear though. but it’s always a struct.

I would suggest having a look at the code that powers the MaskComponent - since what it does is break up an input into R/G/B/A ?

That is the actual data value and doesn’t change depending on what is connected. I took a look at the c++ sources of componentmask and others. There seems to be a lot of different ways to do this, the most frustrating is MaterialExpression->GetOutputs() which is exactly what I want but simply am unable to execute in python. Thank you for your help.

1 Like