SOLVED ShowPopupDialog Freezes Cinema R21

Hi all,

Hope you are well in these crazy times!

I'm having trouble understanding how ShowPopupDialog currently works in R21, if caught by MessageData plugin's CoreMessage. It keeps freezing Cinema, could be thread related (which I don't know a lot of)?

I manage to get the data across externally fine but when creating the menu it freezes immediately after c4d.gui.ShowPopupDialog is called.

# Import modules
import os
import c4d
from c4d import documents

plugin_ID = 1055685
plugin_ID_loader = 1055684

MENU_ADD_ASSETS = c4d.FIRST_POPUP_ID
MENU_ADD_MATERIALS = c4d.FIRST_POPUP_ID + 1
MENU_REPLACE_ASSETS = c4d.FIRST_POPUP_ID + 2
MENU_REPLACE_MATERIALS = c4d.FIRST_POPUP_ID + 3
MENU_REPLACE_TAG = c4d.FIRST_POPUP_ID + 4


class AOMessage(c4d.plugins.MessageData):
    """
    Class for the MessageData plugin.
    """

    def GetTimer(self):
        """
        Called to return a time in milliseconds to receive timer messages (MSG_TIMER) with that interval in CoreMessage.
        This method is queried again after each message..
        :return: The time in miliseconds.
        """
        return 0

    def CoreMessage(self, id, bc):
        """
        Called to receive core messages.
        :param id: The id of the messages received.
        :param bc: The BaseContainer objects with message data.
        :return: True if Message is received.
        """

        if id == plugin_ID_loader:

            world_container = c4d.GetWorldContainerInstance()
            doc = documents.GetActiveDocument()
            paths_str = world_container.GetString(plugin_ID_loader)
            paths_lst = paths_str.split(', ')
            active_objs = doc.GetActiveObjects(c4d.GETACTIVEOBJECTFLAGS_0)
            active_mat = doc.GetActiveMaterial()
            doc.StartUndo()

            # Dialog for importing assets
            menu = c4d.BaseContainer()

            if active_objs or active_mat:
                menu.InsData(MENU_REPLACE_ASSETS, 'Replace Asset(s)')
                menu.InsData(MENU_REPLACE_MATERIALS, 'Replace Material(s)')
                menu.InsData(MENU_REPLACE_TAG, 'Replace Texture Tag(s)')

            else:
                menu.InsData(MENU_ADD_ASSETS, 'Add Asset(s)')
                menu.InsData(MENU_ADD_MATERIALS, 'Add Material(s)')

            result = c4d.gui.ShowPopupDialog(cd=None, bc=menu, x=c4d.MOUSEPOS, y=c4d.MOUSEPOS)

            doc.EndUndo()
            world_container.RemoveData(plugin_ID_loader)

            print("Finished")

        return True


if __name__ == "__main__":

    c4d.plugins.RegisterMessagePlugin(id=plugin_ID,
                                      str="AO Message",
                                      info=0,
                                      dat=AOMessage())

It works perfectly in R19, so I'm not sure what changed since then.
Apologies if this as been asked before.

Thank you very much for your help in advance!

Andre

Hi @AndreAnjos,

it is great that you found the issue. A few points:

  1. Just for clarity: The world container is not document related, but rather the global container used by a Cinema 4D installation. Documents have their own container(s) to store data in.
  2. Before trying to investigate if this is indeed a somehow corrupted container, I would first try if this is not an issue of ownership (probably should have mentioned that first). There is no guarantee that this will work, but it might be worth a shot. So instead of retrieving the world container instance via c4d.GetWorldContainerInstance() get a copy of it via c4d.GetWorldContainer(). When your code then does run, you can try to write back your modifications of the world container via c4d.SetWorldContainer().
  3. If all this fails, I would recommend reinstalling Cinema 4D, because a corrupted world container is not really something you can fix or really want.
  4. When you had to take the route of point 3., I would also recommend using Get/SetWorldContainer(), to avoid any further problems.
  5. Regarding a file based solution: This is of course possible, but comes with on plethora of problems and drawbacks (R/W access and speed of operations), so I would try to avoid it as long as possible.

Cheers,
Ferdinand

Hi @AndreAnjos unfortunately, I'm not able to make Cinema 4D R21 freezing by copying your plugin and executing it by calling: c4d.SpecialEventAdd(1055684) from the Python console.

So on which os version? on which exact version of R21 are you (you can check in the about form)?

In which context and how often are you calling SpecianEventAdd?
Cheers,
Maxime.

@m_adam
Hi Maxime,

Cinema R21.207 Build RB303831.
Windows 10 1809

I'm calling the SpecialEventAdd on an SceneLoaderPlugin.

# Import modules
import c4d

plugin_ID = 1055684


class AOLoader(c4d.plugins.SceneLoaderData):
    """
    Class for the SceneLoaderData plugin.
    """

    def Identify(self, node, name, probe, size):
        """
        Identifies if the format of the custom file captured meets the plugins requirements.
        :param node: BaseContainer for instance.
        :param name: Path and file name of the instance.
        :param probe: The data from the instance.
        :param size: The data size of the instance.
        :return: True if meets object requirements.
        """

        file_name = name.lower()

        if not file_name.endswith('.ao'):
            return False

        return True

    @staticmethod
    def clean_path_str(txt, chr_lst):
        """
        This method removes specific characters in a path.
        :param txt: The string to replace character.
        :param chr_lst: The characters list to be replaced.
        :return: Returns the path with removed c characters.
        """

        for c in chr_lst:
            txt = txt.replace(c, '')

        return txt

    def read_lines(self, data_file):
        """
        Iterates throught the data until finds the line with path for .c4d asset.
        :param data_file: The file data returned by Identify method.
        :return: The path string, not formatted.
        """

        with open(data_file, 'rU') as data:

            ds_file = data.read().splitlines()
            paths = ""

            for line in ds_file:
                if ".c4d" in line:

                    remove_chrs = self.clean_path_str(line, [')', '"'])
                    paths += remove_chrs.replace("\\\\", "\\")

            return paths

    def Load(self, node, name, doc, filterflags, error, bt):
        """
        Loads the data into the active document. In this case, we do not load or merge the file, but
        send a message to CoreMessage when it is catched with a SpecialEventAdd.
        The WorldContainer will save the path to be used later.
        :param node: BaseContainer for the instance.
        :param name: Path and file name of the instance.
        :param doc: The active document.
        :param filterflags: Flags to filter objects to load.
        :param error: Errors brought by Identify Method.
        :param bt: The BaseThread for the document.
        :return: Returns a c4d.FILEERROR_USERBREAK to capture the message.
        """

        paths = self.read_lines(name)

        world_container = c4d.GetWorldContainerInstance()
        world_container.SetString(plugin_ID, paths)
        c4d.SpecialEventAdd(plugin_ID)

        return c4d.FILEERROR_USERBREAK


if __name__ == "__main__":

    c4d.plugins.RegisterSceneLoaderPlugin(id=plugin_ID,
                                          str="AO Loader",
                                          g=AOLoader,
                                          info=c4d.PLUGINFLAG_SCENELOADER_MERGEORIGINAL,
                                          description="")

It's passing the data correctly, so perhaps the call needs to be done somewhere else?

Thank you in advance!

Andre

Hi, Unfortunately, I'm not able to reproduce your issue with the code you provided.
The only difference is that I'm on windows 20H2. But I don't expect this to be the issue, I will ask someone else to try to reproduce.

Just to be sure If you copy-paste the code you provided us, does it freeze for you?

Cheers,
Maxime.

@m_adam
Morning Maxime,

Yes, it freezes with the code I provided.
I've also tried the way that you mention above by running the c4d.SpecialEventAdd(1055684) and the same happens, as per my video below.

https://drive.google.com/file/d/1v4A9xXKb79Z1TlMnlSdZ-tk9WJFldsv7/view?usp=sharing

Let me know if you need any other information.

Thank you!

Andre

Hi @AndreAnjos, hi @m_adam,

@AndreAnjos said in ShowPopupDialog Freezes Cinema R21:

@m_adam
Morning Maxime,

Yes, it freezes with the code I provided.
I've also tried the way that you mention above by running the c4d.SpecialEventAdd(1055684) and the same happens, as per my video below.

https://drive.google.com/file/d/1v4A9xXKb79Z1TlMnlSdZ-tk9WJFldsv7/view?usp=sharing

Let me know if you need any other information.

Thank you!

Andre

I just tried it too on R21.207 and it does not freeze for me either (I just added the message id as a print out, not knowing there was already that "finished" statement).

93d7cbc5-136f-4a1f-bdf5-cfae9d61b757-image.png

One thing you could do is to try to remove other plugins or scripts that are loaded by your Cinema 4D R21 installation, to make sure sure none of them is interfering with the plugin. When you have done this, you could also test, if opening a popup dialog in general, so something like this in the script manager,

import c4d

def main():
    """
    """
    MENU_ADD_ASSETS = c4d.FIRST_POPUP_ID
    MENU_ADD_MATERIALS = c4d.FIRST_POPUP_ID + 1
    MENU_REPLACE_ASSETS = c4d.FIRST_POPUP_ID + 2
    MENU_REPLACE_MATERIALS = c4d.FIRST_POPUP_ID + 3
    MENU_REPLACE_TAG = c4d.FIRST_POPUP_ID + 4

    menu = c4d.BaseContainer()
    menu.InsData(MENU_REPLACE_ASSETS, 'Replace Asset(s)')
    menu.InsData(MENU_REPLACE_MATERIALS, 'Replace Material(s)')
    menu.InsData(MENU_REPLACE_TAG, 'Replace Texture Tag(s)')
    result = c4d.gui.ShowPopupDialog(cd=None, bc=menu, x=c4d.MOUSEPOS, y=c4d.MOUSEPOS)

if __name__ == '__main__':
    main()

will freeze your installation.

Cheers,
Ferdinand

@ferdinand
Hi Ferdinand,

Thank you for your tips!

Unfortunately removing the other plugins does not make a difference, from what I can understand.

I've also run the general popup dialog and it works fine with no freezing.

Not sure what to do about this except perhaps change the c4d.gui.ShowPopupDialog to a c4d.gui.GeDialog
and create a simple menu. Any thoughts?

Thank you in advance!

Hi @AndreAnjos,

well are you sure that ShowPopupDialog() is actually the culprit? Because it would be my last guess at the moment and in the end just replacing one buggy part with another feature is not really our understanding of good support here 😉 Since we cannot reproduce your problem, you have to do the debugging unfortunately yourself. What I would propose is:

  1. Copy your plugin code into an editor which does not have "fancy features", i.e., something like windows notepad, and save it with that editor as a txt file and rename it back to my_plugin.pyp again. So to make sure that you do not have any weird unicode-mishaps in your file.
  2. Reinstall Cinema 4D from scratch. I know this one sucks, but in the end you have to do it anyways, when you have exhausted all other options, so you might also just get it out of the way in the beginning.
  3. Sprinkle in some print statements to see which parts of your code are reached before Cinema does freeze (although you should not put to much faith into this, due to the way Python does unravel its console output, but trying does not hurt).
  4. Remove the GetTimer() implementation form your AOMessage and remove the body of CoreMessage() except for the return statement.
  5. Reintroduce in blocks your code, you could for example first add everything up to active_mat= ... and then up to result =.
  6. With the results of 4. try to pinpoint the call/statement which does cause Cinema 4D to freeze.

For each step you have to check if it did change anything about the freezing.

Your code in general is rather inconspicuous, so at least I cannot point anything out that would be the obvious culprit. The fact that you start and close an undo-block without using it is a bit weird, but should not freeze Cinema 4D. One rather far-fetched reason could be that you somehow corrupted the world-container of your installation permanently and now accessing it will freeze Cinema 4D. But then this should normally happen all the time, because Cinema 4D does access it all the time (and also in your SceneLoaderData plugin, since it does access the same part of the world container.

So at least from my side this is all mostly speculation. The unfortunate truth of debugging is that you are always samerter after figuring it out 😉

Cheers,
Ferdinand

@ferdinand
Hi Ferdinand,

Many thanks for your valid points and I appreciate what you are saying :).

I've made the changes to use a GeDialog instead for the menu and it works.

Here's are my results, based on your proposals:

  1. Nope, same issue still.
  2. Not tried it, but may have to give it a go just in case.
  3. Yes done this at the start and simplify the code, all code works until I introduce the ShowPopupDialog(), as any code after this does not run. Then again it may be something associated with it.
  4. Yes, the GetTimer() function is not doing anything, so definitely good idea.
  5. Yes, that was one of the ways I was debugging and again runs anything that I add to it, except ShowPopupDialog().
  6. I'm going to give it another go.

The undo-block is for something I wrote in between, but for simplification removed it to pinpoint the culprit. 🙂

The world-container is definitely a good point. I'm not sure if I should be using it to store data. I think the reasoning behind it was to keep data accessible between loading and unloading of documents, which is mainly what this plugin is doing.
Would you recommend perhaps a different container to store the data?

What I found most weird is that works perfectly fine with R19, so perhaps as you pointed out on 2, Cinema needs reinstalling.

It's all learning and I think I know a bit more about it now and even if I cannot find the solution for this, I already have another way of dealing with it. Not elegant, but works with no issues :).

Thank you very much for your help and time!

@ferdinand
I just found what perhaps is the issue...

The world-container. It seems that it runs without freezing if the data is not saved and load from the world container.
Quiet odd, but perhaps as you said, it corrupted it somehow?

Just need to find a way to storing data outside of the document.

Thank you!

Hi @AndreAnjos,

it is great that you found the issue. A few points:

  1. Just for clarity: The world container is not document related, but rather the global container used by a Cinema 4D installation. Documents have their own container(s) to store data in.
  2. Before trying to investigate if this is indeed a somehow corrupted container, I would first try if this is not an issue of ownership (probably should have mentioned that first). There is no guarantee that this will work, but it might be worth a shot. So instead of retrieving the world container instance via c4d.GetWorldContainerInstance() get a copy of it via c4d.GetWorldContainer(). When your code then does run, you can try to write back your modifications of the world container via c4d.SetWorldContainer().
  3. If all this fails, I would recommend reinstalling Cinema 4D, because a corrupted world container is not really something you can fix or really want.
  4. When you had to take the route of point 3., I would also recommend using Get/SetWorldContainer(), to avoid any further problems.
  5. Regarding a file based solution: This is of course possible, but comes with on plethora of problems and drawbacks (R/W access and speed of operations), so I would try to avoid it as long as possible.

Cheers,
Ferdinand

@ferdinand
Hi Ferdinand,

Thank you for your time and help!
Will get it sorted with your pointers above.

All the best!

Andre