3
$\begingroup$

I would Like to access the position of the element of a color ramp and directly draw that in the panel as a Float Value Slider so whenever the value in that slider is changed it updates in the color ramp.

I have Created a color ramp with:

col_ramp = group.nodes.new(type="ShaderNodeValToRGB")

And then assigned a position to it:

col_ramp.color_ramp.elements.new(0.750)
col_ramp.color_ramp.elements[0].color = (0,0,0,1)

And I have defined and operator and that will only be executed when the button is pressed from the Panel, and What I don't know is how to access that color ramp element.

I have tried this:

position_control = context.Scene.node_tree.col_ramp.color_ramp.elements[0].position

And this:

position_control = bpy.data.materials["TestMat"].node_tree.nodes["col_ramp"].elements[0].position

But finally, this is giving an error in the console like this:

AttributeError: ramp_pos Error: Python script failed, check the message in the system console

And showing error in line:

del bpy.types.Scene.ramp_pos

Reference to the previously asked question by me relating this Drawing Float Property

class Col_ramp_Property(PropertyGroup):
   ramp_pos : FloatProperty(
       name = "Position",
       description = "A float property",
       default = 0.75,
       min = 0.01,
       max = 1.0
    )

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
        position_control = context.Scene.active_object.active_material.node_tree.nodes.col_ramp.color_ramp.elements[0].position
        layout.operator("test_material.add_material", icon='IMPORT')
        row = layout.row()
        row.prop(position_control, "ramp_pos")

classes = (TEST_MATERIAL_OT_add_material, Col_ramp_Property, TEST_MATERIAL_PT_layout_panel)

def register():
    for cls in classes:
        bpy.utils.register_class(cls)
    bpy.types.Scene.ramp_pos = PointerProperty(type=Col_ramp_Property)

def unregister():
    for cls in classes:
        bpy.utils.unregister_class(cls)
    del bpy.types.Scene.ramp_pos

if __name__ == "__main__":
    register()

Update: I tried doing as in the answer, but this is not working for me, when I tried running this script(written below), it gives this error

TypeError: must be real number, not str Traceback (most recent call last): File "\TEST_MATERIAL.py", line 181, in File "\TEST_MATERIAL.py", line 169, in register ValueError: bpy_struct "Col_ramp_Property" registration error: ramp_pos could not register

Error: Python script failed, check the message in the system console

for knowing the error I tried removing class Col_ramp_Property from classes and in the register and unregister also, and then I got this error -

Traceback (most recent call last): File "\TEST_MATERIAL.py", line 142, in draw AttributeError: 'Object' object has no attribute 'material'

location: :-1

location: :-1

I don't know what I did wrong, can you Please Show me the mistake I made in this -

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,
    PropertyGroup,
)
from bpy.props import (
    FloatProperty,
    PointerProperty,
)


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")
        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 Color Ramp ------------------------------------------------------
        col_ramp = group.nodes.new(type="ShaderNodeValToRGB")
        col_ramp.name = "col_ramp"
        col_ramp.location = (400, -300)
        col_ramp.color_ramp.elements.remove(col_ramp.color_ramp.elements[0])
    
        col_ramp.color_ramp.elements.new(0.750)
        col_ramp.color_ramp.elements[0].color = (0,0,0,1)
    

        col_ramp.color_ramp.elements[1].position = (1.0)
        col_ramp.color_ramp.elements[1].color = (1, 1, 1, 1)
    

        # 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(col_ramp.outputs["Color"], 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 Col_ramp_Property(PropertyGroup):
        ramp_pos : FloatProperty(
            name = "Position",
            description = "A Float Property",
            default = "0.750",
            min = "0.010",
            max = "1.00"
        )



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
        active_object = context.view_layer.objects.active
        if not active_object:
            print("Select an Object")
            return
        active_material = active_object.material
        if not active_material:
            print("Add a material to the selected object")
            return
        node_tree = active_material.node_tree
        if not node_tree:
            print("Check 'Use Nodes' in the active material")
            return
        color_ramp = node_tree.nodes.get("col_ramp")
        if not color_ramp_node:
            print("Add a color ramp node to the active material")
            return
        
        position_control = color_ramp.color_ramp.elements[0]
        
        
        layout.operator("test_material.add_material", icon='IMPORT')
        
        row = layout.row()
        row.prop(position_control, "ramp_pos")


classes = (TEST_MATERIAL_OT_add_material, Col_ramp_Property, TEST_MATERIAL_PT_layout_panel)


