c4d.ObjectData plugin code execution sequence



  • Hi
    I'm developing an ObjectData deformer. I need to have some global variables in there, but I found some difficulty getting them right. I didn't find a clear explanation on how the plugin code is executed, so I'm struggling.

    I tried to declare variables needed in the beginning of the file:

    doc = c4d.documents.GetActiveDocument()
    fps = doc.GetFps()
    
    fC = None
    fP = None
    frameStored = 0
    if fP != None:
        velocity = 1
    else:
        velocity = 0
    

    Some are working (the first two) but when I call for frameStored from the modifier class I get:

    UnboundLocalError: local variable 'frameStored' referenced before assignment
    

    So I thought to go the other way. A dedicated class to store and calculate some global vars, it's simple:

    class storage():
        fC = None
        fP = None
        frameStored = None
        if fP != None and fC != None :
            velocity = 1
        else:
            velocity = 0
    

    I create an object of that class inside of the plugin class (I use your handy example as a base so it's called so :D)

    class SpherifyModifier(c4d.plugins.ObjectData):
        store = storage()
    

    But store var becomes undefined 0_0
    If I try "storage.frameStored" I can read and write that value! But it's wrong as there should not be an instance of the class! I didn't create it!
    At the same time when I try to create it nothing works

    But more is further - when something is calculating inside the class or in global space it does not update
    For example, "velocity" is always 0, even when fC and fP are not "None" anymore but Vector type (I checked it numerous times)

    I though the code itself is executed once when loading plugin and than Cinema calls the methods of c4d.plugins.ObjectData subclass as it needs. But it turns out the sequence is different?

    I really can't get how it works. Can somebody lead me to an explanation please?



  • Hi,

    @intenditore said in c4d.ObjectData plugin code execution sequence:

    doc = c4d.documents.GetActiveDocument()

    never do this in a NodeData plugin, as the the active document will not always be the document your node is in (for rendering the document is getting cloned for example). Either use the document argument passed to its methods or get it from the passed nodes (via BaseList2D.GetDocument()).

    You also seem not to be aware of the scope of your attribute declarations.

    # coding: utf-8
    class MyClass(object):
        """ A type with one attribute bound to the class and one to the instance.
        """
        my_class_attribute = "Yay, I'm bound to a class."
    
        def __init__(self):
            """
            """
            self.my_instance_attribute = "Whee, I'm bound to an instance."
    
    # What does that mean? Well, an attribute bound to a class is shared between
    # all instances of a class. In fact we even do not need an instance to
    # access it.
    print MyClass.my_class_attribute
    
    # We cannot do the same with an attribute bound to the instance of a class
    try:
        print MyClass.my_instance_attribute
    except AttributeError as e:
        print e
    
    #A class attribute is shared between all instances.
    
    a = MyClass()
    b = MyClass()
    
    print "a:", a.my_class_attribute
    print "b:", b.my_class_attribute
    
    MyClass.my_class_attribute = "whoops"
    
    print "a:", a.my_class_attribute
    print "b:", b.my_class_attribute
    
    
    Yay, I'm bound to a class.
    type object 'MyClass' has no attribute 'my_instance_attribute'
    a: Yay, I'm bound to a class.
    b: Yay, I'm bound to a class.
    a: whoops
    b: whoops
    [Finished in 0.1s]
    

    Long story short: Do not use class attributes unless you want to share the attribute between all instances.

    Cheers,
    zipit



  • hi,

    Your object data will not be unique so unless you want to share data between instances there's no really need of using a global variable.
    Thanks @zipit again for your help :)

    @intenditore in your case, i would probably create a list of storage (class that you have created). Store that list in your objectdata as a class variable.

    On a side note, if you are going to store data inside your ObjectData you should implement the function CopyTo. That imply you should implement Read and Write also.

    Cheers,
    Manuel



  • @zipit said in c4d.ObjectData plugin code execution sequence:

    never do this in a NodeData plugin, as the the active document will not always be the document your node is in

    Useful info, thank you! So, I see there's no other way than declaring and calculating all the values in ModifyObject(), sad. It takes away some bit of organization
    I'm not really a huge fan of OOP and still a Python noob so I messed up here, yeah. You answer clarified much more in this concept than all the classes (:D) I took

    @m_magalhaes said in c4d.ObjectData plugin code execution sequence:

    Your object data will not be unique so unless you want to share data between instances there's no really need of using a global variable

    I'm slightly confused here. You mean ObjectData is recreated each frame?
    The problem I seem to stumble is that ObjectData seem to know only what's happening in one frame and no other (that's what you told if I got ir right) and each time it recreates my variables if I declare them there. Logically enough, the same happens if I declare them as global. But if I declare them before the class as global ones for the whole script, they aren't accessible and/or writable

    "UnboundLocalError: local variable 'fP' referenced before assignment"
    

    @m_magalhaes said in c4d.ObjectData plugin code execution sequence:

    i would probably create a list of storage (class that you have created). Store that list in your objectdata as a class variable

    So, to use the class as a "dummy" object to store the data as I did? The problem is the calculations inside don't work for some reason...



  • @intenditore said in c4d.ObjectData plugin code execution sequence:

    @zipit said in c4d.ObjectData plugin code execution sequence:

    I'm slightly confused here. You mean ObjectData is recreated each frame?

    No an instance is created for each object.

    "UnboundLocalError: local variable 'fP' referenced before assignment"

    You should read what I have posted above. You have a crucial misconception on the scope of your attributes. But I assume you are trying to do something like this (which will raise said exception):

    class Foo(object):
        """ This is probably what you are doing.
        """
        fp = 1
    
        def bar(self):
            """fp is not defined in this scope.
            """
            fp += 1
            return fp
    
    foo = Foo()
    print foo.bar()
    """ Will raise:
    UnboundLocalError: local variable 'fp' referenced before assignment
    [Finished in 0.1s]
    """
    

    You can access the class bound attribute in your method, but this is very likely not what you want.

    class Foo(object):
        """ This is what you could do to make a class bound attribute work.
        """
        fp = 1
    
        def bar(self):
            """ This works, but is probably not what you want, since fp is 
            shared by all instances of Foo. So it will be incremented for all
            instances.
            """
            Foo.fp += 1
            return Foo.fp
    
    foo = Foo()
    print foo.bar()
    
    """ will print: 2
    """
    
    foo2 = Foo()
    print foo2.bar()
    
    """ will print: 3
    """
    

    This is what you probably should do.

    class Foo(object):
        """ This is what you probably should do.
        """
        def __init__(self):
            """ Define fp as an attribute bound to the instance.
            """
            self.fp = 1
    
        def bar(self):
            """
            """
            self.fp += 1
            return self.fp
    
    foo = Foo()
    print foo.bar()
    
    """ will print: 2
    """
    
    foo2 = Foo()
    print foo2.bar()
    
    """ will print: 2
    """
    

    The same will apply if you encapsulate your data in your own type (which seems wildly unnecessary from what you have shown here).

    Cheers,
    zipit



  • hi,

    You define a class. For using it, you create an instance of that class.
    Data can be declared and stored in the class or in the instance.

    If the data is stored in the class, it will be the same for each instance.
    If the data is stored in the instance it will be different for each instance.

    If the data is declare in a function, it will be only available in that function.

    Here's a script that use setter and getter to change the class attributs. (cls.something) so they will change the data stored in the class
    The init function use the instance data.(self)

    In python you can have access to attribut of instance but not class. So be careful.

    # this example show how things can go wrong
    import c4d
    
    class myData (object):
        _position = "position default"
        _velocity = "velocity default"
        
        def __init__(self, position_in = None, velocity_in = None):
            if position_in is not None:
                self._position = position_in
            if velocity_in is not None:
                self._velocity = velocity_in
        
        @classmethod
        def GetVelocity(cls):
            return cls._velocity
        
        @classmethod
        def SetVelocity(cls, velocity_in):
            cls._velocity = velocity_in
    
    
    
    def main():
        
        
        myInstanceA = myData()
        myInstanceB = myData("instance B", "instance B" )
        myinstanceC = myData()
        
        print "------------------------------------------"
        print myData._position, myData._velocity ,"class"
        print myInstanceA._position, myInstanceA._velocity, "instance A"
        print myInstanceB._position, myInstanceB._velocity, "instance B"
        print "------------------------------------------"
        
        print myInstanceA.GetVelocity(), myInstanceA._velocity
        myInstanceA.SetVelocity("changnig velocity with setter A")
        print myInstanceA.GetVelocity(), myInstanceA._velocity
        print "------------------------------------------"
        
        print myInstanceB.GetVelocity(), myInstanceB._velocity
        myInstanceB.SetVelocity("changnig velocity with setter B")
        print myInstanceB.GetVelocity(), myInstanceB._velocity
        print "------------------------------------------"
        print myinstanceC.GetVelocity(), myinstanceC._velocity
        myinstanceC._velocity = "changin the velocity without setter"
        print myinstanceC.GetVelocity(), myinstanceC._velocity
    
    # Execute main()
    if __name__=='__main__':
        main()
    

    That said, you are creating a deformer. In the Init function you can init your data for the instance. (self.mydata = "something")
    In the modify function you will be able to retrieve that data with self.mydata. change it and use it. It will be available for the next frame.

    It's a deformer, you have attached it to an object. In cinema4D, you clone that object.
    What cinema4D will do is to create a new instance of your deformer. Call the Init function and the CopyTo function. The CopyTo function (that i told you to implement) will do something like dst.myData = src.myData.
    So the copy will be the same with the same data.

    The Read and Write functions are used when you save or open a c4d file, so they must be implemented also.

    Is it more clear ?

    Cheers,
    Manuel



  • Oh, mindbending. My previous main language was php and there's no such thing (you can create an analogue with static though). So I thought it works very differently. Sorry I forced you to teach me basics :D
    Python is painful for guys like me
    Seems I now get how it works. Thank you all a lot!



  • hi,

    without further feedback i'll set this thread as solved tomorrow.

    Cheer,
    Manuel