SOLVED Questions about Bake Texture tag & Python

Hello,

I have some questions concerning the Bake Texture tag:

1 - Is there command that allow to open the bake preview window

bake_texture_tag_open_win.jpg

2 - Is it possible to detect when the bake render is finished

I imagine that there is no way to display the small image preview inside description resource via python.

Thank you.

Hi,

  1. Not really in Python, since it is inside a context menu of an gadget within a description resource.
  2. You might want to check c4d.CheckIsRunning(c4d.CHECKISRUNNING_BAKING), but I am not sure if it does work for all scenarios of baking.

Cheers
zipit

@zipit said in Questions about Bake Texture tag & Python:

c4d.CheckIsRunning(c4d.CHECKISRUNNING_BAKING)

Hi,
I tried with c4d.CheckIsRunning(c4d.CHECKISRUNNING_BAKING) but this always returning false.

@zipit

Hi,
Since the c4d.CheckIsRunning(c4d.CHECKISRUNNING_BAKING) doesn't work, I'm trying to use an alternative method to check if baking is running.

The problem is that the print() result work but the static text field (description resource) of my object dosen't change.

Also the GetDEnabling() don't react to my custom CheckIsRunning condition or the CheckIsRunning condition doesn't send the change.

see code below:

def Init(self, op):
    self.PyCObject_id = None
    self.hide_bake_button = None
    self.hide_stop_bake_button = c4d.MYOBJECT_BAKETEXTURE_STOP

def Message(self, node, type, data):
        
    if type == c4d.MSG_DESCRIPTION_COMMAND: 
        id = data['id'][0].id
          
        # My custom bake texture button   
        if id == c4d.MYOBJECT_BAKETEXTURE_BAKE:
            self.hide_bake_button = c4d.MYOBJECT_BAKETEXTURE_BAKE

            # Bake texture function
            self.BakeTexture(node, c4d.BAKETEXTURE_BAKE)

        # My custom stop bake texture button 
        if id == c4d.MYOBJECT_BAKETEXTURE_STOP:
            self.hide_stop_bake_button = c4d.MYOBJECT_BAKETEXTURE_STOP
            self.hide_bake_button = None

            # Bake texture function
            self.BakeTexture(node, c4d.BAKETEXTURE_STOP)

    # Check if bake texture is running (my custom CheckIsRunning condition)
    if type == 14:  
        if data['type'] == 1001071:
            pydata= data['data']            
            pythonapi.PyCObject_AsVoidPtr.restype = c_void_p
            pythonapi.PyCObject_AsVoidPtr.argtypes = [py_object]
            PyCObject_id = pythonapi.PyCObject_AsVoidPtr(pydata)
            
            if self.PyCObject_id != PyCObject_id:
                self.PyCObject_id = PyCObject_id
                print("Bake texture is running")
                
                # issue with the following line
                node[c4d.MYOBJECT_BAKETEXTURE_STATICTEXT_STATE] = "Bake texture is running" # STATICTEXT description resource
                
            else:
                print("Bake texture is finished")

                # issue with the following lines 
                self.hide_bake_button = None
                node[c4d.MYOBJECT_BAKETEXTURE_STATICTEXT_STATE] = "Bake texture is running" # STATICTEXT description resource


def BakeTexture(self, op, event):
        obj = op.GetDown()

        tag = obj.GetTag(c4d.Tbaketexture)

        tag[c4d.BAKETEXTURE_FORMAT] = c4d.FILTER_HDR        
        tag[c4d.BAKETEXTURE_CHANNEL_REFLECTION] = True

        c4d.CallButton(tag, event)

        return tag

def GetDEnabling(self, node, id, t_data, flags, itemdesc):
        
    if id[0].id == self.hide_bake_button:
        return False

    if id[0].id == self.hide_stop_bake_button:
        return False

    return True

Hi,

you can't assign a value to a STATICTEXT description element as it is a static element as the name implies 😉 You have either to use the STRING element (here you can just assign a value) or overwrite NodeData.GetDDescription() to modify your description (of your STATICTEXT) dynamically.

