Quicktabs in dynamic prefs description
-
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.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 -
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 ofPreferenceData
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,
FerdinandResult:
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()
-
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 -
@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 -
No worries. Thanks for trying.