1
$\begingroup$

I am creating a script from all sort of sources, I am no coder, so I am sure it is ugly, but it does the trick. The issue is with the button operator, when running the code it works fine, but when I click on the panel button that has been created in the UI I get error messages that are just beyond me.

This is where I got with the button script + operator: Thanks!

import bpy

class _OT_SimpleOperator(bpy.types.Operator):
    """Creates GLB setup after baking"""
    bl_idname = "object.after_bake_setup"
    bl_label = "After Bake Setup"


    def execute(self, context):

        #Materials Remove, Add material using object's name
        selectedObject = bpy.context.selected_objects
        print (selectedObject)

        listOfObj=[]
        for obj in selectedObject:
            listOfObj.append(obj)
            obj = bpy.context.view_layer.objects.active
            for x in obj.material_slots:
                obj.active_material_index = 0
                bpy.ops.object.material_slot_remove()


        #UV's - Remove all UV's except for the 'bake' (option for list ones on 'names')
        def rem_uvs(uvs_to_keep, ob):
            uvs_to_rem = []
            if type(uvs_to_keep) == 'str':
                uvs_to_keep = list(uvs_to_keep)
            if ob.type != 'MESH':
                return
            for uv in ob.data.uv_layers:
                if uv.name in uvs_to_keep:
                    continue
                uvs_to_rem.append(uv)
            while uvs_to_rem:
                ob.data.uv_layers.remove(uvs_to_rem.pop())


        # providing single name to keep
        rem_uvs("baked", bpy.context.object)

        # providing list of names to keep
        #names = [
        #    "bake",
        #    "Combined",
        #    "Normal",
        #    ]
        #rem_uvs(names, bpy.context.object)

        obj = bpy.context.view_layer.objects.active
        newMaterial = bpy.data.materials.new(name=obj.name)

        obj.data.materials.append(newMaterial)

        #Material Node Setup
        newMaterial.use_nodes = True
        principled_node = newMaterial.node_tree.nodes.get('Principled BSDF')

        image_texture_node = bpy.context.object.active_material.node_tree.nodes.new('ShaderNodeTexImage')
        image_texture_node.location = (-300, 300)
        newMaterial.node_tree.links.new(image_texture_node.outputs[0], principled_node.inputs[0])

        uv_map_node = bpy.context.object.active_material.node_tree.nodes.new('ShaderNodeUVMap')
        uv_map_node.location = (-520, 300)
        newMaterial.node_tree.links.new(uv_map_node.outputs[0], image_texture_node.inputs[0])
        uv_map_node.uv_map = "bake"


class _PT_LayoutDemoPanel(bpy.types.Panel):
    """Creates a Panel in the scene context of the properties editor"""
    bl_label = "Layout Demo"
    bl_idname = "SCENE_PT_layout"
    bl_space_type = 'PROPERTIES'
    bl_region_type = 'WINDOW'
    bl_context = "scene"

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

        scene = context.scene


        # Big render button
        layout.label(text="Big Button:")
        row = layout.row()
        row.scale_y = 2.0
        row.operator("object.after_bake_setup")



def register():
    bpy.utils.register_class(_OT_SimpleOperator)
    bpy.utils.register_class(_PT_LayoutDemoPanel)

def unregister():
    bpy.utils.unregister_class(_OT_SimpleOperator)
    bpy.utils.unregister_class(_PT_LayoutDemoPanel)

if __name__ == "__main__":
    register()

The error I'm getting, I'm pretty sure this is going to be only one of few, I think when putting the code in a class it messes up calling some of the functions.. The code (the execute) on its own works fine when running from the text editor.

enter image description here

after adding poll as I can see on blender python templates ('operator simple) I get this error: something to do with context incorrect

location: <unknown location>:-1
Error: Python: Traceback (most recent call last):
  File "\AfterBake_Addon_2.py", line 26, in execute
  File "C:\Program Files\Blender Foundation\Blender 2.92\2.92\scripts\modules\bpy\ops.py", line 132, in __call__
    ret = _op_call(self.idname_py(), None, kw)
RuntimeError: Operator bpy.ops.object.material_slot_remove.poll() failed, context is incorrect
$\endgroup$
4
  • $\begingroup$ Hello, copy pasting the error thrown in the console will help pinpoint the issue :) $\endgroup$ – Gorgious Apr 20 at 16:56
  • $\begingroup$ @Gorgious I have added the error message to the post. $\endgroup$ – Ebi Apr 20 at 17:47
  • $\begingroup$ Give the operator a poll method such that it doesn't poll (cannot be run) when context.object is None ---> this being the case is producing the error mentioned in question. context.object is none for example after delete operator is run $\endgroup$ – batFINGER Apr 22 at 15:00
  • $\begingroup$ @batFINGER I still get this error regarding the context, what still make me think that when putting the code inside the execute operator I loose the active object somehow $\endgroup$ – Ebi Apr 22 at 19:00