Cannot say much about your GetDEnabling, since you are only checking some DescIDs there. I don't really get what is not working there?

Cheers
zipit

@zipit
I tried with all types (float, string, bool..) and that not works, nothing happens.

And about the GetDEnabling
for example in the condition :

if id == c4d.MYOBJECT_BAKETEXTURE_BAKE:
      self.hide_bake_button = c4d.MYOBJECT_BAKETEXTURE_BAKE

the GetDEnabling react and disable the bake button.

but in CheckIsRunning condition the GetDEnabling don't react and re-enable my custom bake:

if type == 14:  
    if data['type'] == 1001071:
        ...
        if self.PyCObject_id != PyCObject_id:
            self.PyCObject_id = PyCObject_id
            ...
        else:
            self.hide_bake_button = None # <<< here the bake button is not re-enabled

it is a bit complicated to explain the issue, if this is not clear I will try to create a full example and share it here.
sorry, and thank you for your time.

Problem solved by adding a global variable.

# Global variable
checkisrunning_baking = False
   
class MyClass(c4d.plugins.ObjectData):

    def Init(self, op):
        self.PyCObject_id = None
        self.hide_bake_button = None
        self.hide_stop_bake_button = c4d.HDRISG_BAKETEXTURE_STOP

    def Message(self, node, type, data):
        global checkisrunning_baking

        if type == c4d.MSG_DESCRIPTION_COMMAND: 
            id = data['id'][0].id
          
            # My custom bake texture button   
            if id == c4d.MYOBJECT_BAKETEXTURE_BAKE:
                # Bake texture function
                self.BakeTexture(node, c4d.BAKETEXTURE_BAKE)

            # My custom stop bake texture button 
            if id == c4d.HDRISG_BAKETEXTURE_STOP:

                # Bake texture function
                self.BakeTexture(node, c4d.BAKETEXTURE_STOP)

        # Check if bake texture is running
        if type == 14:  
            if data['type'] == 1001071:
                pydata= data['data']            
                pythonapi.PyCObject_AsVoidPtr.restype = c_void_p
                pythonapi.PyCObject_AsVoidPtr.argtypes = [py_object]
                PyCObject_id = pythonapi.PyCObject_AsVoidPtr(pydata)
            
                if self.PyCObject_id != PyCObject_id:
                    self.PyCObject_id = PyCObject_id
                    checkisrunning_baking = True
                
                else:
                    checkisrunning_baking = False
    
    def GetVirtualObjects(self, op, hierarchyhelp):
        global checkisrunning_baking

        ...

        self.hide_baketexture_bake = c4d.HDRISG_BAKETEXTURE_BAKE if checkisrunning_baking else None
        self.hide_baketexture_stop = c4d.HDRISG_BAKETEXTURE_STOP if not checkisrunning_baking else None

        baking_state = "Is running baking..." if checkisrunning_baking else ""
        op[c4d.HDRISG_BAKETEXTURE_STATICTEXT] = baking_state

        dirty = op.CheckCache(hierarchyhelp) or op.IsDirty(c4d.DIRTY_DATA)
        if dirty is False: return op.GetCache(hierarchyhelp)

    def BakeTexture(self, op, event):
            obj = op.GetDown()

            tag = obj.GetTag(c4d.Tbaketexture)

            tag[c4d.BAKETEXTURE_FORMAT] = c4d.FILTER_HDR        
            tag[c4d.BAKETEXTURE_CHANNEL_REFLECTION] = True

            c4d.CallButton(tag, event)

            return tag

    def GetDEnabling(self, node, id, t_data, flags, itemdesc):
        
        if id[0].id == self.hide_bake_button:
            return False

        if id[0].id == self.hide_stop_bake_button:
            return False

