5
$\begingroup$

For example the Mixed Shader node has 3 inputs, with names "Fac", "Shader" and "Shader".

Note that 2 of the inputs have the same name; but the first has an "identifier" of "Shader" and the second is "Shader_001".

How can I select the second input (without using it's numerical index).

So this works to select the first input:

material = bpy.data.objects["Cube"].active_material
material.node_tree.nodes[0].inputs.get("Shader").name

But this does not work to select the second input:

material = bpy.data.objects["Cube"].active_material
material.node_tree.nodes[0].inputs.get("Shader_001").name

This works, but I DO NOT want to do this as I may not always know the correct index number:

material = bpy.data.objects["Cube"].active_material
material.node_tree.nodes[0].inputs[2].name

Note that I've added a work around by defining by own function below; but this feels like it must already existing within the blend libraries?

def getByIdentifier(inputs, id):
    for x in inputs:
        if x.identifier == id:
            return x


material = bpy.data.objects["Cube"].active_material
getByIdentifier(material.node_tree.nodes[0].inputs, "Shader_001")
```
$\endgroup$
2
  • 2
    $\begingroup$ Very interesting question. I feel like it's a pretty nice workaround for the time being. By the way, if you're open to constructive critisicism, according to python guidelines, you should write method names in snake_case (pep 8) and you should not overwrite the builtin id (could lead to unexpected results if testing for equality) $\endgroup$
    – Gorgious
    Jul 6, 2021 at 18:16
  • 3
    $\begingroup$ getByIdentifier(inputs, "Shader_001") can be replaced with a oneliner next((i for i in inputs if i.identifier == "Shader_001"), None) $\endgroup$ Jul 7, 2021 at 12:18

1 Answer 1

5
$\begingroup$

Sometimes what we DON'T WANT is the go.

Have previously commented on questions / answers where the poster was indexing by number that indexing by name was "better" more readable etc. Then around about the time of this answer

Principled BSDF via Python API

I discovered that changing the blender language, also changed the socket names. (This appears to be no longer the case, notice though changing to Espanol has changed the node name)

>>> mix = D.materials['Material'].node_tree.nodes['Mezclar sombreadores']
>>> mix
bpy.data.materials['Material'].node_tree.nodes["Mezclar sombreadores"]

I DO NOT want to do this as I may not always know the correct index number:

Once we have a reference to the node, the indices of the inputs. These are always in the same order.

>>> for i, s in enumerate(mix.inputs.values()):
...     i, s.name, s.identifier
...     
(0, 'Fac', 'Fac')
(1, 'Shader', 'Shader')
(2, 'Shader', 'Shader_001')

The inputs collection, like so many collections in blender, including making our own via bpy.props.CollectionProperty is a built-in extension type. Such a collection can be indexed like a list or a dictionary, however unlike a dictionary items can have same name.

>>> type(mix.inputs)
<class 'bpy_prop_collection'>

>>> type(D.objects)
<class 'bpy_prop_collection'>

Some workarounds

Further to question method to search on the identifier, could:

Rename it

enter image description here

>>> mix.inputs[2].name = "Bob"
>>> mix.inputs.get("Bob")
bpy.data.materials['Material'].node_tree.nodes["Mezclar sombreadores"].inputs[2]

Create script to iterate over all nodes and change the socket names.

The name will remain as "Bob" for the blender session. It will revert back to "Shader" for new mix node or on save / re-open.

Kind of silly on one hand, on the other shows how simply one can totally stuff up any script that relies on object names

(0, 'Fac', 'Fac')
(1, 'Shader', 'Shader')
(2, 'Bob', 'Shader_001.001')

... notice this has also auto renamed the read-only identifier property.

Monkey Patch

An example of monkey-patching a method onto a blender class.

This is using not the index in the inputs, rather the index 1 to signify this is the 2nd input socket with the same name. In hindsight perhaps returning the list, instead of passing the index.

Could instead use identifier here, but as noted above there is no guarantee.

import bpy
from collections import defaultdict

context = bpy.context

def wireup(name):
    def getinput(self):
        coll = getattr(self, name, {})
        def _get(name, index=0, default=None):
            sockets = defaultdict(list)
            for k, v in coll.items():
                sockets[k].append(v)
            ret =  sockets[name][index]
            return ret if ret else default
        return _get
    return getinput
        

bpy.types.Node.getinput = property(wireup("inputs"))
bpy.types.Node.getoutput = property(wireup("outputs"))

Test in console.

>>> mix.getinput("Shader")
bpy.data.materials['Material'].node_tree.nodes["Mezclar sombreadores"].inputs[1]

>>> mix.getinput("Shader", 1)
bpy.data.materials['Material'].node_tree.nodes["Mezclar sombreadores"].inputs[2]

>>> mix.getinput("Shader", -1)
bpy.data.materials['Material'].node_tree.nodes["Mezclar sombreadores"].inputs[2]

Other suggestions would be a simple node group to wrap the mix node with io names of our choosing, or enumerating the input and output socket names to a panel in the node editor.

To sum up, in as much as can workaround, perhaps the "goto" method here is to use the index directly, ie that second shader input in a mix node is always in slot 2, and reflect the choice via comment or variable name.

# Mix Node
#    Inputs
#    (0, 'Fac')
#    (1, 'Shader')
#    (2, 'Shader')


shader_input_1 = mix.inputs[1] # "Shader"
shader_input_2 = mix.inputs[2]
$\endgroup$
1
  • $\begingroup$ Indeed helpful! Thank you! $\endgroup$
    – Derekcbr
    Sep 22, 2022 at 8:31

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .