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

    Identity of C4D objects in Python

    Cinema 4D SDK
    4
    6
    1.4k
    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.
    • CairynC
      Cairyn
      last edited by

      Hello again;

      here's an observation that I find interesting. Python and C++ use different object storage concepts (e.g. a simple type like integer is actually a reference in Python but a value in C++), so whatever object is constructed in C++ cannot be used 1:1 in Python (no memory mirroring).

      That means for the Python API, every object returned by a function cannot be identical to the C++ object that C4D itself uses. Python would not understand the memory allocations there. So, the API function must return a proxy object that provides access for Python to the actual C++ objects. (Right?)

      Here's a test (sorry for using the GeListHead again, this topic has nothing to do with my previous one):

      import c4d
      
      def main():
          obj = op
          prevHead = None
          print "---------------"
          while obj != None:
              head = obj.GetListHead()
              print type(head), id(head), head
              if prevHead != None:
                  print prevHead == head, prevHead is head
              else:
                  print "None"
              prevHead = head
              obj = obj.GetNext()
      
      if __name__=='__main__':
          main()
      

      The script goes through some objects in the Object Manager which are all on the same tree level, starting with the selected one, and check the GeListHead for each. The result is compared with the previous GeListHead by equality comparison == and identity comparison is.
      The output looks like this:

      ---------------
      <type 'c4d.GeListHead'> 1809691147248 <c4d.GeListHead object at 0x000001A559FF7BF0>
      None
      <type 'c4d.GeListHead'> 1809691146800 <c4d.GeListHead object at 0x000001A559FF7A30>
      True False
      <type 'c4d.GeListHead'> 1809691146928 <c4d.GeListHead object at 0x000001A559FF7AB0>
      True False
      <type 'c4d.GeListHead'> 1809691147248 <c4d.GeListHead object at 0x000001A559FF7BF0>
      True False
      >>> 
      

      Theoretically, the GeListHead should be the same object - identical, at the same address in memory - for all objects, as the objects belong to the same tree structure. That is not the case: the type is correct, but Python's own id() function returns a different number, and the memory address printed by the plain object printout is also different. These numbers repeat after three iterations, presumably because the garbage collection has destroyed the Python proxy by then and is reusing its properties.

      It is logical that the equality test returns True (as the object is identical on the C++ side) but the identity test returns False (as the proxy object is different)... at least, it is logical in this interpretation.

      Unfortunately, the objects should be identical too. If we were working in C++, the plain pointers would be the same, referencing the same object. The Python API adds an abstraction level which destroys the identity relation. I guess it would be necessary for the API to check for existing proxys and reuse those. (In the sample code, this would prevent the garbage collection from destroying the proxy until the very end of the script, as there would always be a reference to it through prevHead.)

      Is that interpretation of mine even correct? I'm kind of reverse engineering here...

      So, this raises first the question how to check for identity of two C4D objects from different references. The BaseObject has GetGUID() available for comparisons; a GeListHead as used here is no BaseObject though, and doesn't provide a unique ID.

      Second, are there any more Python development details we have to be careful with, because the Python/C++ interface may introduce unforeseen difficulties?

      e.g. memory allocation: C++ has an explicit allocation, while Python uses a garbage collector. Python will destroy an object when there are no more references to it (which in turn may destroy other objects that become non-referenced). Will that properly happen to a C4D object too? Like, I Remove() a BaseObject from a tree and then the variable where I store it goes out of scope... I haven't found evidence to the contrary, but it must be hellishly complicated to count references if the code changes between C++ modifications and Python modifications to the "same" object when methods of a Python plugin are executed.

      e.g. destructor calls: when is the C4D object actually destroyed? The garbage collector may destroy the Python proxy later (C++: immediately) when it happens to run. Is the destructor of the C4D object called when the proxy is destroyed, or earlier (when the reference counter drops to 0), or later? If there are dependent objects (like tags for a BaseObject), when are these destroyed (provided the BaseObject C++ destructor takes care of them)?

      e.g. Generator functions: these keep their state and execution point between calls, so all variables are preserved; including any proxy objects handling C4D objects. These are obviously vulnerable to changes between calls (okay, that's a bad example as Python has the same issue).

      I've trying to find details on the C4D/Python interfacing in the Python manual but can't locate any deeper information.

      1 Reply Last reply Reply Quote 0
      • P
        PluginStudent
        last edited by

        One way to "identify" objects in a C4D scene is to use the position in the object tree. That is what the "unique IP" system is using (GetUniqueIP(), GetUniqueID()).

        CairynC 1 Reply Last reply Reply Quote 0
        • ferdinandF
          ferdinand
          last edited by

          Hi,

          yes, that is an undocumented oddity of the Cinema 4D SDK. To test nodes for identity you have to use the equality operator and an equality comparison is performed by comparing the data containers of the nodes.

          One related and rather annoying side-effect of this memory location peek-a-boo is that almost all types in Cinema are not hashable, i.e. do not implement __hash__, even those where you would not really expect it, like for example c4d.Vector (although vectors are testable for identity via is).

          They probably should document hat more thoroughly.

          Cheers,
          zipit

          MAXON SDK Specialist
          developers.maxon.net

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

            Hi @Cairyn you are right, our Python Object is in most of the time a holder of a C++ object pointer, and copy the python Object actually copy the pointer, not the pointed object which is way more optimized.

            I would like to say that trust something on the pointer even in C++ is not really safe as Baseobject or GeListHead object can change. So that's why it recommended sticking to GetGUID/GeMarker/UniqueIP.

            And all of this stuff is accessible through python, for GeMarker you can retrieve a BitSeq with C4DAtom.FindUniqueID(c4d.MAXON_CREATOR_ID) see Layers with the same name?.

            For more information about how to identify objects, I let you read about Why are GUIDs not globally unique?.

            But I would say in Python you have the same tool than in C++ except that you can access raw data, but you shouldn't trust pointer as they can change a lot (e.g. GetActiveObject() before and after an undo will not return the same pointed BaseObject).

            Cheers,
            Maxime.

            MAXON SDK Specialist

            Development Blog, MAXON Registered Developer

            CairynC 1 Reply Last reply Reply Quote 0
            • CairynC
              Cairyn @PluginStudent
              last edited by

              @PluginStudent said in Identity of C4D objects in Python:

              One way to "identify" objects in a C4D scene is to use the position in the object tree. That is what the "unique IP" system is using (GetUniqueIP(), GetUniqueID()).

              Thanks for the suggestion - GetUniqueIP() is only available in BaseObject though. As far as I understand it, it is meant for the use with generators, so it's probably not "universal" enough to be used in arbitrary situations.

              1 Reply Last reply Reply Quote 0
              • CairynC
                Cairyn @m_adam
                last edited by

                @m_adam Thank you for the confirmation and the reading suggestions.

                Just for context, this is a general conceptual question and not connected to a specific code issue. I noticed the behavior while doing some test scripts with id() and is. It is worth noting that these Python properties need to be used carefully in concert with C4D objects.

                (I wouldn't exactly recommend using pointer comparisons in C++ either 😉 )

                I will include an "advanced" chapter in my Python/C4D book to mention this.

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