Hi @mfersaoui, using the Bake Take is not the way to go to do texture baking programmatically. Since as you discovered you have no control over it. And as you execute CallButton it forces you to do the baking operation from the main thread (that's why CheckIsRunning is failing).

So the correct way is to use c4d.utils.BakeTexture, you can find an example in the github repository: py-texture_baker_r18.
The preview can be retrieved with the BAKE_TEX_PREVIEW flag.

If you have any questions, let me know.
Cheers,
Maxime.

Hi @m_adam, Thank you I used py-texture_baker_r18 and I wanted to display an image preview inside the dialog, so I added a custom GUI to the dialog

bc = c4d.BaseContainer()
bc.SetBool(c4d.BITMAPBUTTON_BUTTON, False)
bc.SetLong(c4d.BITMAPBUTTON_BORDER, c4d.BORDER_THIN_IN)
self.myBitButton=self.AddCustomGui(self.BAKE_PREVIEW, c4d.CUSTOMGUI_BITMAPBUTTON, "", c4d.BFH_LEFT | c4d.BFV_TOP, 512, 256, bc)

And from the CoreMessage() when the baking is finished I save the baked bitmap and then I set it as Image to my custom GUI Button

bmp.Save(self.filename, self.fileformat, c4d.BaseContainer(), c4d.SAVEBIT_ALPHA)
self.myBitButton.SetImage(self.filename, False)

The problem is that my saved image is largest then the GUI button size. And I want to resize this image to fit my custom bitmap button, and without using an external python library.

Or is it possible to set the baked bitmap as image without saving it. and then resizing it to ensure that fit the bitmap button.

# this doesn't work
bmp = self.textureBakerThread.bakeBmp """ bmp = <Objet c4d.bitmaps.MultipassBitmap à 0x000000001BF75850>"""
self.myBitButton.SetImage(bmp, False)

Here is the full example:

class TextureBakerThread(c4d.threading.C4DThread):

    def __init__(self, doc, textags, texuvws, destuvws):
        ...

    def Begin(self):

        # Defines baking setting
        bakeData = c4d.BaseContainer()
        bakeData[c4d.BAKE_TEX_WIDTH] = 1024
        bakeData[c4d.BAKE_TEX_HEIGHT] = 512
        ...
        self.bakeData = bakeData

        # Initializes bake process
        bakeInfo = c4d.utils.InitBakeTexture(self.doc, self.textags, self.texuvws, self.destuvws, self.bakeData, self.Get())
        self.bakeDoc = bakeInfo[0]
        self.bakeError = bakeInfo[1]

        if self.bakeError != c4d.BAKE_TEX_ERR_NONE or self.bakeDoc is None:
            return False

        # Starts bake thread
        self.Start(c4d.THREADMODE_ASYNC, c4d.THREADPRIORITY_BELOW)

        return True

    def BakeTextureHook(self, info):
        ...

    def Main(self):
        ...

class TextureBakerDlg(c4d.gui.GeDialog, TextureBakerHelper):
    BUTTON_BAKE = 1000
    BUTTON_ABORT = 1001
    BAKE_PREVIEW = 1002

    aborted = False
    textureBakerThread = None
    infoText = None

    def CreateLayout(self):

        # Defines the title
        self.SetTitle("Texture Baker")

        # Creates 2 buttons for Bake / Abort button
        if self.GroupBegin(id=0, flags=c4d.BFH_SCALEFIT, rows=1, title="", cols=2, groupflags=0):
            self.AddButton(id=self.BUTTON_BAKE, flags=c4d.BFH_LEFT, initw=100, inith=25, name="Bake")
            self.AddButton(id=self.BUTTON_ABORT, flags=c4d.BFH_LEFT, initw=100, inith=25, name="Abort")
        self.GroupEnd()

        # Creates an image preview
        bc = c4d.BaseContainer()
        bc.SetBool(c4d.BITMAPBUTTON_BUTTON, False)
        bc.SetLong(c4d.BITMAPBUTTON_BORDER, c4d.BORDER_THIN_IN)
        self.myBitButton=self.AddCustomGui(self.BAKE_PREVIEW, c4d.CUSTOMGUI_BITMAPBUTTON, "", c4d.BFH_LEFT | c4d.BFV_TOP, 512, 256, bc)

        # Creates a statics text for the status
        if self.GroupBegin(id=0, flags=c4d.BFH_SCALEFIT, rows=1, title="", cols=1, groupflags=0):
            self.infoText = self.AddStaticText(id=0, initw=0, inith=0, name="", borderstyle=0, flags=c4d.BFH_SCALEFIT)
        self.GroupEnd()

        # Sets Button enable states so only bake button can be pressed
        self.EnableButtons(False)

        return True


    def CoreMessage(self, id, msg):
        # Checks if texture baking has finished
        if id == PLUGIN_ID:

            # Checks if texture baking has finished
            if id == PLUGIN_ID:
                # Sets Button enable states so only bake button can be pressed
                self.EnableButtons(False)

                # If not aborted, means the baking finished
                if not self.aborted:
                    # Updates the information status
                    self.SetString(self.infoText, str("Baking Finished"))

                    # Retrieves the baked bitmap
                    bmp = self.textureBakerThread.bakeBmp
                    if bmp is None:
                        raise RuntimeError("Failed to retrieve the baked bitmap.")

                    # Displays the bitmap
                    c4d.bitmaps.ShowBitmap(bmp)

                    # Save the bitmap
                    bmp.Save(self.filename, self.fileformat, c4d.BaseContainer(), c4d.SAVEBIT_ALPHA)
                
                    self.myBitButton.SetImage(self.filename, False)

                    # Removes the reference to the C4D Thread, so the memory used is free
                    self.textureBakerThread = None
                    return True
                else:
                    # If baking is aborted, updates the information status
                    self.SetString(self.infoText, str("Baking Aborted"))

                return True
        return c4d.gui.GeDialog.CoreMessage(self, id, msg)

Hi,

BitmapButton.SetImage() works just fine. Here is an example on how to set a BitmapButton to a BaseBitmap that has been scaled to fit a target size.

import c4d
from c4d import bitmaps

BUTTONIZE = 128
    
class TextureBakerDlg(c4d.gui.GeDialog):

    def CreateLayout(self):

        # Creates an image preview
        bc = c4d.BaseContainer()
        bc.SetBool(c4d.BITMAPBUTTON_BUTTON, False)
        bc.SetLong(c4d.BITMAPBUTTON_BORDER, c4d.BORDER_THIN_IN)
        self.myBitButton=self.AddCustomGui(1000, c4d.CUSTOMGUI_BITMAPBUTTON, "", 
                                           c4d.BFH_LEFT | c4d.BFV_TOP,
                                           BUTTONIZE, BUTTONIZE,
                                           bc)
        
        # Set up two bitmaps, the source bitmap (it is just the maxon logo in the top 
        # left corner of the forum) and a target sized empty bitmap                  
        src_bmp = bitmaps.BaseBitmap()
        src_bmp.InitWith(
            "https://plugincafe.maxon.net/assets/uploads/system/site-logo.png")
        dst_bmp = bitmaps.BaseBitmap()
        dst_bmp.Init(BUTTONIZE, BUTTONIZE)
        
        # Scale copy our source into the target.
        src_bmp.ScaleIt(dst_bmp, 256, True, True)
        
        # Set your button.
        self.myBitButton.SetImage(dst_bmp)

        return True

def main():
    dlg = TextureBakerDlg()
    dlg.Open(c4d.DLG_TYPE_ASYNC)

# Execute main()
if __name__=='__main__':
    main()

The fact that you do not see anything / that it 'does not work' for you is probably caused by the fact that you are passing a MultipassBitmap to your button. Check MultipassBitmap.GetLayers on how to access individual layers of BaseBitmaps in your MultipassBitmap which you could then hand to your BitmapButton.

Cheers
zipit

Hi,
Thank you very much @zipit and have a good day.