Maxon Developers Maxon Developers
    • Documentation
      • Cinema 4D Python API
      • Cinema 4D C++ API
      • Cineware API
      • ZBrush GoZ API
      • Code Examples on Github
    • Forum
    • Downloads
    • Support
      • Support Procedures
      • Registered Developer Program
      • Plugin IDs
      • Contact Us
    • Categories
      • Overview
      • News & Information
      • Cinema 4D SDK Support
      • Cineware SDK Support
      • ZBrush 4D SDK Support
      • Bugs
      • General Talk
    • Unread
    • Recent
    • Tags
    • Users
    • Login

    Quicktabs in dynamic prefs description

    Cinema 4D SDK
    2
    5
    891
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • a_blockA
      a_block
      last edited by

      Hi,

      I am aware of this thread (and even gave the answer there myself): https://developers.maxon.net/forum/topic/8853/11700_quicktabcustomgui-inside-getddescriptionsolved

      I just want to ask if anything has changed in this regard. So:
      I'd like to achieve a "tabbed group" switching, as seen below in Im-/Exporter preferences. In my plugin I would need to be able to provide this with a dynamic description inside of a PrefData. Is it still true, that I have no means to setup and configure a CUSTOMGUI_QUICKTAB from code in a description? Or are there other widgets by now, which could serve the same purpose?
      A QUICKTABSRADIO widget doesn't quite cut it, as it is lacking the multi selection.

      84eee80b-9ea7-4a1c-b93e-2f8f591e290a-image.png

      I tagged the post as R23 as ideally this would be the minimal version, I'd need such functionality.

      Any ideas would be much appreciated.
      Cheers,
      Andreas

      ferdinandF 1 Reply Last reply Reply Quote 0
      • ferdinandF
        ferdinand @a_block
        last edited by ferdinand

        Hello @a_block,

        Thank you for reaching out to us. I am not quite sure if I do understand your correctly, especially in the context of your link. But generally speaking, I would say that your conclusions are not correct, unless I fundamentally misunderstand you here.

        First, this is not really a CUSTOMGUI_QUICKTAB at work here, at least not on a level that matters for us third party developers. The tabs shown in your screenshot are simply the top-level groups of the description, specifically the tab of the preferences base description is here being loaded in addition to a tab defined by the Cinema 4D Exporter plugin. As always, I would recommend having a look at description resources delivered with Cinema 4D, as this often clarifies things:

        CONTAINER Fc4dexport
        {
        	NAME Fc4dexport;
        	INCLUDE Fbase; // The preferences base description is being loaded.
        
        	GROUP FC4DEXPORTFILTER_EXPORT_SAVEPROJECTWITHASSETS
        	{
        		DEFAULT 1;
        		BOOL FC4DEXPORTFILTER_EXPORT_COPYFILEASSETS {DEFAULT 1;}
        		BOOL FC4DEXPORTFILTER_EXPORT_COPYNODEASSETS {DEFAULT 1;}
        	}
        }
        
        

        You could also work here with a LONG (and a custom GUI of your liking) and manually tie group visibilities to its state, but I frankly do not see the necessity. Dynamically adding new groups at the root of a description, i.e., these tabs, is always a bit tricky. In this case it does not work at all, likely due to special properties of the description of PreferenceData plugins (you will find my attempt in the files provided below). But what you can do is hide and unhide groups (and optionally modify their content). I personally would say that this should suffice for preferences.

        Find an example for this simple approach below. The code provided here should work also in older versions of Cinema 4D. But I wrote and tested my code only for 2023.2, trying to avoid more modern code.

        Cheers,
        Ferdinand

        Result:
        prefs_dyn_desc.gif

        Files: pc14536.zip

        Resource:

        // The desciption container of "My Preferences"
        CONTAINER Fmypreferencedata
        {
          NAME Fmypreferencedata;
          INCLUDE Fbase; // Load in the prefercnes base desciption, i.e., the preset thingy.
        
          // The main tab of the preferences.
          GROUP ID_MYPREF_GRP_MAIN
          {
            DEFAULT 1;
        
            // This parameter has been used to fully dynamically add and remove groups. But it does not
            // work for preferences.
            // LONG ID_MYPREF_NUMBER_EXTRA_GROUPS { MIN 0; MAX 8; }
        
            BOOL ID_MYPREF_SHOW_EXTRA_GROUP_A {} // Shows/hides #ID_MYPREF_EXTRA_GROUP_A
            BOOL ID_MYPREF_SHOW_EXTRA_GROUP_B {} // ...
            BOOL ID_MYPREF_SHOW_EXTRA_GROUP_C {} // ...
          }
        
          // The three tabs which are shown/hidden dynamically.
          GROUP ID_MYPREF_EXTRA_GROUP_A
          {
            LONG ID_MYPREF_VALUE_A {}
          }
        
          GROUP ID_MYPREF_EXTRA_GROUP_B
          {
            LONG ID_MYPREF_VALUE_B {}
          }
        
          GROUP ID_MYPREF_EXTRA_GROUP_C
          {
            LONG ID_MYPREF_VALUE_C {}
          }
        }
        

        Code:

        """Realizes a preference data plugin which dynamically shows and hides top level groups.
        """
        import c4d
        import typing
        
        
        class MyPreferenceData(c4d.plugins.PreferenceData):
            """Realizes a preference data plugin which dynamically shows and hides top level groups.
            """
            ID_PLUGIN: int = 1061017 # The plugin ID.
            STR_NAME: str = "My Preferences" # The plugin name
            STR_DESCRIPTION: str = "fmypreferencedata" # The plugin resource
        
            # The precise IDs of the three dynamic groups mapped to their toggling check boxes. Will be 
            # assigned in #Init to have access to the loaded description resource (we cannot be sure 
            # #__res__ to have been fully loaded at this point). 
            DID_DYNAMIC_GROUPS: dict[int, c4d.DescID] = {}
        
            @classmethod
            def Register(cls) -> None:
                """Registers the plugin hook.
                """
                if not c4d.plugins.RegisterPreferencePlugin(
                    cls.ID_PLUGIN, cls, cls.STR_NAME, cls.STR_DESCRIPTION, 0, 0):
                    print(f"Warning: Failed to register '{cls}' '{cls.__base__.__name__}' plugin.")
        
            def Init(self, node: c4d.GeListNode) -> bool:
                """Called by Cinema 4D to let the plugin initialize its parameter values on instantiation.
                """
                MyPreferenceData.DID_DYNAMIC_GROUPS = {
                    c4d.ID_MYPREF_SHOW_EXTRA_GROUP_A: c4d.DescID(
                        c4d.DescLevel(c4d.ID_MYPREF_EXTRA_GROUP_A, c4d.DTYPE_GROUP, self.ID_PLUGIN)),
                    c4d.ID_MYPREF_SHOW_EXTRA_GROUP_B: c4d.DescID(
                        c4d.DescLevel(c4d.ID_MYPREF_EXTRA_GROUP_B, c4d.DTYPE_GROUP, self.ID_PLUGIN)),
                    c4d.ID_MYPREF_SHOW_EXTRA_GROUP_C: c4d.DescID(
                        c4d.DescLevel(c4d.ID_MYPREF_EXTRA_GROUP_C, c4d.DTYPE_GROUP, self.ID_PLUGIN)),
                }
        
                self.InitAttr(node, bool, c4d.ID_MYPREF_SHOW_EXTRA_GROUP_A)
                self.InitAttr(node, bool, c4d.ID_MYPREF_SHOW_EXTRA_GROUP_B)
                self.InitAttr(node, bool, c4d.ID_MYPREF_SHOW_EXTRA_GROUP_C)
                self.InitAttr(node, int, c4d.ID_MYPREF_VALUE_A)
                self.InitAttr(node, int, c4d.ID_MYPREF_VALUE_B)
                self.InitAttr(node, int, c4d.ID_MYPREF_VALUE_C)
        
                node[c4d.ID_MYPREF_SHOW_EXTRA_GROUP_A] = False
                node[c4d.ID_MYPREF_SHOW_EXTRA_GROUP_B] = False
                node[c4d.ID_MYPREF_SHOW_EXTRA_GROUP_C] = False
        
                # Bonus points for figuring out the relation these three numbers are in ;)
                node[c4d.ID_MYPREF_VALUE_A] = 42
                node[c4d.ID_MYPREF_VALUE_B] = 52
                node[c4d.ID_MYPREF_VALUE_C] = 101010
        
                return True
        
            def GetDDescription(self, node: c4d.GeListNode, description: c4d.Description,
                                flags: int) -> typing.Union[bool, tuple[bool, int]]:
                """Called by Cinema 4D when the description of a node is being evaluated to let the node
                dynamically modify its own description.
                """
                # Bail when the description of the node has not been loaded yet.
                if not description.LoadDescription(self.ID_PLUGIN):
                    return False, flags
        
                # Get the currently to be evaluated parameter ID and iterate over #DID_DYNAMIC_GROUPS.
                evalId: c4d.DescID = description.GetSingleDescID()
                for checkBoxId, groupDid in self.DID_DYNAMIC_GROUPS.items():
        
                    # Bail when Cinema 4D actively tells us what to modify, and it is not a dynamic group.
                    if evalId and not groupDid.IsPartOf(evalId):
                        continue
                    
                    # Get the #state #groupDid should be in and its #parameter description instance.
                    state: bool = node.GetParameter(c4d.DescID(checkBoxId), c4d.DESCFLAGS_GET_NONE)
                    parameter: c4d.BaseContainer = description.GetParameterI(groupDid)
                    if state is None or not parameter:
                        return True, flags
        
                    # Set the visibility value as the inverse because the interface says "show".
                    parameter.SetBool(c4d.DESC_HIDE, not state)
        
                return True, flags | c4d.DESCFLAGS_DESC_LOADED
        
        if __name__ == "__main__":
            MyPreferenceData.Register()
        

        MAXON SDK Specialist
        developers.maxon.net

        1 Reply Last reply Reply Quote 0
        • a_blockA
          a_block
          last edited by

          Hi Ferdinand,

          always a pleasure to ask you something. Thanks, for the quick and thorough answer.

          I am as stupid as a slice of bread (no insult to the bread).
          I hadn't even considered including Fbase, as it is no "filter" plugin and I did not want the Presets group.
          So, my actual question was more. how to create those tabs on top dynamically myself?
          But of course I can simply hide the preset group. Bam, working nicely. Forget the original question.

          Thanks again for your effort!

          Cheers,
          Andreas

          ferdinandF 1 Reply Last reply Reply Quote 0
          • ferdinandF
            ferdinand @a_block
            last edited by

            @a_block said in Quicktabs in dynamic prefs description:

            So, my actual question was more. how to create those tabs on top dynamically myself?

            Yeah, I got that. It is not possible for PreferenceData plugins to fully dynamically add new root level groups. I tried, you will find the code in the files, Cinema 4D simply ignored all my attempts. I then asked the GUI Team what is going on, and they told me that prefs are a bit special, and it is not really intended to do this. If you are really persistent you might be able to make it work, I did not spend much time on it.

            Cheers,
            Ferdinand

            MAXON SDK Specialist
            developers.maxon.net

            1 Reply Last reply Reply Quote 0
            • a_blockA
              a_block
              last edited by

              No worries. Thanks for trying. 🙂

              1 Reply Last reply Reply Quote 0
              • First post
                Last post