Struggling with Messages [SOLVED]



  • 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



  • 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



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

    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 that)

    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. 
            #  If you have better suggestion, I welcome them.

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

    def Message(self, node, id, msg) :
            #  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'
            #  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)



  • 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



  • 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



  • 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



  • 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?



  • 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



  • 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



  • 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


Log in to reply