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

    UuidInterface.ToString() raise a TypeError.

    Cinema 4D SDK
    windows python 2024
    2
    3
    515
    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.
    • DunhouD
      Dunhou
      last edited by

      Hi there,

      I had test of maxon.UuidInterface and the ToString() seems not work.

      return:

      14014181541439318986
      Traceback (most recent call last):
        File "scriptmanager", line 5, in <module>
        File "C:\Program Files\Maxon Cinema 4D 2024\resource\modules\python\libs\python311\maxon\decorators.py", line 553, in NativeDataOrBuiltin
          return ExecMethod(self._data, *args)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      TypeError: net.maxon.interface.uuid.ToString@a73a45c584c879d4() takes exactly 1 argument (0 given)
      

      code:

      import maxon
      
      uid = maxon.UuidInterface.Alloc()
      print(uid.GetHashCode())
      print(uid.ToString())
      

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

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

        Hey @Dunhou,

        Thank you for reaching out to us. You have stumbled here upon something I have stumbled myself more than once, the weird errors when you try to instantiate the "wrong class" in Python. There are less obvious cases, you for example must instantiate a maxon.Vector in Python and cannot instantiate a maxon.Vec3 (which actually holds all the documentation) - it can be quite confusing but has been declared as intentional by the developers.

        TLDR: You did use the incorrect type to instantiate the Uuid, you must use the reference, not the interface. So there is no bug here, the error messages are just a bit cryptic.

        Atomic Type and Memory Management

        When you dip your toes into the maxon API, you have to understand a central concept, interfaces and references. Types postfixed with the word Interface, e.g., maxon.UuidInterface, maxon.FileAssetInterface, or maxon.GraphModelInterface, realize a type in the object system of Cinema 4D, i.e., derive from maxon.ObjectInterface. ObjectInterface is quite similar to Python's own object as it implements the atomic type for 'things' in the API (to get a better grasp of the type you should look at its C++ version as the Python one is mostly a sealed wrapper).

        The object system realizes functionalities one would expect from an object system, like comparing objects, hashing them, getting strings representing them, or poking around in the type metadata of an instance, i.e., it is quite similar to what Python's object offers. And just like Python's object, ObjectInterface also comes with the concept of memory management.

        Many modern programming languages use the concept of automated memory management. Because in so-called unmanaged languages, e.g., C++, you have to handle your memory manually when you want something to live longer than the function scope it is allocated in. But this manual memory management is very error prone, as it produces the infamous memory leaks when we forget to deallocate the memory we have allocated.

        Automated memory management as realized by languages like Python, Java, or C# tries to solve this problem by not asking the user to handle memory deallocation anymore and instead automating it. There are several strategies for this - which are also not mutually exclusive, as Python employs both ideas of garbage collection and reference counting for example - but the for us relevant is here reference counting. Cinema 4D also realizes reference counting with its interface and reference system.

        Objects and References

        Reference counting implies a system of objects and references, i.e., the actual data and references to it. So, when we have this Python code we can understand the concept of objects and references:

        class User: # Could also write `class User (object)` when we want to be more verbose.
            def __init__(self, name: str, friend: "User" | None = None) -> None:
                self.Name: str = str(name)
                self.Friend: "User" | None = friend
        
        # Both statements (user_1, user_2) do two things: 1. They (heap) allocate a `User` object and put
        # it into the Python object table. 2. They draw a reference to that object and assign it to the
        # "variable" the user created (user_1, user_2). So, these variables do not actually hold the object
        # but only a reference to it. The second statement (user_2) then also passes the reference to "Alice"
        # to its `User` constructor, and with that draws a new reference.
        
        user_1: User = User("Alice")
        user_2: User = User("Bob", user_1)
        
        # Reference count at this point: 
        # "Alice": 2 (user_1, user_2.Friend)
        # "Bob": 1 (user_2)
        
        # This means even when the reference user_1 is being deallocated, the "Alice" object will not be 
        # deleted because "Bob".Friend still holds a reference.
        

        So, the variables we assign the User objects to do not actually hold the object but only a reference to it. Which also means that when we have two references of one object we do not need twice the memory but only must pay the price for another reference. We can look at the same example in C#:

        public class User
        {
            public string Name;
            public User Friend;
        
            public User(string name, User friend = null)
            {
                Name = name;
                Friend = friend;
            }
        }
        
        User user1 = new User("Alice");
        User user2 = new User("Bob", user1);
        

        Conceptually things are very similar here. We again create objects and draw references to them, the new keyword in C# makes it more clear where a new object is allocated and where not. Other than Python and the maxon API, C# is not using reference counting for its memory management but a scope based garbage collector. But the concept of objects and references is independent of the concept of reference counting.

        Interfaces and References

        The maxon API operates by the very same principles of objects and references, and implements its garbage collection via reference counting (there are some differences to Python's version of reference counting which I will ignore here). The major difference to Python, C#, Java, etc. is that the maxon API exposes types for both the reference (which we assign to variables) and the actual object (which would live in magic object table land in Python). An interface, e.g., maxon.GraphModelInterface realizes the actual object which is then accompanied by a reference counterpart, in this case maxon.GraphModelRef. So, in code you usually deal with the references and not with the actual object. The reference can do everything the object/interface can, i.e., has access to all its functions.

        And because that all its not yet confusing enough term-wise πŸ˜„ , the maxon AP then also makes a distinction between COW (Copy-on-Write) and non-COW interfaces in its naming conventions. To understand this, we will have a look at our user example again:

        # We allocate a User "Alice" and draw two references for it.
        user_alice: User = User("Alice")
        user_current: User = user_alice
        
        # ...
        
        # We now hit a condition in our code where we want to rename our current user to "Bob".
        if condition:
            user_current.Name = "Bob"
        
        # But since we operate here on one object, this will also change the name of the reference user_alice.
        print (user_alice.Name) # Bob
        

        References have the risk or advantage (depending on the point of view) that we deal with shared data. In some cases, for example our User model, we could declare this a feature, in other cases not so much. Strings for example are also references in Python (and many other languages). When we have ten variables with the string "Hello World!", then there will be exactly one string object for this in the Python object table. When we now edit the string of one of the variables, it would be rather odd and not useful, if the string of the other variables would also change.

        Python labels this as immutable types in its type system, which then leads to confusing statements like that str and int (in the interval [-5, 256]) are immutable. What Python's means with that, is that it will not try to write to the referenced object for these types when a write operation occurs but instead create a new object (so that the data for other reference holders of the original object does not change).

        a: str = "foo"
        b: str = "foo"
        print(id(a) == id(b)) # Prints "True", as a and b reference the same object.
        
        # When we now try to write to the object referenced by #b, something new happens, we do not also 
        # change the value for all other reference holders. That is because strings are in Python "immutable"
        # or in other words, copy-on-write, i.e., we can never edit an existing string object but rather must
        # draw a new copy. Python's immutability system is quite complex and I am ignoring some details here.
        b = "bar"
        print (a, b) # Prints "foo bar"
        print(id(a) == id(b)) # Prints "False", a and b are not the same object anymore.
        

        COW (Copy-on-Write) is pretty much the same thing as Python's immutability system. In the C++ maxon API it is for once expressed when an interface is declared with MAXON_REFERENCE_COPY_ON_WRITE (Link). Another marker is the a bit confusing naming scheme of references.

        • Objects that will not be copied when the user tries to edit data end in Ref, e.g., GraphModelInterface and GraphModelRef.
        • Objects that will be copied when the user tries to edit them (COW), do not end in Ref, e.g., UrlInterface and Url or UuidInterface and Uuid. Do not ask me why we did it like that, I find it confusing too.

        Other than in Python, where the selection of immutable types in that sense is quite limited, there are quite a few in the maxon API. Many (but not all) things that derive from maxon.Data are COW: Urls, uuids, strings, colors, vectors, gradients and many more. So, that an asset has for example the type maxon.Asset and not maxon.AssetRef indeed means that assets are copy-on-write.

        Using References and Interfaces in the maxon API

        Using the system is then fairly straight forward. To allocate a new instance, one must use one of the Create methods found on the reference. The Allocate methods found on the object/interface are backend methods for allocating the actual object which users most of the time do not have to use.

        import maxon
        
        language: maxon.LanguageRef = maxon.LanguageRef.Create()
        

        Creating references for non-COW, i.e., mutable, objects in this manner is however not too meaningful in Python at the moment, as you are usually passed the references by some helper function (find a node in a graph, get the active asset database etc.). More meaningful is the COW case. It is the same here, but here we have actually Create functions which allow us to pass data.

        import maxon
        import uuid
        
        # Create a Uuid instance without any arguments.
        a: maxon.Uuid = maxon.Uuid.Create()
        print(f"{a = }")
        # > a = maxon.Uuid object at 0x7fd11ae1bfa0 with the data: 667FBB24-4ADE-451B-823B-6DC5996F04FB
        
        # Create an Uuid using Python's uuid module and pass its string to the Create constructor.
        myUuid: str = str(uuid.uuid4())
        b: maxon.Uuid = maxon.Uuid.Create(myUuid)
        print(f"{b = }, {myUuid = }")
        # > b = maxon.Uuid object at 0x7fd11ae21760 with the data: CB81AF5D-4535-42C8-9A91-7F821441CB28, 
        #   myUuid = 'cb81af5d-4535-42c8-9a91-7f821441cb28'
        

        Last but not least, there are usually also wrappers for Create to use the more Pythonic __init__ instead. E.g.:

        import maxon
        import uuid
        
        a: maxon.Uuid = maxon.Uuid()
        b: maxon.Uuid = maxon.Uuid(str(uuid.uuid4()))
        
        print (a)
        print (b)
        
        # > E8C2DD7D-8768-4C52-8F39-193574C1761E
        # > D4480A8E-CB0C-4B42-B1F9-AE38B6B8BA86
        

        Conclusion

        • The maxon API realizes an object-reference system for memory management.
        • As a user, the reference is usually the side we work with, it is equal to what we perceive in Python or C# as "the object".
        • Mutable object references are postfixed with "Ref" while immutable objects, i.e., COW-references do not have any postfix and instead just drop the "Interface" from the object name.
        • Objects are effectively allocated with the Create functions found on refereences. They do the same as what object allocation does in Python, Java, C#, etc. does, they allocate the object and return a reference to it (and hide the actual object instance away from us). The Allocate functions found on interfaces are only for hyper-experts useful.

        β„Ή Tip: When I write Python code in the maxon API for myself, I tend to deliberately type-hint objects incorrectly. I.e., I type hint foo: maxon.FooInterface = data.Get() even though Get technically returns a Foo or FooRef. The advantage is that we will now see autocompletions for the interface (where all the important methods and fields are defined) and not for the reference (where almost nothing is defined). References provide access to all members of their interface, but since interface and references are not in an inheritance relationship, the autocomplete does not see that.

        Cheers,
        Ferdinand

        MAXON SDK Specialist
        developers.maxon.net

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

          Wow @ferdinand!

          What a super detailed and super useful information ! Always learning great things from your super user-friendly answers for rookies like me πŸ‘

          Memory Management is very useful to understand what happened here, I will read it again tomorrow after work, hope I can have a deeper understanding then😊

          About the Interfaces/References/Ref class name, super useful information, I always confused them and thought it was an inheritance relationship or something like that, it is much cleaner now!

          Thanks for your typing tips here, I had learn this by testing your answers and codes (In fact, most of my programming skills are learned through studying your code, and I am truly grateful to you)

          Last, thanks or your times during the meeting time!

          Cheers~
          DunHou

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

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