def register():
    for cls in classes:
        bpy.utils.register_class(cls)
    bpy.types.Scene.ramp_pos = PointerProperty(type=Col_ramp_Property)

    

def unregister():
    for cls in classes:
        bpy.utils.unregister_class(cls)
    del bpy.types.Scene.ramp_pos


if __name__ == "__main__":
    register()
$\endgroup$
3
  • $\begingroup$ Hello, if you don't actually rename a color ramp with node.name = "***" when creating it, its name will default to "ColorRamp" or "ColorRamp.00*" if there is already one in the material. So your call should read position_control = bpy.data.materials["TestMat"].node_tree.nodes["ColorRamp"].color_ramp.elements[0].position. $\endgroup$
    – Gorgious
    Commented Jun 18, 2020 at 11:25
  • $\begingroup$ hi! @Gorgious thanks for answering, I renamed the color ramp because I am trying to Build a Complex Node group which will have many of the color ramps and for that purpose, each color ramps need to be called by their name, so that in further coding if I would like to call that color ramp it is always accessible to me in an easy way. $\endgroup$ Commented Jun 19, 2020 at 2:57
  • $\begingroup$ Then you need to add col_ramp.name = "col_ramp" after your initialize it, otherwise you can't access it by this name $\endgroup$
    – Gorgious
    Commented Jun 19, 2020 at 6:27

1 Answer 1

3
+50
$\begingroup$

I don't think you need to redefine a whole class to acces the position of the element of your color ramp. You can just use a reference to the actual position, which is anyways always constrained between 0 and 1.

This eliminates the overhead of having to update one when the other gets updated.

Updated script to look for the node inside a node group :

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,
    PropertyGroup,
)
from bpy.props import (
    FloatProperty,
    PointerProperty,
)


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")
        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 Color Ramp ------------------------------------------------------
        col_ramp = group.nodes.new(type="ShaderNodeValToRGB")
        col_ramp.name = "col_ramp"
        col_ramp.location = (400, -300)
        col_ramp.color_ramp.elements.remove(col_ramp.color_ramp.elements[0])
    
        col_ramp.color_ramp.elements.new(0.750)
        col_ramp.color_ramp.elements[0].color = (0,0,0,1)
    

        col_ramp.color_ramp.elements[1].position = (1.0)
        col_ramp.color_ramp.elements[1].color = (1, 1, 1, 1)
    

        # 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(col_ramp.outputs["Color"], 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')
        
        active_object = context.view_layer.objects.active
        if not active_object:
            print("Select an Object")
            return
        active_material = active_object.active_material
        if not active_material:
            print("Add a material to the selected object")
            return
        node_tree = active_material.node_tree            
        if not node_tree:
            print("Check 'Use Nodes' in the active material")
            return
        group = node_tree.nodes.get("Group")
        if not group:
            print("Node group was not found")
            return
        color_ramp_node = group.node_tree.nodes.get("col_ramp")
        if not color_ramp_node:
            print("Add a color ramp node to the active material")
            return
        
        position_control = color_ramp_node.color_ramp.elements[0]       
        
        row = layout.row()
        row.prop(position_control, "position")


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()

Result :

enter image description here

$\endgroup$
7
  • $\begingroup$ you are adding color ramp manually in this example, but in the basic script, I wrote - blender.stackexchange.com/questions/183011/… the color ramps are created in a material and the material will only be created when that operator(in which material l is created) is executed, what I exactly wanted is when I press that button the color ramp's element's position slider appears in the panel and that could be used to tweak the position of color ramp. Can you please please help me with this problem as I am not very much familiar with python. $\endgroup$ Commented Jun 19, 2020 at 3:05
  • 1
    $\begingroup$ There is no fundamental difference between adding a node by hand or by script, unless you change its name, you can access it the same way $\endgroup$
    – Gorgious
    Commented Jun 19, 2020 at 6:24
  • $\begingroup$ I tried doing this But I am getting errors, Can you please help me with this I have updated my script which I tried and in which I got the error. $\endgroup$ Commented Jun 20, 2020 at 16:31
  • 1
    $\begingroup$ Updated ! Also there is no "ramp_pos" attribute in the color ramp element, it is called "position" $\endgroup$
    – Gorgious
    Commented Jun 21, 2020 at 11:24
  • 1
    $\begingroup$ Yes I forgot to remove it :) $\endgroup$
    – Gorgious
    Commented Jun 21, 2020 at 13:43

You must log in to answer this question.

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