0
$\begingroup$

Creating Materials using python is easy and it has a lot of answers on StackExchange.

Now what I want to ask is -

If I want that the material should only be added if the object (on which material to be added) should be mesh or text,

and material should not be added in case of curves or anything else and it should give a custom error message. Whenever the add test material button is pressed operator should only be executed if the object is mesh. Actually I want to specify in the script that this material to be added only if the object type is mesh. (Please have a look at the script)

import bpy


class TEST_MATERIAL_OT_add_material(bpy.types.Operator):
    bl_idname = "test_material.add_material"
    bl_label = "Add Test Material"
    bl_description = "This button will add a material to your object"

    @classmethod
    def poll(cls, context):
        ob = context.active_object
        return ob is not None and ob.type=='MESH'




        def execute(self, context):
            ob = context.active_object

            if ob is None:
                self.report({'ERROR'}, "No active object")
                return {"CANCELLED"}

            if ob.type != 'MESH':
                self.report({'ERROR'}, "Object is not a mesh")
                return {"CANCELLED"}

            self.report({'INFO'}, "Object is a mesh, operator can run")

            self.create_material()
            return {'FINISHED'}

    def create_material(self):


        # removes unwanted nodes
        for node in tree.nodes:
            tree.nodes.remove(node)

        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", "Mix Factor")
        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 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["Mix Factor"], mix_shader_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])


