[SOLVED] Update function (4 Bool/EnumProp) HowTo?

Hey there,
I know this has probably been asked over & over already, but I just couldn’t quite wrap my head around any of what the search spit out.

You may skip the lengthy explanation in the next paragraph & look right at the code below, if you like:).

So I’m writing (well, tryin’ 2) a little script needed 4 a rig I’m working on. For now I’m just tryin’ 2 get a simple prototype working.
It’s meant 2 draw a panel with a checkbox & a (non-dynamic) popup-menu, giving 3 choices. I’ve figured out how to do this on the UI-side of things, so I have my BoolProperty and my checkbox in the panel as well as my EnumProperty and the corresponding popup-menu.
I’ve also duct-taped together an operator which is meant 2 hide ceartain objects if the checkbox gets checked/the Bool==True.
What I think I need now is an update function, such that toggling the checkbox will immediately run the operator and hide/reveal the objects.

Here’s what I have (forgive me if it’s messy):


import bpy

from bpy.props import *


Ob = [bpy.data.objects["Cube"], bpy.data.objects["Plane"]]



#def update_EyeLeftOptions(self, context):
#     print(self,context)


def initSceneProperties(sce):
       
    
    bpy.types.Scene.EyeLeftClosed=BoolProperty(
        name="Eye Closed",
        description="Closes Eye",
        default = False)
#        update=)
#    sce['EyeLeftClosed'] = False


    
    bpy.types.Scene.EyeLeftOptions=EnumProperty(
        items=[('1', 'Default Behaviour', 'Deforms Upper Boundary Of Eyeballs'), 
               ('2', 'Eyelid Visible', 'Shows Upper Eyelid'),
               ('3', 'Angry Shape', 'Static Angry Eyeshape')],
        name="")
#        update=update_EyeLeftOptions)
    sce['EyeLeftOptions'] = 1
      
            
initSceneProperties(bpy.context.scene)


       
def hide_meshes(hide):
    
    for ob in Ob:
        if ob.type!='MESH':
            continue
        ob.hide=hide
        ob.hide_render=hide
   
        
        
class hideOperator(bpy.types.Operator):
    '''Tooltip'''
    bl_idname="object.hide_operator"
    bl_label="Hide"
    
    if bpy.context.scene.EyeLeftClosed:
        hide_meshes(True)
    else:
        hide_meshes(False)     

bpy.utils.register_class(hideOperator)



class showFaceExtrasEyes(bpy.types.Panel):
    bl_label = "Face Extras: Eyes"
    #bl_idname = "myPanelID"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
 
    def draw(self,context):
        layout = self.layout
        sce=context.scene
        layout.label("Eye Left:")
        layout.prop(sce, 'EyeLeftClosed')
        layout.prop(sce, 'EyeLeftOptions')

bpy.utils.register_class(showFaceExtrasEyes)

#    Registration
bpy.utils.register_module(__name__)


Any help would be appreciated.

greetings,
Kologe

Here’s a clean approach:

import bpy
from bpy.props import *

mesh_obs = ("Cube", "Plane")

class VIEW3D_PT_face_extras_eyes(bpy.types.Panel):
    bl_idname = "view3d.face_extras_eyes"
    bl_label = "Face Extras: Eyes"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
 
    def draw(self, context):
        layout = self.layout
        sce = context.scene
        fee = sce.face_extras_eyes
        
        layout.label("Left eye:")
        box = layout.box()
        box.prop(fee, 'eye_left_closed', text="Closed")
        box.prop(fee, 'eye_left_options', text="")
        

def update_eye_left_closed(self, context):
    hide = self.eye_left_closed
    
    for ob_name in mesh_obs:
        ob = context.scene.objects.get(ob_name)
        if ob is not None:
            ob.hide = hide
            ob.hide_render = hide
        
    
def update_eye_left_options(self, context):
    print("Left eye option:", self.eye_left_options)


class FaceExtrasEyes(bpy.types.PropertyGroup):
    eye_left_closed = BoolProperty(
        name="Eye Closed",
        description="Closes Eye",
        default=False,
        update=update_eye_left_closed
    )
    
    eye_left_options = EnumProperty(
        name="Left Eye Options",
        items=(('1', 'Default Behaviour', 'Deforms Upper Boundary Of Eyeballs'), 
               ('2', 'Eyelid Visible', 'Shows Upper Eyelid'),
               ('3', 'Angry Shape', 'Static Angry Eyeshape')),
        default='1',
        update=update_eye_left_options
    )


