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

    PreferenceData plugin, Text disappears when directory is set

    Cinema 4D SDK
    python r19 sdk
    3
    10
    1.5k
    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.
    • P
      potashalum
      last edited by m_adam

      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 Nasir

      import 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()
      
      1 Reply Last reply Reply Quote 0
      • M
        m_adam
        last edited by

        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 with GetContainer.
        Here the full code working

        import 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.

        MAXON SDK Specialist

        Development Blog, MAXON Registered Developer

        1 Reply Last reply Reply Quote 1
        • P
          potashalum
          last edited by potashalum

          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.

          1 Reply Last reply Reply Quote 0
          • M
            m_adam
            last edited by

            Hi @potashalum did you update your GetPreferenceContainer function as well?

            By copy/pasting the code provided you also get the issue?
            Cheers,
            Maxime.

            MAXON SDK Specialist

            Development Blog, MAXON Registered Developer

            1 Reply Last reply Reply Quote 1
            • P
              potashalum
              last edited by potashalum

              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

              1 Reply Last reply Reply Quote 0
              • M
                m_adam
                last edited by

                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.

                MAXON SDK Specialist

                Development Blog, MAXON Registered Developer

                1 Reply Last reply Reply Quote 1
                • P
                  potashalum
                  last edited by

                  Hi @m_adam,

                  No problem, I hope you had great holidays and thank you for confirming the problem.

                  Regards,
                  Alamgir

                  1 Reply Last reply Reply Quote 0
                  • DunhouD
                    Dunhou
                    last edited by

                    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 python😞

                    For 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!

                    https://boghma.com
                    https://github.com/DunHouGo

                    1 Reply Last reply Reply Quote 0
                    • M
                      m_adam
                      last edited by

                      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.

                      MAXON SDK Specialist

                      Development Blog, MAXON Registered Developer

                      DunhouD 1 Reply Last reply Reply Quote 0
                      • DunhouD
                        Dunhou @m_adam
                        last edited by

                        @m_adam Thanks a lot for this example 👏 A big shout out !

                        https://boghma.com
                        https://github.com/DunHouGo

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