PreferenceData plugin, Text disappears when directory is set
-
Hello everyone,
I am having this problem that when I try to use a Filename parameter to get the path of a directory,
It either, in case I don't override the GetDParameter and SetDParameter functions does not save the path,
or, in case I do override these functions and set and get the plugin container myself, the path text in the UI disappears.If someone can have a look I will greatly appreciate it. I created a self contained project illustrating the problem. Just unzip it in the plugins folder and it should work.
I am also going to go ahead and paste the python code here, in case some one can find something just by looking at the code, there is also of course accompanying resource files that are included in the zip file.
Thank you,
Alamgir Nasirimport c4d PREFERENCE_PLUGIN_ID = 1040402 PREFERENCE_RENDER_PATH = 1000 def GetPreferenceContainer(): world = c4d.GetWorldContainerInstance() if world is None: return None bc = world.GetContainerInstance(PREFERENCE_PLUGIN_ID) if bc is None: world.SetContainer(PREFERENCE_PLUGIN_ID, c4d.BaseContainer()) bc = world.GetContainerInstance(PREFERENCE_PLUGIN_ID) if bc is None: return None return bc class TestPreference(c4d.plugins.PreferenceData): def GetDParameter(self, node, id, flags): bc = GetPreferenceContainer() if bc is None: return False # Retrieves either check or number preference value paramID = id[0].id if paramID == PREFERENCE_RENDER_PATH: return (True, bc.GetFilename(PREFERENCE_RENDER_PATH), flags | c4d.DESCFLAGS_GET_PARAM_GET) return False def SetDParameter(self, node, id, data, flags): bc = GetPreferenceContainer() if bc is None: logger.error("SetDParameter: bc is none.") return False # Changes either check or number preference value paramID = id[0].id if paramID == PREFERENCE_RENDER_PATH: bc.SetFilename(PREFERENCE_RENDER_PATH, data) return (True, flags | c4d.DESCFLAGS_SET_PARAM_SET) return False def Register(self): print("Registered test preferences") return c4d.plugins.RegisterPreferencePlugin( id=PREFERENCE_PLUGIN_ID, g=TestPreference, name="TEST", description="testpreference", parentid=0, sortid=0) TestPreference().Register()
-
Hi @potashalum, first of all, welcome in the plugincafe community!
Regarding the issue, as you may know, in Python there is no Filename object. So when you call
return (True, bc.GetFilename(PREFERENCE_RENDER_PATH), flags | c4d.DESCFLAGS_GET_PARAM_GET)
bc.GetFilename(PREFERENCE_RENDER_PATH)
returns str, so it set a string as a parameter while the description expects a Filename.To do so simply replace by
GetCustomDataType
(note it will print an error, but it's actually working, so you will need a try/except block. But I will investigate the error and fix it for a future release).Then with that's said your
GetPreferenceContainer
is actually wrong. I replaced it withGetContainer
.
Here the full code workingimport c4d PREFERENCE_PLUGIN_ID = 1040402 PREFERENCE_RENDER_PATH = 1000 def GetContainer(node=None): bc = None if node is None: plug = c4d.plugins.FindPlugin(PREFERENCE_PLUGIN_ID, c4d.PLUGINTYPE_PREFS) if plug is None: return bc = plug.GetDataInstance() else: bc = node.GetDataInstance() return bc class TestPreference(c4d.plugins.PreferenceData): def GetDParameter(self, node, id, flags): bc = GetContainer(node) if bc is None: return False # Retrieves either check or number preference value paramID = id[0].id if paramID == PREFERENCE_RENDER_PATH: try: return (True, bc.GetCustomDataType(PREFERENCE_RENDER_PATH), flags | c4d.DESCFLAGS_GET_PARAM_GET) except: return False return False def SetDParameter(self, node, id, data, flags): bc = GetContainer() if bc is None: print ("SetDParameter: bc is none.") return False # Changes either check or number preference value paramID = id[0].id if paramID == PREFERENCE_RENDER_PATH: bc.SetFilename(PREFERENCE_RENDER_PATH, data) return (True, flags | c4d.DESCFLAGS_SET_PARAM_SET) return False def Register(self): print("Registered test preferences") return c4d.plugins.RegisterPreferencePlugin( id=PREFERENCE_PLUGIN_ID, g=TestPreference, name="TEST", description="testpreference", parentid=0, sortid=0) TestPreference().Register()
I will add a note in the documentation about Filename.
If you have any question, please let me know.
Cheers,
Maxime. -
Thank you for your help @m_adam, but I am still having the same problem.
Using
GetCustomDataType
doesn't give me any errors but it doesn't work either. It still clears the string immediately after I set the field.It does seem like the problem is still with the
GetDParameter
, because if I comment it out everything works except that the filename isn't loaded when cinema is restarted. -
Hi @potashalum did you update your GetPreferenceContainer function as well?
By copy/pasting the code provided you also get the issue?
Cheers,
Maxime. -
Hi @m_adam,
I updated the GetPreferenceContainer function as well, it did help in that the SetDParameter function works now, which didn't before. So that is definitely better. But the GetDParameter function didn't work, even if I only use your code.
Could it be a problem in the res files? Would you mind taking a look please. I tried my best to get those right but maybe I still have an error there.
Regards,
Alamgir -
Hi @potashalum, I'm terribly sorry I missed your reply when I came back from holiday.
After digging more into this, it's currently not possible to handle Filename from python in GetDParameter of a world preference.
A workaround would be to store parameter as a string, display a string instead of a FileName and make a button to define this string parameter.Cheers,
Maxime. -
Hi @m_adam,
No problem, I hope you had great holidays and thank you for confirming the problem.
Regards,
Alamgir -
Hi @m_adam ,
I also have a problem with filename in the preference . It has the same problem as @potashalum did, and I look for the Github py-preference example , I notice that a note Filename parameter type are not supported. .
But I fine some build-in res file has the
FILENAME
attribute (like ...\description\prefsbrowser.res) , and it can actually read and set filename in preference , Is it fixed now and miss some information that I failed to do this in pythonFor the suggestion of the example, I try to fake it with a bitmap , but I failed draw a bitmap in res file ,could you please update the Github example a little for this ,or some tricks and tips?
Thanks!
-
Hi @Dunhou, the issue is only a python one since in python there is no Filename type so there is no way for you to return a default value, or you will have no way to read/write the value and therefore store them and value will be lost after each restart.
Regarding bitmap you need to define a
BITMAPBUTTON
in the res then in the Message method of your NodeData you should react to the MSG_DESCRIPTION_GETBITMAP and fed the bitmap icon.So with that's said here an example adapted from py-preference plugin
The .pyp file:import c4d # Unique plugin ID obtained from www.plugincafe.com PLUGIN_ID = 1039699 # Unique plugin ID for world preference container obtained from www.plugincafe.com WPREF_PYPREFERENCE = 1039700 # ID for the World Preference Container parameter WPREF_PYPREFERENCE_STRING = 1000 WPREF_PYPREFERENCE_BUTTON = 1001 class PreferenceHelper(object): @staticmethod def GetPreferenceContainer(): """Helper method to retrieve or create the WPREF_PYPREFERENCE container instance stored in the world container. Returns: c4d.BaseContainer: The container instance stored in the world container. Raises: RuntimeError: The BaseContainer can't be retrieved. MemoryError: The BaseContainer can't be created. """ # Retrieves the world container instance world = c4d.GetWorldContainerInstance() if world is None: raise RuntimeError("Failed to retrieve the world container instance.") # Retrieves the container of our plugin, stored in the world container instance # Parameter values will be stored in this container. bc = world.GetContainerInstance(WPREF_PYPREFERENCE) # If there is no container, creates one if bc is None: # Defines an empty container world.SetContainer(WPREF_PYPREFERENCE, c4d.BaseContainer()) # Retrieves this empty container instance bc = world.GetContainerInstance(WPREF_PYPREFERENCE) if bc is None: raise MemoryError("Failed to create a BaseContainer.") return bc def InitValues(self, descId, description=None): """Helper method to define type and default value of parameter Args: descId (c4d.DescID): The parameter ID describing the type and the ID of the parameter you want to initialize. description (c4d.Description, optional): The description of the PreferenceData. Defaults to None. Returns: True if success otherwise False. """ # Retrieves the world BaseContainer of this preference, where values have to be defined bc = self.GetPreferenceContainer() # Defines default values paramId = descId[0].id if paramId == c4d.PYPREFERENCE_STRING: self.InitPreferenceValue(WPREF_PYPREFERENCE_STRING, "File", description, descId, bc) return True class Preference(c4d.plugins.PreferenceData, PreferenceHelper): def Init(self, node): """Called by Cinema 4D on the initialization of the PreferenceData, the place to define the type of object. Args: node (c4d.GeListNode): The instance of the PreferenceData. Returns: True if the initialization success, otherwise False will not create the object. """ # Init default values bc = self.GetPreferenceContainer() self.InitValues(c4d.DescID(c4d.DescLevel(c4d.PYPREFERENCE_STRING, c4d.DTYPE_STRING, 0))) return True def SetDParameter(self, node, id, data, flags): """Called by Cinema 4D, when SetParameter is call from the node. The main purpose is to store the data in the world container. Args: node (c4d.GeListNode): The instance of the PreferenceData. id (c4d.DescID): The parameter Id. data (Any): the data, the user defines and we have to store. flags (DESCFLAGS_SET): The input flags passed to define the operation. Returns: Union[Bool, tuple(bool, Any, DESCFLAGS_SET)]: The success status or the data to be returned. """ # Retrieves the world BaseContainer of this preference, where values have to be defined bc = self.GetPreferenceContainer() # Retrieves the parameter ID changed paramID = id[0].id # Store the values in the World Container if paramID == c4d.PYPREFERENCE_STRING: bc.SetString(WPREF_PYPREFERENCE_STRING, data) return True, flags | c4d.DESCFLAGS_SET_PARAM_SET if paramID == c4d.PYPREFERENCE_BUTTON: return True, flags | c4d.DESCFLAGS_SET_PARAM_SET return True def GetDParameter(self, node, id, flags): """Called by Cinema 4D, when GetParameter is call from the node. The main purpose is to return the data from the world container. Args: node (c4d.GeListNode): The instance of the PreferenceData. id (c4d.DescID): The parameter Id. flags (DESCFLAGS_GET): The input flags passed to define the operation. Returns: Union[Bool, tuple(bool, Any, DESCFLAGS_GET)]: The success status or the data to be returned. """ # Retrieves the world BaseContainer of this preference, where values have to be retrieved bc = self.GetPreferenceContainer() # Retrieves the parameter ID asked paramID = id[0].id # Returns the values from the World Container if paramID == c4d.PYPREFERENCE_STRING: return True, bc.GetString(WPREF_PYPREFERENCE_STRING), flags | c4d.DESCFLAGS_GET_PARAM_GET # Instantiate a BitmapButtonStruct to be used by the Bitmap parameter if paramID == c4d.PYPREFERENCE_BUTTON: bbs = c4d.BitmapButtonStruct(node, id, 0) return True, bbs, flags | c4d.DESCFLAGS_GET_PARAM_GET return True def Message(self, node, type, data): # Determine the icon to use for the WPREF_PYPREFERENCE_BUTTON if type == c4d.MSG_DESCRIPTION_GETBITMAP: if data['id'][0].id == c4d.PYPREFERENCE_BUTTON: iconOld = c4d.gui.GetIcon(1039689) icon = c4d.IconData() icon.bmp = iconOld['bmp'] icon.x = iconOld['x'] icon.y = iconOld['y'] icon.w = iconOld['w'] icon.h = iconOld['h'] icon.flags = c4d.ICONDATAFLAGS_NONE data['bmp'] = icon.GetClonePart() data['bmpflags'] = c4d.ICONDATAFLAGS_NONE return True # When a user click on the Button if type == c4d.MSG_DESCRIPTION_COMMAND: if data['id'][0].id == c4d.PYPREFERENCE_BUTTON: path = c4d.storage.LoadDialog(c4d.FILESELECTTYPE_ANYTHING, "Select a file", c4d.FILESELECT_LOAD) if path: # Retrieves the world BaseContainer of this preference, where values have to be stored and save the path bc = self.GetPreferenceContainer() bc.SetString(WPREF_PYPREFERENCE_STRING, path) return True return True if __name__ == '__main__': c4d.plugins.RegisterPreferencePlugin(id=PLUGIN_ID, g=Preference, name="Py-Preference", description="pypreference", parentid=0, sortid=0)
The pypreference.str
STRINGTABLE pypreference { pypreference "Py-Preference"; PYPREFERENCE_STRING "URL"; PYPREFERENCE_BUTTON ""; }
The pyprefrence.h
#ifndef PYPREFERENCE_H__ #define PYPREFERENCE_H__ enum { PYPREFERENCE_MAIN_GROUP = 999, PYPREFERENCE_STRING = 1000, PYPREFERENCE_BUTTON = 1001, PYPREFERENCE_DUMMY }; #endif // PYPREFERENCE_H__
The pyprefrence.res
CONTAINER pypreference { NAME pypreference; GROUP PYPREFERENCE_MAIN_GROUP { DEFAULT 1; COLUMNS 2; STRING PYPREFERENCE_STRING { } BITMAPBUTTON PYPREFERENCE_BUTTON { } } }
Just a side note previously, I don't remember exactly when it happened but the button was a text "..." and not a bitmap as it is right now.
Cheers,
Maxime. -
@m_adam Thanks a lot for this example A big shout out !