def register():
    bpy.utils.register_module(__name__)
    bpy.types.Scene.face_extras_eyes = PointerProperty(type=FaceExtrasEyes)
    
    
def unregister():
    bpy.utils.unregister_module(__name__)
    del bpy.types.Scene.face_extras_eyes
    
    
if __name__ == '__main__':
    register()

Thx very much. Works like a charm.
I’m afraid I have some little follow-up questions though. Thought I’d figure this out myself, but looks like I don’t get it.

So… afaik, each of the items in the EnumProp has/is a key, right? And this key is (I guess) basically an integer from 0 through 2 in this case, right?
Like key[0] is the tuple
(‘1’, ‘Default Behaviour’, ‘Deforms …’),
right? (“key[0]” may be syntactically wrong.)

Anyway, what I need the “update_eye_left_options”-function 2 do is somewhat similar 2 what the “update_eye_left_closed”-function does in your code:
Say there were 3 more objects in the scene, say ‘Cone’, ‘Icosphere’ & ‘Torus’.
Now if key[0] (well the default option, you get the idea) was picked in the popup-menu, the “update_eye_left_options”-function would (if needed) reveal ‘Cone’, but hide ‘Icosphere’ & ‘Torus’.
If the 2nd option was chosen, it’d reveal ‘Icosphere’, but hide ‘Cone’ & ‘Torus’ etc. You get the idea.

Btw., as I’ll need half a dozen EnumProps/popup-menus, which would all do sth. comparable (like hiding/unhiding stuff), just on other objects/ sets of objects, it’d be probably much cleaner in my case, 2 externalize the whole hiding into a dedicated, generic “hide_meshes”-function, which would take a list of objects to be hidden as an arguement & would get called by the update-functions (with each of these passing the corresponding list to it, depending on the option chosen).

This is mostly pseudo-code or some crude wannabe-python:


import bpy
from bpy.props import *

mesh_obs = ("Cube", "Plane")
mesh_obs2 = [("Cone"), ("Icosphere"), ("Torus")]

class hide_meshes(bpy.types.Operator):
    bl_idname="mesh.hide"
    bl_label="Hide Meshes"
  

def hide_meshes(self, context, meshes_2B_hidden, hide):
    for ob_name in meshes_2B_hidden:
        ob = context.scene.objects.get(ob_name)
        if ob is not None:
            ob.hide = hide
            ob.hide_render = hide





class VIEW3D_PT_face_extras_eyes(bpy.types.Panel):
   #...your code as seen above

        

def update_eye_left_closed(self, context):
    meshes_2B_hidden=mesh_obs
    
   hide=self.eye_left_closed
   hide_meshes(meshes_2B_hidden, hide)

        
    
def update_eye_left_options(self, context):

       for ob_name in mesh_obs2:
        ob = context.scene.objects.get(ob_name)
        if ob is not None:
            for i in enumerate(mesh_obs2):
                if i in mesh_obs2[i] == self.eye_left_options.setkey:
                    meshes_2B_hidden=mesh_obs2[i]
                    hide=False
                    hide_meshes(meshes_2B_hidden, hide)
                else:
                    meshes_2B_hidden=mesh_obs2[i]
                    hide=True
                    hide_meshes(meshes_2B_hidden, hide)




class FaceExtrasEyes(bpy.types.PropertyGroup):
   #...your code as seen above

    )


def register():
    bpy.utils.register_module(__name__)
    bpy.types.Scene.face_extras_eyes = PointerProperty(type=FaceExtrasEyes)
    
    
def unregister():
    bpy.utils.unregister_module(__name__)
    del bpy.types.Scene.face_extras_eyes
    
    
if __name__ == '__main__':
    register()

Well, one more thing (maybe two): Is there such a thing like a ‘switch’-statement in python (like in say C or Java), for the sake of not having 2 stack if-statements when tryin’ 2 check many possibilities?
Blenders syntax-highliting-thingie didn’t seem 2 recognize the word.
Just thought it might be usefull when trying 2 figure out which out of many EnumProperty-Options was actually picked.
Though if it actually works out anyhow similar to how I guess it does (the whole thing with every item having an integer key somehow accessible which might be simply compared 2 a list index or sth.), it might not be needed.

Last qestion: Is there a way 2 grey out an option from such popup menu? Like it still shows & the item is still there (not like it got removed from the EnumProp as in a dynamic popup-menu like here), but one can’t select it.
I’ll need 2 popup-menus in one of my panels, giving the exact same (16) choices, though it wouldn’t make any sense 2 pick the same option on both simultanously.
Thus it’d be nice if whatever option was chosen in one of 'em was greyed out in the other.
Maybe there’s some sort of @classmethod/poll-thingy which could be implemented there.

