# How to Update NodeSocketString Value inside a node group in a attribute node using python

I have an Attribute node inside a node group, and for the node group inputs, I have created a NodeSocketString, Using group.inputs.new("NodeSocketString", "Factor") and I want that whenever I enter any String value in that NodeSocket, it should also be updated in the attribute node immediately I tried attribute_node.attribute_name = group.inputs[3].default_value but it is not working as expected this gives the default value set for node socket in the attribute node but what is want is that this should change whenever I change the value there so I also tried to do this using a for loop -

for default_value in group.inputs["Factor"]:
attribute_node.attribute_name = group.inputs[3].default_value


but this is also not working, this produces an error - TypeError: 'NodeSocketInterfaceString' object is not iterable

I want that if I insert any string value in the Factor input shown in screenshot 1 that value should also be updated in the attribute node (that is present inside this node group) shown in the screenshot 2

Screenshot 1 -

I want this value(in the attribute node) to be updated every time when any value is inserted in the group node shown in screenshot-1

Screenshot 2 -

So is this Possible? and do I have to declare separately a string variable? Can anyone help me with this problem?

This is the Updated Script -

bl_info = {
"author": "Rakesh Choudhary",
"version": (1, 0),
"blender": (2, 83, 0),
"location": "View3D > Sidebar > Test Material Node",
"description": "Click on the 'Test Material' button to add a material to your object.",
"warning": "",
"wiki_url": "",
"category": "3D View"
}

import bpy
from bpy.types import (
Operator,
Panel,
)

def execute(self, context):
self.create_material(context)
return {'FINISHED'}

def create_material(self, context):
mesh = context.object.data
mesh.materials.clear()
context.object.active_material.use_nodes = True

for mat in bpy.data.materials:
if "TestMat" in mat.name:
nodes = mat.node_tree.nodes
for node in nodes:
if node.type != 'OUTPUT_MATERIAL':  # skip the material output node as we'll need it later
nodes.remove(node)

# Creating Node Group Test_Material

# Creating Group Input
group.inputs.new("NodeSocketColor", "Diffuse Color")
group.inputs.new("NodeSocketColor", "Glossy Color")
group.inputs.new("NodeSocketFloat", "Glossyness")
group.inputs.new("NodeSocketString", "Factor")
input_node = group.nodes.new("NodeGroupInput")
input_node.location = (-800, 0)

group.inputs[0].default_value = (1, 1, 1, 1)
group.inputs[1].default_value = (1, 1, 1, 1)
group.inputs[2].default_value = (0.500)
group.inputs[3].default_value = "foam"

# Creating Group Output Node

output_node = group.nodes.new("NodeGroupOutput")
output_node.location = (1500, 0)

# Creating Diffuse Node
diffuse_node.location = (150, 100)

# Creating Glossy Node
glossy_node.location = (300, 250)

#Attribute Node
attribute_node.location  = (100, 400)
attribute_node.attribute_name = group.inputs[3].default_value

# Putting Node Group to the node editor
tree = bpy.context.object.active_material.node_tree
group_node.node_tree = group
group_node.location = (-40, 300)
group_node.use_custom_color = True
group_node.color = (1, 0.341, 0.034)
group_node.width = 250

#Material ends here------------------------------

class TEST_MATERIAL_PT_layout_panel(Panel):
bl_label = "Test Material Node"
bl_category = "Test Material"
bl_space_type = "VIEW_3D"
bl_region_type = "UI"

def draw(self, context):
layout = self.layout

def get_group_nodes(material):
if material.use_nodes:
return [n for n in material.node_tree.nodes if isinstance(n, bpy.types.ShaderNodeGroup)]
return []

def set_value(self, value):
print(self, self.id_data, value)
if not 'Factor' in self.inputs.keys():
return None
self.inputs['Factor'].default_value = value
name = self.node_tree.name
bpy.data.node_groups[name].nodes['Attribute'].attribute_name = value