class TEST_MATERIAL_PT_layout_panel(bpy.types.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')


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()
$\endgroup$
11
  • $\begingroup$ Can anyone help me in applying the solution to this given script in the question? $\endgroup$ Mar 10, 2020 at 9:51
  • 2
    $\begingroup$ It's hard to tell what exact functionality you are after and in what context. Maybe see the script examples in the Text Editor's Templates menu. That might be helpful. Like brockmann mentioned poll might be what you are after. You will see how it's used in the templates. $\endgroup$ Mar 10, 2020 at 10:15
  • 2
    $\begingroup$ Please see the operator examples in the Text Editor's Templates menu. Poll is a classmethod(sort of a function) that defines conditions the operator is available in. It can be defined inside your operator as you would see in the examples. If it returns true, the operator becomes available. So if you put return context.object.type == 'MESH' the operator will only be available if the active object is of 'MESH' type. I am not sure if that's what you want to do or you want to go through some objects and then only act on the ones that are meshes. $\endgroup$ Mar 10, 2020 at 10:52
  • 2
    $\begingroup$ Yep, it would really make sense to start from the beginning and to define exactly what you want. $\endgroup$ Mar 10, 2020 at 10:57
  • 1
    $\begingroup$ So, yes, the poll method will fix this. That's the solution, just study the examples until you get how it works. $\endgroup$ Mar 10, 2020 at 11:24

3 Answers 3

3
$\begingroup$

To test whether the object is a mesh, you can ask for Object.type:

>>> C.object.type 
'MESH'

For multiple objects, you can use a list comprehension:

>>> [o for o in C.selected_objects if o.type=='MESH']
[bpy.data.objects['Cube'], bpy.data.objects['Icosphere']]

API on Object.type:

Type of Object

Type of Object Type: enum in [‘MESH’, ‘CURVE’, ‘SURFACE’, ‘META’, ‘FONT’, ‘ARMATURE’, ‘LATTICE’, ‘EMPTY’, ‘GPENCIL’, ‘CAMERA’, ‘LIGHT’, ‘SPEAKER’, ‘LIGHT_PROBE’], default ‘EMPTY’, (readonly)



In case you want to assign a material to the 'active object' using a custom operator, you can add a poll() method to limit the scope to mesh objects. poll() basically allows to adding a custom check if the operator can run even before executing, read: What do operator methods do?.

@classmethod
def poll(cls, context):

    # Get the active object in context
    ob = context.active_object

    # Returns True if there is an active object and the object is a mesh
    return ob is not None and ob.type=='MESH'

In other words: the execute() method of your operator is only called in case there is an 'active object' and if its type is a 'mesh'.


To be on the safe side, you can add the same checks to your execute method and report potential errors to the Info Area, read: Is it possible to print to the Report window in the Info view?

def execute(self, context):

    # Get the active object in context
    ob = context.active_object

    if ob is None:
        self.report({'ERROR'}, "No active object")
        return {"CANCELLED"}

    if ob.type != 'MESH':
        self.report({'ERROR'}, "Object is not a mesh")
        return {"CANCELLED"}

    self.report({'INFO'}, "Object is a mesh, operator can run")

    # Assign the material here
    # ....

    return {'FINISHED'}

To assign the material, read: How to assign a new material to an object in the scene from Python?

Also notice

  1. You already have context which is passed to the operator methods, use it! I'd suggest pass it to create_material(self, context) as well in order to get eg. the same reference to the object context.active_object instead of using bpy.context.active_object

    def create_material(self, context):
    
        # Get the active object in context
        ob = context.active_object
    
        # Get the active material of ob
        mat = ob.active_material
        #...
    
  2. context.scene.use_nodes = True adds the default nodes to the Compositor so it has nothing to do with materials

  3. I suggest add bl_options = {'REGISTER', 'UNDO'} to the operator which allows the user going back in case


I think you agree that adding materials is not that simple and there is a lot to think about.

$\endgroup$
3
  • $\begingroup$ Ok, this gave me the solution, but I am stuck in another problem the answer will work if the object is a mesh type but what if I want that, this material can also be added to the text object, can I do this - return ob is not None and ob.type==['MESH', 'FONT'] $\endgroup$ May 5, 2020 at 17:45
  • $\begingroup$ Usually something like if ob.type in ('MESH', 'FONT'): is what you want (testing items in tuple using in operator). Suggest take a basic python course or have a look at stackoverflow: stackoverflow.com/a/15112149 @Rakeshchoudhary You're welcome. $\endgroup$
    – brockmann
    May 5, 2020 at 17:51
  • $\begingroup$ Thank you very much and I am joining course now instantly $\endgroup$ May 5, 2020 at 17:58
2
$\begingroup$

In a bit more simple terms:

Just go through the objects and check if the type is 'MESH'

For example if you wanted to go through all the selected objects you could do something like this:

import bpy 

m = bpy.data.materials.new('Some New Material')

for o in bpy.context.selected_objects:
    if o.type == 'MESH':
        if len(o.material_slots) < 1: #if no materials on the object
            o.data.materials.append(m) 
            #this will create a slot and add the material
        else:
            o.material_slots[o.active_material_index].material = m 
            #if there are slots, assign the material to the active one

You can obviously go through all the objects in your file(bpy.data.objects), or all the objects in the active scene(bpy.context.scene.objects) or whatever other list of the objects you wish to work with.

$\endgroup$
3
  • 1
    $\begingroup$ I don't understand why to be honest. I would probably check if material exists before making it, but if it does, why not just address it by the name then? get() is probably faster then checking if 'Some Material' in bpy.data.materials, isn't it? $\endgroup$ Mar 10, 2020 at 9:42
  • $\begingroup$ it is not needed to check whether the material is already existing or not, because this script will add another material on the object. what I wanted was what and where should I add a piece of code so that this script checks the type of object before adding material and then whenever the button is pressed, the material is only added if it is mesh, if not, the mesh then the blender should print a custom message to the console. $\endgroup$ Mar 10, 2020 at 9:57
  • 1
    $\begingroup$ Oh, so all of this is happening in an operator?.. I am not sure I understand the question any more. :D $\endgroup$ Mar 10, 2020 at 10:03
0
$\begingroup$

Finally, I got a solution to my question, before that I would like to thank Martynas Žiemys and brockmann as they tried solving my question. I got the hint from this post

Back to the solution - It just needed this if statement before creating the material and this worked for me

active = bpy.context.active_object
    if active.type == "MESH":
$\endgroup$
1
  • 2
    $\begingroup$ Agree with comments: This is really the job of the poll classmethod. Polling via the if statement, return context.active_object and context.active_object.type == 'MESH' on operator class will make operator button disabled in panel, or on panel class, make the panel not show. (Notice the use of context from the method arguments) In general an operator works on all the selected objects, hence the other answers given. Having an operator "do nothing" as suggested by this answer will work, but is not good practice. Also the other answers IMO are about 99.9% of the way there. $\endgroup$
    – batFINGER
    Mar 11, 2020 at 8:58

You must log in to answer this question.

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