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