So that’s all. Sorry for the wall of text & the crude pseudo-code. Hope you (or any other friendly, helpful person around for that matter) can make sense of it.
Feel free 2 ask if I was unclear on sth.

greetings & thx in advance,
Kologe

OK, so I guess I figured the first bit out. How to externalize the “hiding stuff”-part from the “update_eye_left_closed”-function (as seen in CoDEmanX’ skript), that is.

The next paragraph may be skipped, it’s pretty much for the records only.

For testing purposes I included the right-eye-related stuff into the “Face Extras: Eyes”-Panel, so there’s another BoolProp/checkbox the update-function of which will call the “hideObjects”-function I wrote passing it a different list of objects 2 B hidden. Seems to work out fine.

The code currently looks like this:

import bpy
from bpy.props import *

mesh_obs1 = ("Cube", "Plane")
mesh_obs1a = ("Cube.001", "Plane.001")
mesh_obs2 = [("Cone"), ("Icosphere"), ("Torus")]
obs2hide = []



def hideObjects(context, obs2hide, hide):
    
    for ob_name in obs2hide:
        ob = context.scene.objects.get(ob_name)
        if ob is not None:
            ob.hide = hide
            ob.hide_render = hide

    return {'FINISHED'}    
    


class VIEW3D_PT_face_extras_eyes(bpy.types.Panel):
    bl_idname = "view3d.face_extras_eyes"
    bl_label = "Face Extras: Eyes"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
 
    def draw(self, context):
        layout = self.layout
        sce = context.scene
        fee = sce.face_extras_eyes
        
        layout.label("Right eye:")
        box = layout.box()
        box.prop(fee, 'eye_right_closed', text="Closed")
        box.prop(fee, 'eye_right_options', text="")
        
        layout.label("Left eye:")
        box = layout.box()
        box.prop(fee, 'eye_left_closed', text="Closed")
        box.prop(fee, 'eye_left_options', text="")
             
        
def update_eye_right_closed(self, context):
    
    obs2hide=mesh_obs1a
    hide=self.eye_right_closed
    hideObjects(context, obs2hide, hide)

def update_eye_left_closed(self, context):
    
    obs2hide=mesh_obs1
    hide=self.eye_left_closed
    hideObjects(context, obs2hide, hide)
#    bpy.ops.hide.meshes.meshes2hide=mesh_obs1
    

def update_eye_right_options(self, context):
    print("Right eye option:", self.eye_right_options)        
    
def update_eye_left_options(self, context):
    print("Left eye option:", self.eye_left_options)



class FaceExtrasEyes(bpy.types.PropertyGroup):
    
    eye_right_closed = BoolProperty(
        name="Eye Closed",
        description="Closes Eye",
        default=False,
        update=update_eye_right_closed
    )
    
    eye_left_closed = BoolProperty(
        name="Eye Closed",
        description="Closes Eye",
        default=False,
        update=update_eye_left_closed
    )
    
    eye_right_options = EnumProperty(
        name="Right Eye Options",
        items=(('1', 'Default Behaviour', 'Deforms Upper Boundary Of Eyeballs'), 
               ('2', 'Eyelid Visible', 'Shows Upper Eyelid'),
               ('3', 'Angry Shape', 'Static Angry Eyeshape')),
        default='1',
        update=update_eye_right_options
    )
    
    eye_left_options = EnumProperty(
        name="Left Eye Options",
        items=(('1', 'Default Behaviour', 'Deforms Upper Boundary Of Eyeballs'), 
               ('2', 'Eyelid Visible', 'Shows Upper Eyelid'),
               ('3', 'Angry Shape', 'Static Angry Eyeshape')),
        default='1',
        update=update_eye_left_options
    )


def register():
    bpy.utils.register_module(__name__)
    bpy.types.Scene.face_extras_eyes = PointerProperty(type=FaceExtrasEyes)
    
    
def unregister():
    bpy.utils.unregister_module(__name__)
    del bpy.types.Scene.face_extras_eyes
    
    
if __name__ == '__main__':
    register()

From studying the API-docs a little closer, I think I now realized there is no simpler way 2 check which item of an EnumProp is currently chosen by the corresponding popup-menu other than checking which identifier-string gets passed by the update-function (though right now, it doesn’t get passed anywhere but printed 2 the console).

This kinda brings me back to the question wheather there’s sth. like a “switch”-statement in python. Or any other way of checking for 16 different items in an EnumProp more elegantly than stacking 16 if-statements.

