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 = { "name": "Add Test Material", "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, ) class TEST_MATERIAL_OT_add_material(Operator): bl_idname = "test_material.add_material" bl_label = "Add Test Material" bl_description = "This button will add a material to your object" def execute(self, context): self.create_material(context) return {'FINISHED'} def create_material(self, context): test_shader_mat = bpy.data.materials.new("TestMat") mesh = context.object.data mesh.materials.clear() mesh.materials.append(test_shader_mat) 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 group = bpy.data.node_groups.new(type="ShaderNodeTree", name="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 group.outputs.new("NodeSocketShader", "Diffuse Color") group.outputs.new("NodeSocketShader", "Glossy Color") group.outputs.new("NodeSocketShader", "Mix Output") output_node = group.nodes.new("NodeGroupOutput") output_node.location = (1500, 0) # Creating Diffuse Node diffuse_node = group.nodes.new(type='ShaderNodeBsdfDiffuse') diffuse_node.location = (150, 100) # Creating Glossy Node glossy_node = group.nodes.new(type='ShaderNodeBsdfGlossy') glossy_node.location = (300, 250) # Creating Mix Shader Node mix_shader_node = group.nodes.new(type='ShaderNodeMixShader') mix_shader_node.location = (450, 100) #Attribute Node attribute_node = group.nodes.new(type='ShaderNodeAttribute') attribute_node.location = (100, 400) attribute_node.attribute_name = group.inputs[3].default_value # Creating Links Between Nodes---------------------------------------------- group.links.new(diffuse_node.outputs["BSDF"], mix_shader_node.inputs[1]) group.links.new(glossy_node.outputs["BSDF"], mix_shader_node.inputs[2]) group.links.new(input_node.outputs["Diffuse Color"], diffuse_node.inputs[0]) group.links.new(input_node.outputs["Glossy Color"], glossy_node.inputs[0]) group.links.new(input_node.outputs["Glossyness"], glossy_node.inputs[1]) group.links.new(output_node.inputs["Diffuse Color"], diffuse_node.outputs[0]) group.links.new(output_node.inputs["Glossy Color"], glossy_node.outputs[0]) group.links.new(output_node.inputs["Mix Output"], mix_shader_node.outputs[0]) group.links.new(attribute_node.outputs["Fac"], mix_shader_node.inputs[0]) # Putting Node Group to the node editor tree = bpy.context.object.active_material.node_tree group_node = tree.nodes.new("ShaderNodeGroup") 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 shader_node_output_material_node = tree.nodes["Material Output"] links = tree.links links.new(group_node.outputs[0], shader_node_output_material_node.inputs[0]) #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 layout.operator("test_material.add_material", icon='IMPORT') 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 bpy.types.ShaderNodeGroup.factor = bpy.props.StringProperty( 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") bpy.types.NODE_HT_header.prepend(draw_prop) classes = (TEST_MATERIAL_OT_add_material, TEST_MATERIAL_PT_layout_panel) 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() Follow up -
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 definedlocation: :-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 = { "name": "Add Test Material", "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 class TEST_MATERIAL_OT_add_material(Operator): bl_idname = "test_material.add_material" bl_label = "Add Test Material" bl_description = "This button will add a material to your object" def execute(self, context): self.create_material() return {'FINISHED'} def create_material(self): test_shader_mat = bpy.data.materials.new("TestMat") mesh = bpy.context.object.data mesh.materials.clear() mesh.materials.append(test_shader_mat) 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 group = bpy.data.node_groups.new(type="ShaderNodeTree", name="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 group.outputs.new("NodeSocketShader", "Diffuse Color") group.outputs.new("NodeSocketShader", "Glossy Color") group.outputs.new("NodeSocketShader", "Mix Output") output_node = group.nodes.new("NodeGroupOutput") output_node.location = (1500, 0) # Creating Diffuse Node diffuse_node = group.nodes.new(type='ShaderNodeBsdfDiffuse') diffuse_node.location = (150, 100) # Creating Glossy Node glossy_node = group.nodes.new(type='ShaderNodeBsdfGlossy') glossy_node.location = (300, 250) # Creating Mix Shader Node mix_shader_node = group.nodes.new(type='ShaderNodeMixShader') mix_shader_node.location = (450, 100) # Creating Attribute Node attribute_node = group.nodes.new(type="ShaderNodeAttribute") attribute_node.location = (400, -300) attribute_node.attribute_name = group.inputs[3].default_value # Creating Links Between Nodes---------------------------------------------- group.links.new(diffuse_node.outputs["BSDF"], mix_shader_node.inputs[1]) group.links.new(glossy_node.outputs["BSDF"], mix_shader_node.inputs[2]) group.links.new(input_node.outputs["Diffuse Color"], diffuse_node.inputs[0]) group.links.new(input_node.outputs["Glossy Color"], glossy_node.inputs[0]) group.links.new(input_node.outputs["Glossyness"], glossy_node.inputs[1]) group.links.new(output_node.inputs["Diffuse Color"], diffuse_node.outputs[0]) group.links.new(output_node.inputs["Glossy Color"], glossy_node.outputs[0]) group.links.new(output_node.inputs["Mix Output"], mix_shader_node.outputs[0]) # Putting Node Group to the node editor tree = bpy.context.object.active_material.node_tree group_node = tree.nodes.new("ShaderNodeGroup") 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 shader_node_output_material_node = tree.nodes["Material Output"] links = tree.links links.new(group_node.outputs[0], shader_node_output_material_node.inputs[0]) #Material ends here------------------------------ def register(): bpy.utils.register_class(TEST_MATERIAL_OT_add_material) def unregister(): bpy.utils.unregister_class(TEST_MATERIAL_OT_add_material) 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 layout.operator("test_material.add_material", icon='IMPORT') 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) bpy.types.ShaderNodeGroup.factor = bpy.props.StringProperty( 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?



NodeGroupInputson 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 trymsgbusper node: blender.stackexchange.com/questions/150809/… or just add a button to force an update. $\endgroup$