Python: Global Variable vs Plugin ID
-
Hi all,
I'm trying to understand some basic concepts on good programming practices in c4d.
When we want to save a persistent data I was advised to use a plugin id that is attached to my python object.
What is the drawback of using global variables instead?
In my tests global variables seem to work, so what is the plugin id offering that the global variables don't?My example case is manually storing a matrix array in a variable so I can compare it and see if it changes, since the GetDirty fucntions seem to be problematic for that particular object.
-
@orestiskon
You might have been confused by the advice given.
Persistent data means when you want to store information into an "object", viaBaseContainer
(or possible other means) THEN it is advised to use a plugin ID in order to provide for a unique storage identification. With "object" being a scene file, a polygon mesh, or primitive, a material, shader, tag, ...Global variables, while potentially useful in certain cases, are not persistent. Same goes for local variables. This means, if you close and restart Cinema4D, the value will not be maintained. Unless you specifically store that value in a scene file, or separate file ... in other words: making it persistent.
I guess this whole discussion might better be located in the "General programming & plugin discussions".
-
Thank you for the reply.
The idea is to save a value beyond the limits of the main function of a python generator, and Global Variable seems to achieve it.
Saving the value when saving the scene isn't important in this case, as the purpose is performance. On load it would re-evaluate the generator anyway.
So I'm trying to understand when global variables fail, and we need to use a plugin id instead. Is the only benefit of the plugin id that it can save the data with the file? -
I am not exactly sure how it works in Python but I will give you a run down of how Global Variables work in C++.
If you set a global variable in C++, and you are using it in a Generator Object, then that variable will be exactly the same in every object in every document that you have open. So if you change the value of it on any object (again in any document you have opened) it will be changed across everything. That is the definition of a global variable. It is global, it is the same everywhere.
Now it may not work exactly like this in python depending on how it is setup in C4D. But in general it is a bad idea to use this for ay variable in something like a generator object.
So even though it might be working for now in a basic test scene, it will come back to bite you once you use it for anything more complex, such as two or more objects.
It all comes down to what it is that you are trying to achieve and what this data is that you are using in a global variable.
In C++ you could use a SceneHook to hold scene wide data. But I don't think that is in Python yet, so the best option to use scene wide data is to use a PluginID as your way to access that data in the preferences for your scene.
For storing a matrix value to compare why don't you just use a local variable in the generator object itself?
Also I would avoid using the word "Save" when you discuss this data, since "saving" and "persistent" mean writing data to disk. And that is not what you are doing. But if you did want to save the value then just make the matrix a description in the resource files for your object generator and access it using the BaseContainer. Then it will be saved automatically.
Cheers,
Kent -
Actually, Python works differently from C++. A "global" variable there is only global for the current execution context. In Python, variables are not declared; Python creates them when they are first used ("first" meaning, there is no such variable in the current execution context already).
Where a C++ global variable is created at compile time and represents one location in memory throughout, Python globals are created at runtime in their context. Cinema 4D has one context per Python element, so if you are accustomed to C++ globals, the following will irritate you:
- Create two objects with a Python tag
- Write the following code into the tags:
import c4d def main(): global x if doc.GetTime().GetFrame(doc.GetFps()) == 0: x = 1 print "Tag 0 x set to 1" else: print "Tag 0 x = ", x x += 1
for one tag, and
import c4d def main(): global x if doc.GetTime().GetFrame(doc.GetFps()) == 0: x = 0.5 print "Tag 1 x set to 1/2" else: print "Tag 1 x = ", x x += 1
for the other. Obviously, the first tag counts on full integers and the second on halfs, both are reset on frame 0.
The output amounts to
Tag 0 x set to 1 Tag 1 x set to 1/2 Tag 0 x = 1 Tag 1 x = 0.5 Tag 0 x = 2 Tag 1 x = 1.5 Tag 0 x = 3 Tag 1 x = 2.5 Tag 0 x = 4 Tag 1 x = 3.5 Tag 0 x = 5 Tag 1 x = 4.5 Tag 0 x = 6 Tag 1 x = 5.5 Tag 0 x = 7 Tag 1 x = 6.5 Tag 0 x = 8 Tag 1 x = 7.5 Tag 0 x = 9 Tag 1 x = 8.5 Tag 0 x = 10 Tag 1 x = 9.5
Obviously, although both tags claim a
global x
, they access differentx
's. That's because their contexts are different. Which also means that they cannot exchange data through globals this way. (You can try with an external module that you import, but I don't have time to verify that at the moment.)If all you want is to store a value between executions of a tag, this is easy to implement this way. However:
- You must make sure that Python creates the variable at some point. Here, I did that through a first value setting at frame 0. Which also means that every time the timeline passes through 0, the value is reset.
- You must pay attention to the evaluation of the object tree. I chose a tag here since the tag is evaluated with the tree every time. That is NOT the case for a generator, as there are caching mechanisms at work.
- You must take care of situations like "jumping around in the timeline", replaying backwards, setting the time explicitly through some other plugin, and such.
-
Hi while previous answers give some hints I would like to provide more in-depth answers to these questions.
First, what is a global variable?
In Python, a global variable is a variable that is stored in the dictionary returned by "globals()".
The globals() function returns a dictionary that is local to the current Python Scope.
Note that a Python scope only lives for a given time and is not persistent.
This means it doesn't save its state or current data to a file so if you restart Cinema 4D, it starts with a fresh new Python Scope) but as long as the host object of the Python Scope is alive the data will be stored.So few things to know about Python implementation within Cinema 4D.
Each BaseList2D that implements a Python implementation (Python Generator, Python Scripting Tag, Python Xpresso, Field Layer) has its own Python Scope.
That means if I have two Python Generator they don't share the same Python Scope. But they both have a distinct one.Now in a case of a plugin, it's a bit tricky, if you use a global variable let's say in an ObjectData.
The global variable will be available for all instances (BaseObject) of this ObjectData. This is because a BaseObject is an instance of an ObjectData implementation.
And since there is only one implementation (your ObjectData that uses the global variable) they all use the same global variable.Now when you refer to plugin ID, it's a kind of term abuse.
A plugin ID is a unique ID (aka a number who is unique into Cinema 4D context).
And can be retrieved from https://developers.maxon.net/forum/pid (you must be logged).So a Plugin ID can be used to register a plugin.
When you write BaseObject(100000) Cinema 4D will look at the ObjectData that is registered at the ID 100000 and create a BaseObject that will implement this particular ObjectData.
But you can also use a PluginId (or aka a Unique ID) to register a data into a BaseContainer.But what is a BaseContainer?
BaseDocument, Objects, tag and pretty much everything a user can find in C4D inherit from BaseList2D, and get a BaseContainer.
A BaseContainer is a kind of array (or a dictionary in python), where data are stored according to a given ID.
This BaseContainer is owned by the host BaseList2D (seems logical but it's an important point).
This way Cinema 4D has a generic way of storing and reading data. So Cinema 4D can automate few things such as saving automatically the data stored in this BaseContainer into a File (aka Cinema 4D File) and read them back when loading the Cinema 4D file.So back to our topic, if we assign a particular value to a "Plugin ID/Unique ID" you can assume that nothing within in Cinema 4D will overwrite this particular value.
(Third-party developer can access this data however and may erase them, so that's why we recommend using a Unique ID when you store data in a BaseContainer, this way you are sure to not erase other people data and you're is ok).
Of course, this works because everyone plays the game if I can tell.Now back to the initial questions.
What's the use case of a global variable in Python in Cinema 4D ecosystem?- Storing data across multiple frames. (Can also be done with a BaseContainer, but you have to convert to a DataType that BaseContaienr accept, so list, dict are not supported so you have to "bake" which is slow)
- Data are only alive for the current lifetime of the host and in maximum the Cinema 4D session.
What's the use case of data stored in a BaseContainer in Cinema 4D?
- Storing data across multiple frames. (See the previous point in some case it may be ineffective).
- Storing persistent data over multiple Cinema 4D sessions (the data will be stored in the host, e.g. the Python Generator Object by itself, so if you open the file in Cinema 4D, the Python Generator will restore the data in the BaseContainer).
- Exposing data to others (Maybe other Objects want to know the current value stored in a particular Python Generator Object).
Finally, BaseContainer should be primarily chosen but as stated in some condition it makes more sense to use a global variable.
And here is how to define a global variable within a Python Generator:
import c4d # Checks if the test variables exist in the global dict, if not assign a value of 10 global test if 'test' not in globals(): test = 10 def main(): global test print test return c4d.BaseObject(c4d.Ocube)
I hope it answers your questions. Additionally please read BaseContainer Manual.
Cheers,
Maxime.