Maxon Developers Maxon Developers
    • Documentation
      • Cinema 4D Python API
      • Cinema 4D C++ API
      • Cineware API
      • ZBrush Python 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

    Struggling with Messages [SOLVED]

    Scheduled Pinned Locked Moved PYTHON Development
    10 Posts 0 Posters 886 Views
    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.
    • H Offline
      Helper
      last edited by

      On 24/10/2016 at 09:14, xxxxxxxx wrote:

      Hi all,
      I am writing a shader plugin, with amongst others two parameters, grid and scale. They are each others inverses, ie grid = Vector(1/scale.x, 1/scale.y, 1/scale.z). The Texture tags in c4d work the same, you can either set Length U/V or Tiles U/V and the other is automatically updated.
      Now here comes the weird part: The code below does update the values in the usermenu (ie everything looks fine) and the print statement prints the correct values. But the value in self.scale and self.grid are never updated at all. So when I check scale and grid from the Output function, it is still Vector(1,1,1) or whatever I assigned them to in the __init__ function.
      I have been struggling with this for a day now, and I hope somebody has some suggestions.

      (Method 1)
          def Message(self, node, id, msg) :
              if (id==c4d.MSG_DESCRIPTION_POSTSETPARAMETER) :
                  bc = node.GetDataInstance()
                  if msg['descid'][0].id == INTERIOR_SCALE:
                      scale = bc.GetVector(INTERIOR_SCALE)
                      self.scale = 1.0 * scale
                      self.grid = Vector(1.0/scale.x, 1.0/scale.y, 1.0/scale.z)
                      bc.SetVector(INTERIOR_GRID, self.grid)
                      print 'scale', scale, self.scale
                  elif msg['descid'][0].id == INTERIOR_GRID:
                      grid = bc.GetVector(INTERIOR_GRID)
                      self.grid = 1.0 * grid
                      self.scale = Vector(1.0/grid.x, 1.0/grid.y, 1.0/grid.z)
                      bc.SetVector(INTERIOR_SCALE, self.scale)
                      print 'grid', grid, self.grid

      If I put the code below in the InitRender method, all works fine, but the update of the inverse values happens only after the preview render, so is rather sluggish. 
      So why does Method 2 work, albeit sluggishly, and Method 1 not?
      Regards,

      Hermen

      (Method 2)
          def InitRender(self, sh, irs) :
              bc = sh.GetDataInstance()
              scale = bc.GetVector(INTERIOR_SCALE)
              grid = bc.GetVector(INTERIOR_GRID)
              if scale != self.scale:
                  print 'scale'
                  self.scale = scale
                  self.grid = Vector(1.0/scale.x, 1.0/scale.y, 1.0/scale.z)
                  bc.SetVector(INTERIOR_GRID, self.grid)
              if grid != self.grid:
                  print 'grid'
                  self.grid = grid
                  self.scale = Vector(1.0/grid.x, 1.0/grid.y, 1.0/grid.z)
                  bc.SetVector(INTERIOR_SCALE, self.scale)
              print 'initrender', self.scale

      1 Reply Last reply Reply Quote 0
      • H Offline
        Helper
        last edited by

        On 25/10/2016 at 01:35, xxxxxxxx wrote:

        Hello,

        are "scale" and "grid" member variables of your plugin class? Why are you not storing this data in the element's BaseContainer? If an element is changed Cinema will create a copy of this element for the undo-stack. Doing so it will automatically copy the element's BaseContainer. But it cannot handle any other internal data. So you have to implement CopyTo() to make sure that any internal data is copied correctly to another instance. See also the C++ NodeData::CopyTo() Manual.

        best wishes,
        Sebastian

        1 Reply Last reply Reply Quote 0
        • H Offline
          Helper
          last edited by

          On 25/10/2016 at 04:51, xxxxxxxx wrote:

          Hi Sebastian,
          I'm not sure I understand you. And maybe I am using the 'Message' method in the wrong way. I am trying to read this instance's menudata, not to communicate with another instance.

          "are "scale" and "grid" member variables of your plugin class?" - What do you mean by member variable?
          "Why are you not storing this data in the element's BaseContainer?" - Because I thought this bc is not everywhere accessible (ie from 'Output', where I need it)

          Below I have copied the essence of my code, which is how I would normally go about writing a plugin. It is commented so hopefully you can follow along. Specific questions are preceded by a Question.

          From what I gist from your reply, I do not need to copy all variables from 'node' to 'self', because I can access the node also from Output, but then it is called 'sh'? (I'm not even sure what 'sh', and 'node' refer to, maybe you could clarify thatQuestion)

          import c4d

          etc

          import c4d and other dependencies

          MODULE_ID = xxxxxxxx

          copy all values that are in interior.h for easy access in python

          INTERIOR_SCALE          = 1001
          INTERIOR_GRID           = 1002

          etc

          class Interior(plugins.ShaderData) :
              def __init__(self) :
                  # initialize all values
                  self.scale = Vector(0.5)
                  self.grid  = Vector(2.0)
                  
                  self.SetExceptionColor(Vector(1,0,0))
              
              def Init(self, node) :
                  # copy all values from self to 'node'
                  bc = node.GetDataInstance()
                  bc.SetVector(INTERIOR_SCALE, self.scale)
                  bc.SetVector(INTERIOR_GRID, self.grid)
                  
                  return True

          def PluginMessage(id, data) :
                  # don't really know what should go in here, but I had some issues
                  # with c4d crashing on reloading python plugins. 
                  # Question If you have better suggestion, I welcome them.

          if id==c4d.C4DPL_RELOADPYTHONPLUGINS:
                      return True
                  else:
                      return False

          def Message(self, node, id, msg) :
                  # Question Are you saying I don't need this function at all?
                  if (id==c4d.MSG_DESCRIPTION_POSTSETPARAMETER) :
                      bc = node.GetDataInstance()
                      # etc
                      return True
                  return False

          def InitRender(self, sh, irs) :
                  # just before rendering, I make sure the values in 'sh' are identical to those in node
                  # if grid and scale were NOT mutually dependent, I would do it like so:
                  bc = sh.GetDataInstance()
                  self.scale = bc.GetVector(INTERIOR_SCALE)
                  self.grid = bc.GetVector(INTERIOR_GRID)
                  # etc
                  #
                  # With the mutual dependence, I use the 'method 2' in my previous post
                  
                  return 0
              
              def Output(self, sh, cd) :
                  # In Output, I read the values from self, not from 'sh'
                  # Question Are you saying I could also read them from 'sh' directly? Like so:
                  #
                  # bc = sh.GetDataInstance()
                  # scale = bc.GetVector(INTERIOR_SCALE)
                  # etc
                  #
                  # instead of:

          scale = self.scale
                  # etc ?

          # the rest of the code is no problem
                  rgb = SomeFunction(cd, scale) # pseudocode

          return rgb
              
              def FreeRender(self, sh) :
                  return

          if __name__=='__main__':
              plugins.RegisterShaderPlugin(MODULE_ID, 'Interior Shader', 0, Interior, "interior", 0)

          1 Reply Last reply Reply Quote 0
          • H Offline
            Helper
            last edited by

            On 25/10/2016 at 09:07, xxxxxxxx wrote:

            Hello,

            well, a member variable is a member variable. I suggest you should get familiar which the basis of object orientated programming and especially how classes are handled in Python, see Classes.

            The "self" object is just a reference to the object (class instance) itself. It is used to access member functions or variables (or call it "data attributes").

            You should also understand the difference between your plugin class (based on TagData ) and the created tag instance (based on BaseTag ). See "General Plugin Information" in the C++ documentation. So for example in your "Init" function you receive the "node" argument which is the representation of you shader as a BaseShader (GeListNode to be precise). Therefore you can access its BaseContainer. But in the end it is the same "entity" as "self". You can always access the BaseContainer from the given "node" or "sh" etc.

            PluginMessage() is a global function that should be implemented in your plugin ( pyp file) to receive specific messages if needed. It does not have anything to do with a NodeData based plugin class.

            You code in "Message" makes no sense since the "bc" variable only exist inside the scope of this function.

            best wishes,
            Sebastian

            1 Reply Last reply Reply Quote 0
            • H Offline
              Helper
              last edited by

              On 25/10/2016 at 13:08, xxxxxxxx wrote:

              Hi Sebastian,

              Thank you for the clarifications. I think I am beginning to get a deeper understanding of what it means to write a plugin as opposed to a standalone program. If you write a standalone python program, you know what data you need and where you can find it. 
              But a plugin is part of a larger ecosystem (c4d) and you have to write functions that are called from the outside. Thus, you are no longer in full control, but you have to 'wait till you get asked'. Those functions have different names for the same arguments every time, like 'sh' and 'node', and I am beginning to see that these are just sub- and superclasses for the same object. Like a PolygonObject is a BaseObject but not vice-versa.

              "Your code in "Message" makes no sense since the "bc" variable only exist inside the scope of this function."
              But its not about bc, it's about self! (Remember I posted the full code in "Message" in my first post) The funny thing is that bc IS updated correctly, but self IS NOT , which is kinda weird. Could this be some threading issue? 
              However, although I still don't fully understand why it was not functioning correctly, I do understand why this works: Using the code in "Message" together with reading the values from 'node'/'sh' solves the issue. "Message" updates both variables instantaneously in the node, which I can then read in "Output".

              So thank you for your time and help!

              Regards,
              Hermen

              1 Reply Last reply Reply Quote 0
              • H Offline
                Helper
                last edited by

                On 26/10/2016 at 01:45, xxxxxxxx wrote:

                Hello,

                Threading shouldn't be an issue with any of this. If you have a standard parameter that is defined in your resource files than the value is stored in the BaseContainer. You don't have to do anything in Message() at all. Please notice that it is preferred to use GetParameter()/SetParameter() instead of accessing the BaseContainer. See Please use GetParameter/SetParameter and Set Tag-Parameter in R17.

                As said before, if you add a member variable to "self" you have to copy this in your implementation of CopyTo(). Only the BaseContainer is handled automatically.

                Please also use the CODE tags to format your code snippets. Thanks.

                best wishes,
                Sebastian

                1 Reply Last reply Reply Quote 0
                • H Offline
                  Helper
                  last edited by

                  On 26/10/2016 at 05:18, xxxxxxxx wrote:

                  My goodness Sebastian,

                  From the SDK:
                  NodeData.CopyTo(self, dest, snode, dnode, flags, trn)[](file:///Users/anique/Documents/C4D/SDKs/CINEMA4DR18020PYTHONSDKHTML20160831/docs/html/modules/c4d.plugins/BaseData/NodeData/index.html?highlight=copyto#NodeData.CopyTo)

                  Override - Called when a node is duplicated. 
                  We're having this conversation for two days now and you keep thinking I am copying some instance to another. I am not!
                  I wish I could post a picture in here, to avoid these miscommunications. Instead, follow these instructions:
                  -Open up c4d
                  -Add a material
                  -Swing it on an object
                  -Check the tags attributes in the attribute manager
                  -Now, set "Tiles U" to some value, let's say 2
                  -Notice how "Length U" instantly decreases to 0.5 !
                  _
                  _
                  This is the  behavior I am trying to emulate. "grid" should be the inverse of "scale", AND vice-versa. Now I don't see how CopyTo is gonna help me with that.
                  BTW, using the CODE tags seems a good idea. Where can I find it?

                  Regards,
                      Hermen

                  PS:
                  I think I found out on c4dlounge.eu:

                  this is pseudocode
                  #and more code
                  for i in range(10) :
                      do something()
                   
                  

                  Why doesn't this forum just have a button for that, just like over there?

                  1 Reply Last reply Reply Quote 0
                  • H Offline
                    Helper
                    last edited by

                    On 26/10/2016 at 08:32, xxxxxxxx wrote:

                    Hello,

                    you are creating copies of your shader. Or to be precise: Cinema will create copies of your shader. When you edit a parameter of your shader (or any other NodeData based plugin), Cinema will create a copy for the undo stack (as described in my first answer). While creating this copy, Cinema will call CopyTo(). This is needed when you have internal data, like a member variable.

                    Do you understand what "self" in the context of Python means? Do you understand what a construct like "self.something" is? Something like "self.something" is a member variable. And if your class wants to handle such a member variable correctly, it should handle it inside a implementation of CopyTo(). That is how Cinema works. This is why "bc" is updated and "self" not. You have to decide if you use "self" for your purposes or not.

                    If you want to change how a parameter is set or want to react when a parameter is set, this is typically done by implementing SetDParameter(). This was added to the Python API with R18. In this implementation you can define how the given parameter value is handled and if anything else should happen:

                      
                    def SetDParameter(self, node, id, t_data, flags) :  
                      
                      # check if the parameter with the ID 10002 was set  
                      if id[0].id == 10002:  
                      
                          # don't divide by zero  
                          if t_data == 0.0:  
                              t_data = 1.0  
                      
                          # inverse  
                          inv = 1.0 / t_data  
                      
                          # store given value in the BaseContainer  
                          bc = node.GetDataInstance()  
                          bc[10002] = t_data  
                           
                          # set inverse parameter  
                          node.SetParameter(10003, inv, c4d.DESCFLAGS_SET_0)  
                      
                          # inform Cinema that the parameter was successfully set  
                          return (True, flags | c4d.DESCFLAGS_SET_PARAM_SET)  
                      
                      return False  
                    

                    This example works completely with the BaseContainer returned by the given node and without any "self" which makes it much more easy to understand.

                    best wishes,
                    Sebastian

                    1 Reply Last reply Reply Quote 0
                    • H Offline
                      Helper
                      last edited by

                      On 26/10/2016 at 08:55, xxxxxxxx wrote:

                      Hello Hermen,

                      if you don´t use R18 and stick to member variables you can try this:

                        
                        def __init__(self) :  
                            self.scale = (c4d.Vector())  
                            self.grid = (c4d.Vector())  
                        
                        def CopyTo(self, dest, snode, dnode, flags, trn) :  
                            data = snode.GetDataInstance()  
                            scale = data.GetVector(INTERIOR_SCALE)  
                            grid =  data.GetVector(INTERIOR_GRID)  
                            dest.scale = scale #this updates the self.scale member variable  
                            dest.grid = grid  
                            return True  
                        
                        def Message(self, node, id, msg) :  
                            if (id==c4d.MSG_DESCRIPTION_POSTSETPARAMETER) :  
                                data = node.GetDataInstance()  
                                if msg['descid'][0].id == INTERIOR_SCALE:  
                                    scale = data.GetVector(INTERIOR_SCALE)  
                                    if scale.x == 0:  
                                        scale.x += 0.00000001  
                                    if scale.y == 0:  
                                        scale.y += 0.00000001  
                                    if scale.z == 0:  
                                        scale.z += 0.00000001  
                                    data.SetVector(INTERIOR_GRID,c4d.Vector(1.0/scale.x, 1.0/scale.y, 1.0/scale.z))  
                        
                      

                      best wishes
                      martin

                      1 Reply Last reply Reply Quote 0
                      • H Offline
                        Helper
                        last edited by

                        On 27/10/2016 at 13:05, xxxxxxxx wrote:

                        Hi all,
                        Thank you for your answers and code examples. I think the light has finally dawned on me.
                        @ Sebastian: I apologize for my grumpiness😊. To answer your question, I would say that 'self' refers to the class instance in Python. But I assumed that this class instance would be alive as long as the node lives. But after reading your reply a number of times (and having some good coffee:) I came up with the idea to print self from "InitRender", and indeed,   a different instance is created every time a user variable is **  changed**! Knowing that, everything you said makes sense, and I understand the importance of CopyTo().

                        I am not hung up on keeping 'self', it's just out of habit and what I've seen other people doing. So I've switched to use the elements' base container. I am also on R18, so I guess the correct way to implement the mutual dependence is through "SetDParameter", but I can confirm that monkeytack's "Message" works well (with the added benefit of backward compatibility).

                        I wasn't aware of the short-lived nature of a class instance in c4d (and I guess many people who are familiar with python with me) but it's pretty crucial knowledge for plugin development.

                        Best regards,

                        Hermen

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