There's some weird hidden state going on in Operator subclass

Hello there, so I’m trying to script blender a little bit in order to use an external fracturing tool, however, I need to clear custom normal data for the objects that are imported back, and after my script was working in plain python (using the “Run Script” button), I decided to make a proper button in the toolbar for that, however, when the code is called from execute in an Operator subclass, some things in the loop don’t work anymore, it’s very weird because removing doubles for example works properly, but clearing the custom normals in the exact same loop doesn’t, for some reason it doesn’t apply to the object that’s being explicitly selected and activated in the python code, so I made an sscce using modifiers, here’s a simple working code:

import bpy

context = bpy.context
scene = context.scene

selected_objs = context.selected_objects[:] #copy just in case

print("\nstarting")
for obj in selected_objs:
    bpy.ops.object.select_all(action='DESELECT')
    obj.select = True
    scene.objects.active = obj
    print("current",obj.name,"selected",context.selected_objects[0].name,"active",scene.objects.active.name)
    bpy.ops.object.shade_smooth()
    bpy.ops.object.modifier_add(type='TRIANGULATE')

this works wonders, you can for example create a few spheres select them and test it… now I wrap that in Operator and add a button for it:

import bpy

class SimpleOperator(bpy.types.Operator):
    """Tooltip"""
    bl_idname = "object.simple_operator"
    bl_label = "Simple Object Operator"

    @classmethod
    def poll(cls, context):
        return context.active_object is not None

    def execute(self, context):
        context = bpy.context
        scene = context.scene

        selected_objs = context.selected_objects[:] #copy just in case

        print("\nstarting")
        for obj in selected_objs:
            bpy.ops.object.select_all(action='DESELECT')
            obj.select = True
            scene.objects.active = obj
            print("current",obj.name,"selected",context.selected_objects[0].name,"active",scene.objects.active.name)
            bpy.ops.object.shade_smooth()
            bpy.ops.object.modifier_add(type='TRIANGULATE')
        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 = "object"

    def draw(self, context):
        self.layout.operator("object.simple_operator")


def register():
    bpy.utils.register_class(SimpleOperator)
    bpy.utils.register_class(LayoutDemoPanel)


def unregister():
    bpy.utils.unregister_class(SimpleOperator)
    bpy.utils.unregister_class(LayoutDemoPanel)

if __name__ == "__main__":
    register()

So I now select the objects and click that button, and it doesn’t work anymore… but the weird part is that bpy.ops.object.shade_smooth() is working, but bpy.ops.object.modifier_add() doesn’t, it seems to be ignoring the selection/active and all modifiers are added to one of the selected objects.
Am I doing something wrong there?

OK, after further investigation it seems what’s causing the problem is the panel, but I still have no idea of what’s going on. It can’t be that context var since it’s overridden right away.

Update: I couldn’t find a solution to that, but I’ve tried a workaround and I’m facing more problems now: Script crashes Blender - Is there a way to somehow bypass the hidden states set by UI widgets?

I love it when running in text editor and as a operator gives different results. You think you got it working and then you have to figure out why it doesn’t work.

See if this works

import bpy

class SimpleOperator(bpy.types.Operator):
	"""Tooltip"""
	bl_idname = "object.simple_operator"
	bl_label = "Simple Object Operator"

	@classmethod
	def poll(cls, context):
		return context.active_object is not None

	def execute(self, context):
		context = bpy.context
		scene = context.scene

		selected_objs = context.selected_objects[:] #copy just in case

		print("\nstarting")
		for obj in selected_objs:
					
			bpy.ops.object.select_all(action='DESELECT')
			obj.select = True
			scene.objects.active = obj
			print("current",obj.name,"selected",context.selected_objects[0].name,"active",scene.objects.active.name) 
			bpy.ops.object.shade_smooth()

			for window in bpy.context.window_manager.windows:
				screen = window.screen

				for area in screen.areas:
					if area.type == 'VIEW_3D':
						override = {'window': window, 'screen': screen, 'area': area}
						bpy.ops.object.modifier_add(override, type='TRIANGULATE')
						break

						
		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 = "object"

	def draw(self, context):
		self.layout.operator("object.simple_operator")


def register():
	bpy.utils.register_class(SimpleOperator)
	bpy.utils.register_class(LayoutDemoPanel)


def unregister():
	bpy.utils.unregister_class(SimpleOperator)
	bpy.utils.unregister_class(LayoutDemoPanel)

if __name__ == "__main__":
	register()

Yes, it is very frustrating. The code works, but unfortunately the other problem in the linked thread persists. I still couldn’t find a way to run bpy.ops.mesh.customdata_custom_splitnormals_clear() from an operator without crashing Blender, and it’s very slow to iterate with Blender crashing all the time. I just need a convenient way to somehow run the python functions without breaking them, but that’s been proving to be a monumental challenge to the point I’m honestly considering giving up altogether :worried:. But thank you for your reply and snippet, it helps to see that people care :wink:.