def get_value(self):
if not 'Factor' in self.inputs.keys():
return ""
return self.inputs["Factor"].default_value

get=get_value,
set=set_value)

def draw_prop(self, context):
mat = context.material
nodes = get_group_nodes(mat)
for n in nodes:
self.layout.prop(n, "factor")

def register():
for cls in classes:
bpy.utils.register_class(cls)

def unregister():
for cls in classes:
bpy.utils.unregister_class(cls)

if __name__ == "__main__":
register()


I tried from the answer I just put that code into the panel class, but that is not working and the error I am getting is -

NameError: name 'get_group_nodes' is not defined


what I did wrong?

Follow up - If I split the script and use init.py to import file, this method does not work I get this error -

Traceback (most recent call last): File "C:\Users\ADMIN\AppData\Roaming\Blender Foundation\Blender\2.83\scripts\addons\Test for init\panel.py", line 19, in draw for n in get_group_nodes(mat): NameError: name 'get_group_nodes' is not defined

location: :-1

location: :-1 Traceback (most recent call last):
File "C:\Users\ADMIN\AppData\Roaming\Blender Foundation\Blender\2.83\scripts\addons\Test for init\panel.py", line 19, in draw for n in get_group_nodes(mat): NameError: name 'get_group_nodes' is not defined

location: :-1

I split this script into 3 files first is init.py, second is material_operator.py and third is panel.py

_ init _.py

bl_info = {
"author": "Rakesh Choudhary",
"version": (1, 0),
"blender": (2, 83, 0),
"location": "View3D > Sidebar > Test Material Node",
"description": "Click on the 'Test Material' button to add a material to your object.",
"warning": "",
"wiki_url": "",
"category": "3D View"
}

import bpy
from bpy.types import (
Operator,
Panel,
)
from . import material_operator
from . import panel

def register():
material_operator.register()
panel.register()

def unregister():
material_operator.unregister()
panel.unregister()

if __name__ == "__main__":
register()


material operator.py -

import bpy
from bpy.types import (
Operator,
Panel,
)
def get_group_nodes(material):
if material.use_nodes:
return [n for n in material.node_tree.nodes if isinstance(n, bpy.types.ShaderNodeGroup)]
return []

def set_value(self, value):
print(self, self.id_data, value)
if not 'Factor' in self.inputs.keys():
return None
self.inputs['Factor'].default_value = value
name = self.node_tree.name
bpy.data.node_groups[name].nodes['Attribute'].attribute_name = value

def get_value(self):
if not 'Factor' in self.inputs.keys():
return ""
return self.inputs["Factor"].default_value

def execute(self, context):
self.create_material()
return {'FINISHED'}

def create_material(self):
mesh = bpy.context.object.data
mesh.materials.clear()
bpy.context.object.active_material.use_nodes = True

for mat in bpy.data.materials:
if "TestMat" in mat.name:
nodes = mat.node_tree.nodes
for node in nodes:
if node.type != 'OUTPUT_MATERIAL':  # skip the material output node as we'll need it later
nodes.remove(node)

# Creating Node Group Test_Material

# Creating Group Input
group.inputs.new("NodeSocketColor", "Diffuse Color")
group.inputs.new("NodeSocketColor", "Glossy Color")
group.inputs.new("NodeSocketFloat", "Glossyness")
group.inputs.new("NodeSocketString", "Factor")
input_node = group.nodes.new("NodeGroupInput")
input_node.location = (-800, 0)

# Creating Group Output Node

output_node = group.nodes.new("NodeGroupOutput")
output_node.location = (1500, 0)

# Creating Diffuse Node
diffuse_node.location = (150, 100)

# Creating Glossy Node
glossy_node.location = (300, 250)

# Creating Attribute Node
attribute_node.location = (400, -300)
attribute_node.attribute_name = group.inputs[3].default_value

