1
$\begingroup$

Is it possible to change the content of a custom panel based on user input?

Let's say I have a panel with an explanation text (Label) and a button which starts the function. As soon as the user pressed the button, I would like to change the text and replace the button with another one which executes some code. When this new button is pressed and the function "behind" is finished, I would like to switch back to the initial text and button.

Is this possible?

import bpy

class ButonOperator(bpy.types.Operator):
    bl_idname = "scene.button_operator"
    bl_label = "Add-On Function"           
    
    def execute(self, context):
        # Start the Add-on. Here I would like to swith the content of the label below 
        # ("Sample text") and replace the "Add-On Function" button with a "START" button.
        # This should invoke another operator and when that is finished I would like
        # to restore the initial state.
        pass
        return {'FINISHED'}

class TestPanel(bpy.types.Panel):
    bl_label = "Test Panel"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    
    def draw(self, context):
        self.layout.label(text="Sample Text")
        self.layout.separator_spacer
        self.layout.operator("scene.button_operator")
        
def register():
    bpy.utils.register_class(ButonOperator)       
    bpy.utils.register_class(TestPanel)

def unregister():
    bpy.utils.register_class(ButonOperator)       
    bpy.utils.register_class(TestPanel)
    
if __name__ == "__main__":
    register()
$\endgroup$
1
  • 2
    $\begingroup$ Short answer yes it is possible. eg self.layout.label(text=some_object.some_property_i_set_with_operator) $\endgroup$
    – batFINGER
    Commented Jun 6, 2021 at 15:46

1 Answer 1

2
$\begingroup$

Here is a variation of your script that does the simple two state version of what you describe. An explanation follows.

import bpy
from bpy.types import Operator, Panel
from enum import Enum

class Choice(Enum):
    Uninitialized = -1
    One = 1
    Two = 2

class ButtonMachine:
    def __init__(self, initialState = Choice.Uninitialized):
        self.state = initialState

machine = ButtonMachine(Choice.One)

class MISC_OT_ButonOneOperator(Operator):
    bl_idname = "scene.buttonone_operator"
    bl_label = "Add-On Function 1"
    
    def execute(self, context):
        machine.state = Choice.Two
        self.report({'INFO'}, "one")
        return {'FINISHED'}

class MISC_OT_ButonTwoOperator(Operator):
    bl_idname = "scene.buttontwo_operator"
    bl_label = "Add-On Function 2"
    
    def execute(self, context):
        machine.state = Choice.One
        self.report({'INFO'}, "two")
        return {'FINISHED'}

class MISC_PT_TestPanel(Panel):
    bl_label = "Test Panel"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
            
    def draw(self, context):
        if machine.state == Choice.One:
            self.layout.label(text="- 1 -")
            self.layout.separator_spacer
            self.layout.operator("scene.buttonone_operator")
        elif machine.state == Choice.Two:
            self.layout.label(text="- 2 -")
            self.layout.separator_spacer
            self.layout.operator("scene.buttontwo_operator")
        else:
            self.layout.label(text="lost")
        
def register():
    bpy.utils.register_class(MISC_OT_ButonOneOperator)       
    bpy.utils.register_class(MISC_OT_ButonTwoOperator)       
    bpy.utils.register_class(MISC_PT_TestPanel)

def unregister():
    bpy.utils.unregister_class(MISC_OT_ButonOneOperator)       
    bpy.utils.unregister_class(MISC_OT_ButonTwoOperator)       
    bpy.utils.unregister_class(MISC_PT_TestPanel)
    
if __name__ == "__main__":
    register()

Explanation

What you are asking about is often implemented using a state machine pattern. When the system is in a given state, then an action can transition it to another state. A global variable is used to keep track of the state the system is in. When actions are performed that change the state of the machine, this fact is recorded in the global variable.

Since the system can only be in a finite number of states, an Enum is often used to record the state and the state machine is implemented as an if statement with a number of branches, one for each state, and a final branch in case there is an error.

I've implemented a two-state machine. ButtonOne is the state where the first button should be available and ButtonTwo is for the second. Each button changes the state when they're pressed.

The actual state machine is the if statement in the Panel draw function.

There are more sophisticated ways to implement this, but this version shows how the system works very clearly. If I had a lot of states, I would use a dictionary with one entry for each state containing a tuple. In the example, the tuple would contain the text string for the label, the function that goes with the state, and the value of the Enum for the next state.

Alternate Implementation

Here's a version using a dictionary and a transition class.

import bpy
from bpy.types import Operator, Panel
from enum import Enum


class Choice(Enum):
    Uninitialized = -1
    One = 0
    Two = 1
    Three = 3

class ButtonMachine:
    def __init__(self, initialState = Choice.Uninitialized):
        self.state = initialState

def nextState(state):
    currentState = states.get(state)
    if currentState:
        return currentState.nextState
    else:
        return None

class Transition:
    def __init__(self, newText = "None", newOperator = "None", newState = Choice.Uninitialized):
        self.displayText = newText
        self.stateOperator = newOperator
        self.nextState = newState

class MISC_OT_ButonOneOperator(Operator):
    bl_idname = "scene.buttonone_operator"
    bl_label = "Add-On Function 1"
    
    def execute(self, context):
        machine.state = nextState(machine.state)
        return {'FINISHED'}

class MISC_OT_ButonTwoOperator(Operator):
    bl_idname = "scene.buttontwo_operator"
    bl_label = "Add-On Function 2"
    
    def execute(self, context):
        machine.state = nextState(machine.state)
        return {'FINISHED'}

class MISC_OT_ButonThreeOperator(Operator):
    bl_idname = "scene.buttonthree_operator"
    bl_label = "Add-On Function 2"
    
    def execute(self, context):
        machine.state = nextState(machine.state)
        return {'FINISHED'}

machine = ButtonMachine(Choice.One)

states = {
    Choice.One : Transition("- 1 -", "scene.buttonone_operator", Choice.Two),
    Choice.Two : Transition("- 2 -", "scene.buttontwo_operator", Choice.Three),
    Choice.Three : Transition("- 3 -", "scene.buttonthree_operator", Choice.One),
}

class MISC_PT_TestPanel(Panel):
    bl_label = "Test Panel"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
            
    def draw(self, context):
        currentState = states.get(machine.state)
        if currentState:
            self.layout.label(text=currentState.displayText)
            self.layout.separator_spacer
            self.layout.operator(currentState.stateOperator)
            nextState = currentState.nextState
        else:
            self.layout.label(text="lost")
        
def register():
    bpy.utils.register_class(MISC_OT_ButonOneOperator)       
    bpy.utils.register_class(MISC_OT_ButonTwoOperator)       
    bpy.utils.register_class(MISC_OT_ButonThreeOperator)       
    bpy.utils.register_class(MISC_PT_TestPanel)

def unregister():
    bpy.utils.unregister_class(MISC_OT_ButonOneOperator)       
    bpy.utils.unregister_class(MISC_OT_ButonTwoOperator)       
    bpy.utils.unregister_class(MISC_OT_ButonThreeOperator)       
    bpy.utils.unregister_class(MISC_PT_TestPanel)
    
if __name__ == "__main__":
    register()
$\endgroup$

You must log in to answer this question.

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