Python tag doesn't work when rendering
-
Windows 10
Cinema 2023.2.1Good evening. I am faced with a strange situation. Almost all the behavior in my scene is written using Python tag. In the viewport everything works exactly as it should, but when I try to render the scene only the manually animated object moves, and everything that adjusts the Python tag doesn't work. What could be the problem?
.
To understand more about the problem, I made a short video
Here is the code written in the tag (I apologize for the part of the description written in Russian. I didn't think that this code would be intended for anyone but me.):from typing import Optional import c4d import math # определяем глобальную переменную для истории history = [] def rad_to_deg(rad): """ переводит радианы в градусы :param rad: :return: """ # Проверяем, является ли rad числом if isinstance(rad, (int, float)): # Вычисляем градусы по формуле deg = rad * 180 / pi deg = rad * 180 / math.pi # Возвращаем результат return deg else: # Выводим сообщение об ошибке, если rad не число print("Неверный тип аргумента. Ожидается число.") def deg_to_rad(deg): """ Переводит градусы в радианы :param deg: :return: """ # Проверяем, является ли deg числом if isinstance(deg, (int, float)): # Вычисляем радианы по формуле rad = deg * pi / 180 rad = deg * math.pi / 180 # Возвращаем результат return rad else: # Выводим сообщение об ошибке, если deg не число print("Неверный тип аргумента. Ожидается число.") def spring_length_calculation(spring_target: c4d.BaseObject, helix: c4d.BaseObject) -> float: """ Вычисляет длину пружины. В качестве аргументов получает helix и цель для пружины. :param spring_target: :param helix: :return: """ katet_1 = spring_target[c4d.ID_BASEOBJECT_GLOBAL_POSITION, c4d.VECTOR_Y] - helix[ c4d.ID_BASEOBJECT_GLOBAL_POSITION, c4d.VECTOR_Y] katet_2 = spring_target[c4d.ID_BASEOBJECT_GLOBAL_POSITION, c4d.VECTOR_Z] - helix[ c4d.ID_BASEOBJECT_GLOBAL_POSITION, c4d.VECTOR_Z] kvadrat_giopotenuzy = katet_1 ** 2 + katet_2 ** 2 return kvadrat_giopotenuzy ** 0.5 def spring_direction_calculation(spring_direction: c4d.BaseObject) -> float: """ Рассчитывает угол направления пружины. Из за того что тег цель работает не корректно, пружина отражается каждый раз при пересечении рубежа в 90 градусов, из за этого приходится делать рассчёты её положения таким образом что бы не учитывался разворот. :param spring_direction: принимает объект :return: """ # получаем значение направления направляющей в радианах rad_spring_direction = spring_direction[c4d.ID_BASEOBJECT_REL_ROTATION, c4d.VECTOR_Y] # получаем значение вращения пружины в радианах rad_spring_rotation = spring_direction[c4d.ID_BASEOBJECT_REL_ROTATION, c4d.VECTOR_X] # переводим радианы в градусы deg_spring_rotation = rad_to_deg(rad_spring_rotation) # смотрим если градус разворота направляющей меньше 10 то направления пружины и направляющей должны быть одинаковыми # в ином случае рассчитываем новое направление. Перед тем как вернуть значение переводим его обратно в радианы if deg_spring_rotation < 10: return rad_spring_direction else: deg_spring_direction = rad_to_deg(rad_spring_direction) d = 180 - deg_spring_direction return deg_to_rad(d) def main() -> None: count_oscillators = 15 doc = c4d.documents.GetActiveDocument() for i in range(count_oscillators - 1): spring_target = doc.SearchObject(f"spring target {i}") helix = doc.SearchObject(f"helix {i}") spring = doc.SearchObject(f"spring {i}") spring_direction = doc.SearchObject(f"spring direction {i}") # изменяем длину пружины в соответсвии с положением цели helix[c4d.PRIM_HELIX_HEIGHT] = spring_length_calculation(spring_target, helix) # изменяем направление пружины в соответсвии с направляющей для пружины spring[c4d.ID_BASEOBJECT_REL_ROTATION, c4d.VECTOR_Y] = spring_direction_calculation(spring_direction) # создаём механику памяти # Get the document and the fps fps = doc.GetFps() # Это крайний куб вслед за которым будут двигаться все остальные кубы cube_main = doc.SearchObject(f"cube 0") pos_main = cube_main[c4d.ID_BASEOBJECT_REL_POSITION,c4d.VECTOR_Z] # Declare the global variable history global history # Create a global variable to store the history of the first cube's position # If it doesn't exist, initialize it with the current position if not "history" in globals(): history = [pos_main] else: # Append the current position to the history list history.append(pos_main) # Get the current frame number frame = doc.GetTime().GetFrame(fps) # Check if the current frame is the first one if frame == 0: # Clear the history list history = [] for i in range(1, count_oscillators): # Get the cube by name cube = doc.SearchObject(f"cube {i}") # Set the initial position of the cube cube[c4d.ID_BASEOBJECT_REL_POSITION,c4d.VECTOR_Z] = 0 # Повторяем движение для остальных кубов с задержкой for i in range(1, count_oscillators): # Get the cube by name cube = doc.SearchObject(f"cube {i}") # Calculate the time offset in seconds offset = i * 10 # Calculate the index in the history list index = frame - offset # If the index is valid, get the position from the history list if index >= 0 and index < len(history): pos = history[index] # Otherwise, use the initial position else: pos = cube[c4d.ID_BASEOBJECT_REL_POSITION,c4d.VECTOR_Z] # Set the position of the cube cube[c4d.ID_BASEOBJECT_REL_POSITION,c4d.VECTOR_Z] = pos # Update the scene c4d.EventAdd()
-
Apparently I've found a solution, but it doesn't look very convenient. In order for the animation to work in the renderer, you need to bake all objects that are affected by the python code. If there is a more elegant solution to the problem, I will be very grateful to someone who will share it.
-
Have you tried different priority settings of the tag ?
-
@mogh I just tried it and it didn't make any difference. I don't have many tags in my scene, besides the Python tag there are few targets. I set Python to priority 1, the others to 10, everything stayed the same.
-
Hello @ll2pakll,
Thank you for reaching out to us. I do not have too much time at the moment, so I cannot test things myself, but the likely reason for your problems is this line:
def main() -> None: count_oscillators = 15 # doc = c4d.documents.GetActiveDocument() # The culprit. # Should be replaced with: doc: c4d.documents.BaseDocument = op.GetDocument()
There are three things at play here:
- You should NEVER access the active document in any
NodeData
derived interface, this also includes things like the Python tag or Python Generator object. Always use GeListNode.GetDocument() to access the document the node is contained in. Nodes will not only be executed in the active document. - In all
NodeData
derived interfaces you are bound to the threading restrictions in most methods as they do run off-main-thread in parallel. This also applies to themain()
function of a Python programming tag. It will never run on the MT, and you therefore are not allowed to do things like adding or removing scene elements or adding events. Modifying parameters of scene elements is okay to do. As far as I can see at a quick glance, you seem not violate the thread restrictions in your script. - When a document is rendered in anything else than the viewport, it will be cloned. When your tag than operates on the active document, a tag instance in the render document will then reach into the active document.
Last but not least, using module level attributes just as you
history
is risky, as there is no gurantee of the lifetime of Python tag module (i.e. the 'script'). There is a good chance that the Python VM reallocates the module in the middle of a scene and you loose all of your history. There are two patterns to deal with this, either write things into a module attribute of a persistent module insys.modules
or write things into theBaseContainer
of the tag.Cheers,
Ferdinand - You should NEVER access the active document in any
-
@ferdinand Your solution of replacing the
doc = c4d.documents.GetActiveDocument()
line withdoc: c4d.documents.BaseDocument = op.GetDocument()
really worked. From my point of view, as an ordinary person, your level of understanding of the code seems to be something beyond the realm of possibility. I wish you a good day and thank you again for your help.