# Putting Node Group to the node editor
tree = bpy.context.object.active_material.node_tree
group_node.node_tree = group
group_node.location = (-40, 300)
group_node.use_custom_color = True
group_node.color = (1, 0.341, 0.034)
group_node.width = 250

#Material ends here------------------------------

def register():

def unregister():

if __name__ == "__main__":
register()


panel.py -

import bpy
from bpy.types import (
Operator,
Panel,
)

from material_operator import get_group_nodes

class TEST_MATERIAL_PT_layout_panel(Panel):
bl_label = "Test Material Node"
bl_category = "Test Material"
bl_space_type = "VIEW_3D"
bl_region_type = "UI"

def draw(self, context):
layout = self.layout
mat = context.object.active_material
if mat:
for n in get_group_nodes(mat):
layout.prop(n, "factor")

def register():
bpy.utils.register_class(TEST_MATERIAL_PT_layout_panel)
get=get_value,
set=set_value)

def unregister():
bpy.utils.unregister_class(TEST_MATERIAL_PT_layout_panel)

if __name__ == "__main__":
register()


Can you show me where I am doing wrong?

• I'd suggest you upload a minimal working example of your addon. The creation of all those nodes does not seem relevant to your problem. Jun 30 '20 at 9:37
• Agree with Leander. Also, can't see any StringProperty declared... Where and how the user can enter a new string? Jun 30 '20 at 14:27
• @brockmann I updated my question so that it is easy to understand, I want that whatever string value I feed in the Factor Socket in the group node that value should automatically be updated inside the attribute node present inside the node group. Is it possible? Jun 30 '20 at 15:32
• AFAIK there is no way to call a function for NodeGroupInputs on update: docs.blender.org/api/current/… Not entirely sure though... Too bad that the attribute node doesn't provide any input string socket by default, might be a nice feature request IMHO. You can try msgbus per node: blender.stackexchange.com/questions/150809/… or just add a button to force an update. Jun 30 '20 at 15:59
• @brockmann thanks for the update, as you said I can add a button to force an Update can you show me how to do that. Jul 1 '20 at 6:14

Updating a user defined property

In the proof of concept (ie needs checks and balances to test for particular group nodes) code below:

• Defines a custom property on a shader node group. As with all properties defined this way, all shadernode groups will have this value.

• When the custom property is updated it sets the group node input and attribute nodes attribute name to value.

• Have instead of an update method on the property used a getter and setter by way of example.. An update, as demonstrated in linked, could be used instead.

• Use an enum property to only be able to choose from a particular set. Gif above uses "froth" by way of example, with no idea if it is a viable option.

• Example uses shader editor header where context.material is defined. To put the property in panel in 3D view UI would require getting material from, for example context objects active material

Test code:

import bpy

def get_group_nodes(material):
if material.use_nodes:
return [n for n in material.node_tree.nodes if isinstance(n, bpy.types.ShaderNodeGroup)]
return []

def set_value(self, value):
print(self, self.id_data, value)
if not 'Factor' in self.inputs.keys():
return None
self.inputs['Factor'].default_value = value
name = self.node_tree.name
bpy.data.node_groups[name].nodes['Attribute'].attribute_name = value

def get_value(self):
if not 'Factor' in self.inputs.keys():
return ""
return self.inputs["Factor"].default_value

get=get_value,
set=set_value)

def draw_prop(self, context):
mat = context.material
nodes = get_group_nodes(mat)
for n in nodes:
self.layout.prop(n, "factor")



Re edit, quicker to do than explain, here is question code with above added. Recommend you read https://stackoverflow.com/questions/291978/short-description-of-the-scoping-rules re the scope of variables / methods in python.

bl_info = {
"author": "Rakesh Choudhary",
"version": (1, 0),
"blender": (2, 83, 0),
"location": "View3D > Sidebar > Test Material Node",
"description": "Click on the 'Test Material' button to add a material to your object.",
"warning": "",
"wiki_url": "",
"category": "3D View"
}

