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

    Python tag doesn't work when rendering

    Cinema 4D SDK
    windows 2023 python
    3
    6
    968
    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.
    • L
      ll2pakll
      last edited by ll2pakll

      Windows 10
      Cinema 2023.2.1

      Good 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()
      
      1 Reply Last reply Reply Quote 0
      • L
        ll2pakll
        last edited by

        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.

        1 Reply Last reply Reply Quote 0
        • M
          mogh
          last edited by

          Have you tried different priority settings of the tag ?

          L ferdinandF 2 Replies Last reply Reply Quote 0
          • L
            ll2pakll @mogh
            last edited by

            @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.

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

              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:

              1. 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.
              2. 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 the main() 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.
              3. 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 in sys.modules or write things into the BaseContainer of the tag.

              Cheers,
              Ferdinand

              MAXON SDK Specialist
              developers.maxon.net

              L 1 Reply Last reply Reply Quote 1
              • L
                ll2pakll @ferdinand
                last edited by

                @ferdinand Your solution of replacing the doc = c4d.documents.GetActiveDocument() line with doc: 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.

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