1
$\begingroup$

Edits.

  • Please never post code as image.
  • Using context.object in the properties can be dodgy as (speculating) it is also used to pin an object. Printing will show it is often None, this was causing the polling error with remove material slot operator.
  • To remove all the materials from selected objects can override the operator to pass the non (or) context object to it directly. Suggest this could be a logic error as it will simply clear all materials of the selected objects, whereas perhaps you desire to do same to all.
  • There is no need to tack a _PT or _OT onto the name of classes. Let the bl_idname do this for us, for operators "foo.bar" -> "FOO_OT_bar".
  • The operator has to return a status set, eg {'FINISHED'} if run successfully.
  • If we poll that the active object need be, and need be a mesh there need be no need to test in code.
  • Use the context argument passed to an operator, there is never a need to see bpy.context in operator or panel code.
  • On a semantics side, and recommended in best practice, stick to lower case variable names, active_obj over activeObj. The latter is reminiscent of blender circa "python 2.x"
  • IMO good practice to put all our registerable classes in a list. This comes in very handy both when adding new classes, and when inspecting the addon.

Code revamp.

import bpy

class SimpleOperator(bpy.types.Operator):
    """Creates GLB setup after baking"""
    bl_idname = "object.after_bake_setup"
    bl_label = "After Bake Setup"

    @classmethod
    def poll(cls, context):
        ob = context.active_object
        return ob and ob.type == 'MESH'
    
    def execute(self, context):

        for obj in context.selected_objects:
            if obj.type != 'MESH':
                continue

            while obj.material_slots:
                obj.active_material_index = 0
                bpy.ops.object.material_slot_remove({"object" : obj})


        def rem_uvs(uvs_to_keep, ob):
            uvs_to_rem = []
            if type(uvs_to_keep) == 'str':
                uvs_to_keep = list(uvs_to_keep)
            for uv in ob.data.uv_layers:
                if uv.name in uvs_to_keep:
                    continue
                uvs_to_rem.append(uv)
            while uvs_to_rem:
                ob.data.uv_layers.remove(uvs_to_rem.pop())


        # providing single name to keep
        obj = context.active_object
        rem_uvs("baked", obj)

        mat = bpy.data.materials.new(name=obj.name)

        obj.data.materials.append(mat)

        #Material Node Setup
        mat.use_nodes = True
        principled_node = mat.node_tree.nodes.get('Principled BSDF')

        image_texture_node = mat.node_tree.nodes.new('ShaderNodeTexImage')
        image_texture_node.location = (-300, 300)
        mat.node_tree.links.new(image_texture_node.outputs[0], principled_node.inputs[0])

        uv_map_node = mat.node_tree.nodes.new('ShaderNodeUVMap')
        uv_map_node.location = (-520, 300)
        mat.node_tree.links.new(uv_map_node.outputs[0], image_texture_node.inputs[0])
        uv_map_node.uv_map = "bake"
        return {'FINISHED'}


class LayoutDemoPanel(bpy.types.Panel):
    """Creates a Panel in the scene context of the properties editor"""
    bl_label = "Layout Demo"
    bl_idname = "SCENE_PT_layout"
    bl_space_type = 'PROPERTIES'
    bl_region_type = 'WINDOW'
    bl_context = "scene"

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

        scene = context.scene


        # Big render button
        layout.label(text="Big Button:")
        row = layout.row()
        row.scale_y = 2.0
        row.operator("object.after_bake_setup")


classes = (
        SimpleOperator,
        LayoutDemoPanel
        )
        
def register():
    for cls in classes:
        bpy.utils.register_class(cls)

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

if __name__ == "__main__":
    register()
$\endgroup$
2
  • 1
    $\begingroup$ Thanks so much for that, works perfect. I will have to look through the code a bit longer to see what you've done there. many thanks. $\endgroup$ – Ebi Apr 23 at 12:39
  • $\begingroup$ NP, happy to help. $\endgroup$ – batFINGER Apr 23 at 12:55

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.