import bpy
from bpy.types import (
Operator,
Panel,
)

def get_group_nodes(material):
if material.use_nodes:
return [n for n in material.node_tree.nodes if isinstance(n, bpy.types.ShaderNodeGroup)]
return []

def set_value(self, value):
print(self, self.id_data, value)
if not 'Factor' in self.inputs.keys():
return None
self.inputs['Factor'].default_value = value
name = self.node_tree.name
bpy.data.node_groups[name].nodes['Attribute'].attribute_name = value

def get_value(self):
if not 'Factor' in self.inputs.keys():
return ""
return self.inputs["Factor"].default_value

def execute(self, context):
self.create_material(context)
return {'FINISHED'}

def create_material(self, context):
mesh = context.object.data
mesh.materials.clear()
context.object.active_material.use_nodes = True

for mat in bpy.data.materials:
if "TestMat" in mat.name:
nodes = mat.node_tree.nodes
for node in nodes:
if node.type != 'OUTPUT_MATERIAL':  # skip the material output node as we'll need it later
nodes.remove(node)

# Creating Node Group Test_Material

# Creating Group Input
group.inputs.new("NodeSocketColor", "Diffuse Color")
group.inputs.new("NodeSocketColor", "Glossy Color")
group.inputs.new("NodeSocketFloat", "Glossyness")
group.inputs.new("NodeSocketString", "Factor")
input_node = group.nodes.new("NodeGroupInput")
input_node.location = (-800, 0)

group.inputs[0].default_value = (1, 1, 1, 1)
group.inputs[1].default_value = (1, 1, 1, 1)
group.inputs[2].default_value = (0.500)
group.inputs[3].default_value = "foam"

# Creating Group Output Node

output_node = group.nodes.new("NodeGroupOutput")
output_node.location = (1500, 0)

# Creating Diffuse Node
diffuse_node.location = (150, 100)

# Creating Glossy Node
glossy_node.location = (300, 250)

#Attribute Node
attribute_node.location  = (100, 400)
attribute_node.attribute_name = group.inputs[3].default_value

# Putting Node Group to the node editor
tree = bpy.context.object.active_material.node_tree
group_node.node_tree = group
group_node.location = (-40, 300)
group_node.use_custom_color = True
group_node.color = (1, 0.341, 0.034)
group_node.width = 250

#Material ends here------------------------------

class TEST_MATERIAL_PT_layout_panel(Panel):
bl_label = "Test Material Node"
bl_category = "Test Material"
bl_space_type = "VIEW_3D"
bl_region_type = "UI"

def draw(self, context):
layout = self.layout
mat = context.object.active_material
if mat:
for n in get_group_nodes(mat):
layout.prop(n, "factor")

def register():
for cls in classes:
bpy.utils.register_class(cls)
get=get_value,
set=set_value)

def unregister():
for cls in classes:
bpy.utils.unregister_class(cls)

if __name__ == "__main__":
register()

• thanks for answering but still I have one problem, maybe I made some mistake in the code because it is not working for me I updated in the question whatever I did after your answer Jul 1 '20 at 14:41
• NP. note made a small change making material in panel draw objects active material. Jul 1 '20 at 15:09
• If its elsewhere import it eg from test_addon.operators import get_nodes Jul 2 '20 at 12:23
• Re multi file I would ask myself whats the logical way to split up the files. Why not have the operator that creates the material and the panel that displays it all in the one module. On the errors and assoc edits to question. These are, strictly speaking, basic python errors, not associated with the use of blender. Strongly recommend googling eg "python nameerror" the causes and fixes are well documented. Stackoverflow is good in this regard. Jul 2 '20 at 12:40
• One operator could make multiple materials. Anyway please review my previous comment re import. Don't mind helping, but this is getting way past the original question or related to usage of python in blender. Is basic relative importing. I'm not here to teach you python, nor am I inclined to argue your point on what is or isn't good practice. Jul 2 '20 at 14:00