Last time’s last qestion remains either 4 now:
Is there a way 2 grey out an option from such popup menu? Like it still shows & the item is still there (not like it got removed from the EnumProp as in a dynamic popup-menu like here), but one can’t select it.
I’ll need 2 popup-menus in one of my panels, giving the exact same (16) choices, though it wouldn’t make any sense 2 pick the same option on both simultanously.
Thus it’d be nice if whatever option was chosen in one of 'em was greyed out in the other.
Maybe there’s some sort of @classmethod/poll-thingy which could be implemented there.

Though I guess I’ll figure the rest of this out myself by and by (maybe excluding the stuff mentioned in the last paragraph, but it’s not super-crucial), I’d appreciate any tips still.

greetings, Kologe

Every enum prop item HAS a key, which can be any string. It can’t be a number.

“key[0]” is a string, hence it could be used as key. But I discourage the use brackets etc. which can be misleading.

Is there such a thing like a ‘switch’-statement in python

No, and you don’t need it. Use variables, functions and if/else constructs to achieve the exact same thing.

Especially in your case, there is no need at all. The EnumProperty instance gives you the key, you can directly use that in your condition test. If you need the name or description, create a dictionary from the items (I would store the triplet tuple in global scope). Example:


import bpy

eye_left_options_items = (
    ('option_a', "1st option", "First option description"),
    ('option_b', "2nd option", "Second option description"),
    ('option_c', "3rd option", "Third option description"),
)

# Create lookup dictionary for enum prop identifiers to retrieve names efficiently
eye_left_options_dict = {identifier: name for identifier, name, description in eye_left_options_items}

def update_eye_left_options(self, context):
    print("Update: %s (%s)" % (self.enum, eye_left_options_dict[self.enum]))
    
    if self.enum == 'option_a':
        col = (0.4, 0.0, 0.0)
    elif self.enum == 'option_b':
        col = (0.0, 0.2, 0.0)
    else:
        col = (0.0, 0.0, 0.3)
        
    context.user_preferences.themes[0].view_3d.space.gradients.high_gradient = col
    
bpy.types.Scene.enum = bpy.props.EnumProperty(
    items=eye_left_options_items,
    update=update_eye_left_options)

bpy.types.VIEW3D_HT_header.prepend(lambda self, context: self.layout.prop(context.scene, "enum", text=""))


No, you can’t gray out enum items. You can only replace the static items by a callback and filter out items entirely.
If you really wanna gray something out, you’ll have to use a menu, and let it draw opertors (this can be a helper operator to set a property, and the poll classmethod can be used to gray out elements)

Thx again, CoDEmanX.

Guess I’ll do without the greying out. “Real” menus require too much space for my taste (for what I need all of this 2 be like). So I’ll just leave it up to the animator not to do nonsensical stuff there. The rig’s not very likeley 2 ever get touched by anyone but myself, anyway (though I might still put it on BlendSwap when finished).

I’ve just stumbled upon another little problem, though:
Trying 2 “finalize” (so to speak) my script, I got global vars defined in the beginning, like so:


#blabla...
mesh_eye_openR = (
                "Head|Face|Eye_R|Eye_Right",
                "Head|Face|Eye_R|Pupil_Right", 
                "Head|Face|Eye_R|Pupil_Spec_Right"
                )
mesh_eye_closedR = ("Head|Face|Eye_R|Eye_R_Closed")

#blabla...

Further down in the code, those vars get used by e.g. the “eye_right_update”-function, 2 define which objects are 2 be hidden/revealed by the “hideObjects”-function, like so:


#blabla...
def update_eye_right_closed(self, context):
    
#   hides cards showing open eye
    obs2hide=mesh_eye_openR
    hide=self.eye_right_closed
    hideObjects(context, obs2hide, hide)

#reveals card showing closed eye
    obs2hide=mesh_eye_closedR
    hide= not self.eye_right_closed
    hideObjects(context, obs2hide, hide)

#blabla...

In practice, this doesn’t work. Well, the first part does, but I suppose it fails at the line | obs2hide=mesh_eye_closedR |.
Seems like a list can’t contain less than 2 entries or somethin’.
I could of course workaround this by, say, putting maybe an empty or sth. else not actually affecting anything in my scene/rig into the “mesh_eye_closedR”-list, just 2 make it work somehow.

Though I assume there must be sth. better to do. Maybe using some other datastructure would fix this, I assume.

Anyway, I’d again appreciate any hints again, though if my ignorance of python-basics starts 2 get too annoying, I’ll figure out myself or in worst case do with the silly workaround described above.:wink:

greetings, Kologe