Solved Gray Out Custom UI Bitmap Button?

Hi,

Is there a way I can gray out a button that doesn't need a specific condition?
You can see an illustration of the problem here:
https://www.dropbox.com/s/rfw2nw8o298r0ej/c4d162_python_gray_out_custom_gui_button.jpg?dl=0

I used the SetToggleState(set), BITMAPBUTTON_ICONID1 and BITMAPBUTTON_ICONID2 but I am having a problem on implementing it.

Here is the code so far:

import c4d
from c4d import bitmaps, documents, gui, plugins, threading, utils

PLUGIN_ID   = 1011328

class MyDialog(gui.GeDialog):

    def CreateLayout(self):

        self.SetTitle('Colorizer')
        self.doc = c4d.documents.GetActiveDocument()
        
        # Prepare a red bitmap for the button.
        w = 50
        h = 50

        # BitmapButton configuration
        bcBitmapButton = c4d.BaseContainer()
        bcBitmapButton[c4d.BITMAPBUTTON_BUTTON] = True
        bcBitmapButton[c4d.BITMAPBUTTON_ICONID1] = c4d.Ocube
        bcBitmapButton[c4d.BITMAPBUTTON_ICONID2] = c4d.Oplane # should be the greyed out Ocube

        buttonId = 2000
        _bitmapButton = self.AddCustomGui(buttonId, c4d.CUSTOMGUI_BITMAPBUTTON, "", c4d.BFH_CENTER|c4d.BFV_CENTER, w, h, bcBitmapButton)

        icon = c4d.bitmaps.InitResourceBitmap(c4d.Ocube)
        _bitmapButton.SetImage(icon, True)

        if len(doc.GetActiveObjects())>0: 
            _bitmapButton.SetToggleState(0)
        else:
            _bitmapButton.SetToggleState(1)

        return True

    def Command(self, id, msg):
        
        if id==2000 : 
            print "Create Cube"

        return True

class MyMenuPlugin(plugins.CommandData):

    dialog = None
    def Execute(self, doc):
    # create the dialog
       if self.dialog is None:
          self.dialog = MyDialog()

       return self.dialog.Open(dlgtype=c4d.DLG_TYPE_ASYNC, pluginid=PLUGIN_ID, defaultw=200, defaulth=150, xpos=-1, ypos=-1)

    def RestoreLayout(self, sec_ref):
    # manage the dialog
       if self.dialog is None:
          self.dialog = MyDialog()
       return self.dialog.Restore(pluginid=PLUGIN_ID, secret=sec_ref)

if __name__ == "__main__":

    okyn = plugins.RegisterCommandPlugin(PLUGIN_ID, "Cubey",0, None, "Cubey initialized", MyMenuPlugin())
    if (okyn):
        print "Cubey initialized"

Thank you for looking at my problem

The correct way would be to have a timer or to forward the GetState method from the CommandData to the GeDialog. And in this method call GeDialog.Enable(buttonId, False).

Let me know if it fulfills your needs.
Cheers,
Maxime.

@m_adam

Thanks for the response.

RE: And in this method call GeDialog.Enable(buttonId, False).
This was clear to me. I was able to dim it down.

But how do I implement this one:
The correct way would be to have a timer or to forward the GetState method from the CommandData to the GeDialog.

Hi, sorry I totally forget about EVMSG_CHANGE which is called when anything in the scene changed.

Side note doesn't store the current active document in a class variable since the active document could change while the dialog is opened, so the preferred way is to retrieve it each time (I've done it through a python property).

class MyDialog(c4d.gui.GeDialog):

    buttonId = 2000

    @property
    def doc(self):
        return c4d.documents.GetActiveDocument()

    def CreateLayout(self):

        self.SetTitle('Colorizer')

        # Prepare a red bitmap for the button.
        w = 50
        h = 50

        # BitmapButton configuration
        bcBitmapButton = c4d.BaseContainer()
        bcBitmapButton[c4d.BITMAPBUTTON_BUTTON] = True
        bcBitmapButton[c4d.BITMAPBUTTON_ICONID1] = c4d.Ocube
        bcBitmapButton[c4d.BITMAPBUTTON_ICONID2] = c4d.Oplane # should be the greyed out Ocube
        
        _bitmapButton = self.AddCustomGui(self.buttonId, c4d.CUSTOMGUI_BITMAPBUTTON, "", c4d.BFH_CENTER|c4d.BFV_CENTER, w, h, bcBitmapButton)

        icon = c4d.bitmaps.InitResourceBitmap(c4d.Ocube)
        _bitmapButton.SetImage(icon, True)
        return True
    
    def InitValues(self):
        self.SetEnableCheck()
        return True

    def SetEnableCheck(self):
        if len(self.doc.GetActiveObjects(c4d.GETACTIVEOBJECTFLAGS_NONE))>0: 
            self.Enable(self.buttonId, True)
        else:
            self.Enable(self.buttonId, False)

    def Command(self, id, msg):
        if id==2000 : 
            print "Create Cube"
        return True
    
    def CoreMessage(self, id, data):
        if id == c4d.EVMSG_CHANGE:
            self.SetEnableCheck()
        return True

Cheers,
Maxime.

@m_adam Side question: Is this really how it's done in general?

On every EVMSG_CHANGE (which happens often) the dialog checks the enabling status for this button by looking up GetActiveObjects() which returns a list. So, the system had to assemble a list of active objects which may have hundreds of entries out of thousands of living objects in the scene.

Now there are hundreds of icons in my layout, and a good deal of them have a functionality like this. Being CommandData objects, they cannot share the result of GetActiveObjects() (like it would be possible in a dialog), so for each of them, this overhead is created just to determine whether something is selected?

I know that we measure processor speed in billions of executions per second, but doing this on EVMSG_CHANGE still seems incredibly wasteful. Maybe my understanding of the overhead is wrong and we could perform much more stuff during an EVMSG_CHANGE than I currently am comfortable with?

@m_adam

Thanks for the response. It works as expected. It took me a while to get the code.
I was thinking how is the id == c4d.EVMSG_CHANGE where the id buttonId = 2000. Then it hit me, the id in the Command() and CoreMessage is actually different ids .

Hahaha. Anyhow thanks!

@Cairyn

Just out of curiosity. I'm assuming you are not using the workflow above, may I ask how would you have done it?

@bentraje I do not have the issue at the moment; my last plugins were all independent of selections and therefore always active. When I program for myself, I normally skip the niceties and test the requirements after clicking the button; I can always stop execution after that.

Currently I am working on writing a Python tutorial series - the Python script templates have a State() function but don't tell me when they are called. Maybe that actually IS during an EVMSG_CHANGE.

Given the internal messaging system of C4D, it seems logical. It just is a lot of stuff to do during that timeframe.

@Cairyn sorry for the delay, I missed your reply.

React to EVMSG_CHANGE is used a lot (I agree with you, performance-wise it's not the best idea, but until everything is moved to the new core, this is like that).
So as long as you don't do something fancy there, this is not a big issue, and it's also what all render engine do to tracks change into the current document to refresh their IPR.

GetState is called at each UI redraw so it's called multiple time per frames each frame (so I would say it's even more critical than EVMSG_CHANGE, as you can really slow down Cinema 4D quickly)

Unfortunately, currently this is the only way.
Cheers,
Maxime.

@Cairyn

Gotcha. Thanks for sharing!

@m_adam
". . . but until everything is moved to the new core"

That's a nice tease there :)