Python tag initalization?
-
Hello everyone,
I'm new here
I've a python script that creates a Python tag and injects some code into it. This Python tag is then searching in the hierarchy for objects to link to. This search is in the main() so I guess it executing every frame/interaction, which doesn't sound very performant, especially as it will relink the same object over and over again.
Is there a way to initialize the python tag somehow, so in the example below, crtl_01 gets assigned just a single time, and then keeps its value without updating it?
Is initialization the correct term for this?
The code looks similar to this:
import c4d def get_object_from_python_tag(tag): if tag and tag.GetType() == c4d.Tpython: linked_object = tag.GetMain() if linked_object: return linked_object return None def get_parent_object(obj): return obj.GetUp() def find_object_in_hierarchy_by_name(current_obj, target_name): if current_obj.GetName() == target_name: return current_obj child = current_obj.GetDown() while child: result = find_object_in_hierarchy_by_name(child, target_name) if result: return result child = child.GetNext() return None def main(): python_null = get_object_from_python_tag(op) ctrl_01 = find_object_in_hierarchy_by_name(get_parent_object(python_null), "Helper_01") # Execution if __name__ == '__main__': main()
-
Hi @gaschka, there is multiple way to set it up.
- Do as you do currently, in each execution, check if the object are the same if not update it. I would say there is nothing really wrong about it.
- If you want the user to have the ability to change it, just create a link user data.
Then either your tag or the script that create the tag, have the duty to define it and within your execute method you do as you do currently and check if the link from the user data should be updated or not. - The name is hardcoded, you can react to the MSG_MENUPREPARE in the message function of a tag like so:
import c4d CTRL_NAME = "Helper_01" def find_object_in_hierarchy_by_name(current_obj, target_name): if current_obj.GetName() == target_name: return current_obj child = current_obj.GetDown() while child: result = find_object_in_hierarchy_by_name(child, target_name) if result: return result child = child.GetNext() return None def cache_ctrl_01(target_name): python_null = op.GetObject() ctrl_01 = find_object_in_hierarchy_by_name(python_null.GetUp(), target_name) op[10001] = ctrl_01 def message(id, data): if id == c4d.MSG_MENUPREPARE: cache_ctrl_01(CTRL_NAME) def main(): ctrl_01 = op[10001] # Check if the object exist and is not deleted retrieve it if ctrl_01 is None or not ctrl_01.IsAlive(): cache_ctrl_01(CTRL_NAME) ctrl_01 = op[10001] # If its still None, then something is wrong raise an error in the Python Console if ctrl_01 is None or not ctrl_01.IsAlive(): raise ValueError(f"Unable to find {CTRL_NAME}") print(op[10001])
op[10001]
will write into the basecontainer of op (the tag). A BaseContainer is like a dict in python, except that it's native to Cinema 4D and used all over the place in Cinema 4D. For example the parameter of an object,tags,material,document are stored in a BaseContainer. When you pass some settings to execute a command, you pass a BaseContainer with the appropriate value in it. I've used the id 10001 as any ID above 10000 are safe to use and will not conflict with internal stuff, but if you really want to be safe, you should register a plugin id in plugincafe we did not yet migrated pluginId to this new website so you will need an old account, the migration will happen in the upcoming weeks/months.Then the code to create your tag should be:
import c4d SOME_PYTHON_TAG_CODE = """import c4d CTRL_NAME = "Helper_01" def find_object_in_hierarchy_by_name(current_obj, target_name): if current_obj.GetName() == target_name: return current_obj child = current_obj.GetDown() while child: result = find_object_in_hierarchy_by_name(child, target_name) if result: return result child = child.GetNext() return None def cache_ctrl_01(target_name): python_null = op.GetObject() ctrl_01 = find_object_in_hierarchy_by_name(python_null.GetUp(), target_name) op[10001] = ctrl_01 def message(id, data): if id == c4d.MSG_MENUPREPARE: cache_ctrl_01(CTRL_NAME) def main(): ctrl_01 = op[10001] # Check if the object exist and is not deleted retrieve it if ctrl_01 is None or not ctrl_01.IsAlive(): cache_ctrl_01(CTRL_NAME) ctrl_01 = op[10001] # If its still None, then something is wrong raise an error in the Python Console if ctrl_01 is None or not ctrl_01.IsAlive(): raise ValueError(f"Unable to find {CTRL_NAME}") print(op[10001]) """ def add_python_tag(obj): if not isinstance(obj, c4d.BaseObject): return None python_tag = obj.MakeTag(c4d.Tpython) doc.AddUndo(c4d.UNDOTYPE_NEWOBJ, python_tag) python_tag[c4d.TPYTHON_CODE] = SOME_PYTHON_TAG_CODE python_tag.Message(c4d.MSG_MENUPREPARE) return python_tag # Execution if __name__ == '__main__': doc.StartUndo() add_python_tag(op) doc.EndUndo() c4d.EventAdd()
Cheers,
Maxime. -
-
@m_adam thanks a lot for the explaination and the code. Messages is a new concept for me and something I'll definitely explore.
Referring to point 1: So checking if the object is changed, even with the OM traversal, is something I can ignore performance wise? I'm asking, because I want the tag to control a custom piece of a character rig, perhaps with multiple copies of it. Rigs tend to be quite performance critical and running single threaded in C4D due to the OM so I wonder if this kind of search in the OM can have a significant impact?
-
@gaschka
I can think like hundreds of operations are nothing for the Python
When it counts as thousands — you'll notice that.Optimizations requires some knowledge of Cinema tech side, to listen events, understand execution context, and so on.
Because there might be multiple scenarios, which you're not expecting in your "optimal" logic.
Main issue is the user — who will delete your key tags/objects, move objects within hierarchy, wrap objects into a null, and so on -
@baca thanks for the insights! I guess I sit too long next to game devs who have a tendency for premature optimizations and got infected
But I can second that: I know so many C4D artists who don't have a clue, neither they care, about how to keep theirs scene performant. -
OM is not single threaded, see Threading Manual all operations of a scene execution are not done in the same thread / main thread. The main bottleneck will be the execution of Python by itself. A Xpresso setup may be faster but is single threaded and I don't think will let you do the same thing.
With that's said if you know that the object will change a lot and you will need to perform the search over and over then yes you may rethink your approach and probably use a link field, so the user can rewire the object instead of you searching for it but what you are doing currently is fine.
-
@m_adam said in Python tag initalization?:
OM is not single threaded,
Oh, that's an interesting insight for me. Perhaps I was misinformed, as Character Rigs had the tendency to get slow quite easily in C4D, and Animators look envy over to Maya, as they do have a parallel evaluation (and caching). Though I'm aware that there are changes/improvements to C4D performance lately. Thanks for the link. A lot of new information to learn an absorb