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

    How do you manipulate the points on a spline?

    Cinema 4D SDK
    2023 windows python
    2
    3
    499
    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

      Greetings. Is there any way to manage specific points on a spline? Create, move and delete them?
      I have created a spline with a certain shape, and I want it to be visible not the whole spline, but only a part of it. I want to change the end visible points over time, which will make it look like it is moving in space. I have done this with a "formula", it has parameters for the endpoints and I can change them over time. But using the formula I can't create a spline of the shape I want, and if I convert the formula to a spline and edit it, I find that I can't implement the same movement logic, because I don't understand how I can access the endpoints to create, delete and control their parameters.

      It's a basic script. The second script must be in the file located at the address specified in line 171. It contains the logic of Python tags.

      import c4d
      import math
      import sys
      
      doc: c4d.documents.BaseDocument
      
      
      def open_script_for_python_tag(file_path: str, replacement_dictionary: dict = None):
          """
          Эта функция возвращает открытый файл в котором заменены указанные в replacement_dictionary строки.
      
          :param file_path: путь к файлу
          :param replacement_dictionary: передаётся словарь в котором ключ это строка которую нужно заменить, а значение это
          строка на которую нужно заменить.
          :return:
          """
          # Открываем файл для чтения
          if replacement_dictionary:
              with open(file_path, "r", encoding="utf8") as file:
                  # Читаем все строки файла в список lines
                  lines = file.readlines()
              # Создаем пустую строку result
              result = ""
              # Проходим по каждой строке в списке lines
              for line in lines:
                  for sours, target in replacement_dictionary.items():
                      # Если строка совпадает с ключом, то она заменяется на значение ключа
                      if line == sours:
                          result += target
                      # Иначе оставляем строку без изменений
                      else:
                          result += line
              # Возвращаем результат
              return result
          with open(file_path, "r", encoding="utf8") as file:
              text = file.read()
          return text
      
      
      def get_all_objects() -> list[c4d.BaseObject]:
          """
          # функция, которая возвращает все объекты в сцене.
          :return:
          """
          # Создаем пустой список для хранения имен объектов
          objects = []
          # Получаем корневой объект сцены
          root = doc.GetFirstObject()
          # Если сцена не пуста
          if root:
              # Создаем стек для обхода дерева объектов
              stack = [root]
              # Пока стек не пуст
              while stack:
                  # Извлекаем верхний элемент стека
                  obj = stack.pop()
                  # Добавляем его имя в список
                  objects.append(obj)
                  # Если у объекта есть дочерние объекты
                  if obj.GetDown():
                      # Добавляем их в стек
                      stack.append(obj.GetDown())
                  # Если у объекта есть соседние объекты
                  if obj.GetNext():
                      # Добавляем их в стек
                      stack.append(obj.GetNext())
          # Возвращаем список имен объектов
          return objects
      
      
      def get_all_objects_names():
          """
          Получает имена всех объектов в сцене.
          :return:
          """
          object_names = []
          for object_c4d in get_all_objects():
              object_names.append(object_c4d.GetName())
      
          return object_names
      
      
      def find_all_single_waves() -> list:
          """
          Ищет все объекты одиночных волны в сцене.
          :return:
          """
          # Get the first object in the scene
          obj = doc.GetFirstObject()
          waves = []
          # Loop through all objects in the scene
          while obj:
              # Get the object name
              name = obj.GetName()
      
              # Check if the name matches the pattern "Wave" followed by a number
              if name.startswith("Wave") and name[5:].isdigit():
                  # Print the object name and its global position
                  waves.append(obj)
      
              # Get the next object in the scene
              obj = obj.GetNext()
          return waves
      
      
      class WaveMove:
          def __init__(self, length: (float, int) = 304, amplitude: (float, int) = 100, velocity: (float, int) = 30,
                       phase: int = 0, name: str = None):
              self.__length = length
              self.__amplitude = amplitude
              self.__velocity = velocity
              self.__phase = phase  # в градусах от 0 до 360
              self.__start_point = 0
              self.__end_point = 10
              self._python_tag = None
      
              # создаём сплайн формула, который будет представлять волну
              self.wave_obj = c4d.BaseObject(c4d.Osplineformula)
              # если было передано имя волны то сразу называем её указанным именем
              if name:
                  self.wave_obj.SetName(name)
              else:
                  # если имя не было передано, то смотрим сколько волн в сцене и исходя из этого нумеруем волну.
                  count_waves_in_scene = len(find_all_single_waves())
                  self.wave_obj.SetName('Wave ' + str(count_waves_in_scene))
      
              # меняем плоскость расположения волны
              self.wave_obj[c4d.PRIM_PLANE] = c4d.PRIM_PLANE_ZY
      
              if name == 'Result wave':
                  self.wave_obj[c4d.ID_BASEOBJECT_VISIBILITY_EDITOR] = c4d.OBJECT_OFF
                  self.wave_obj[c4d.ID_BASEOBJECT_VISIBILITY_RENDER] = c4d.OBJECT_OFF
              # добавляем волну в сцену
              doc.InsertObject(self.wave_obj)
      
              # создаём пользовательские данные которые будут определять параметры волны
              # создаём дневник с названиями и значениями полей пользовательских данных
              self.ud_dict = {"Length wave": self.__length,
                              "Amplitude wave": self.__amplitude,
                              "Velocity wave": self.__velocity,
                              "Phase wave": self.__phase}
      
              for ud_name, ud_value in self.ud_dict.items():
                  self.create_user_data(ud_name, ud_value)
      
              # устанавливаем точки начала и конца волны
              self.set_start_point(self.__start_point)
              self.set_end_point(self.__end_point)
      
              # устанавливаем количество точек в сплайне
              self.wave_obj[c4d.PRIM_FORMULA_SAMPLES] = 100
      
              # включаем кубическую интерполяцию
              self.wave_obj[c4d.PRIM_FORMULA_CUBIC] = True
      
              # промежуточные точки = равномерно
              self.wave_obj[c4d.SPLINEOBJECT_INTERPOLATION] = c4d.SPLINEOBJECT_INTERPOLATION_UNIFORM
      
              # задаём формулу для поля X(t) которое будет определять длину волны
              self.formula_xt = f"100*t"
              self.set_Xt(self.formula_xt)
      
              # задаём формулу для поля Y(t) которое будет определять амплитуду волны
              self.formula_yt = f"{str(self.__amplitude)}*Sin((t+{str(math.radians(phase))})*{str(self.__length)})"
              self.set_Yt(self.formula_yt)
      
              # создаём словарь в котором будут содержаться строки, которые нужно заменить в скрипте тега
              self.replacement_dictionary = {
                  "    wave_obj: c4d.BaseObject =\n": f"    wave_obj: c4d.BaseObject = doc.SearchObject('{self.wave_obj.GetName()}')\n"
              }
              self.script_for_tag_path = r'F:\Work_area\YouTube\c4d\scripts\9_script_for_teg_single_wave.py'
              self.create_tag_python()
              # self.create_volume()
      
          def create_user_data(self, name: str, value: int):
              """
              Создаёт параметр в пользовательских данных
              :param value:
              :param name:
              :return:
              """
              ud = c4d.GetCustomDataTypeDefault(c4d.DTYPE_LONG)
              ud[c4d.DESC_NAME] = name
      
              self.wave_obj[self.wave_obj.AddUserData(ud)] = value
      
          def create_tag_python(self):
              """
              Создаёт тег Python в котором будет описываться поведение волны и надевает его на объект формула сплайн волны.
              :return:
              """
              # создаём тег и вешаем его
              self._python_tag: c4d.BaseTag = self.wave_obj.MakeTag(c4d.Tpython)
              # помещаем скрипт в тело тега Python и передаём в него вводные данные
              self._python_tag[c4d.TPYTHON_CODE] = open_script_for_python_tag(
                  replacement_dictionary=self.replacement_dictionary,
                  file_path=self.script_for_tag_path
              )
      
          def create_volume(self):
              """
              Создаёт sweep и circle что бы появился объём.
              :return:
              """
              sweep = c4d.BaseObject(c4d.Osweep)
              sweep.SetName(f"Sweep {self.get_name()}")
      
              # создаём окружность
              circle = c4d.BaseObject(c4d.Osplinecircle)
              circle[c4d.PRIM_CIRCLE_RADIUS] = 4
              circle[c4d.SPLINEOBJECT_SUB] = 2
              circle.SetName(f'Circle {self.get_name()}')
      
              # вставляем пружину и спираль в sweep
              self.wave_obj.InsertUnder(sweep)
              circle.InsertUnder(sweep)
              doc.InsertObject(sweep)
      
          def set_Xt(self, xt: str):
              """
              устанавливаем поле Xt для формулы
              :param xt:
              :return:
              """
              self.wave_obj[c4d.PRIM_FORMULA_X] = xt
      
          def set_Yt(self, yt: str):
              """
              устанавливаем поле Yt для формулы
              :param yt:
              :return:
              """
              self.wave_obj[c4d.PRIM_FORMULA_Y] = yt
      
          def set_start_point(self, value: float):
              """
              Устанавливает точку начала волны
              :param value:
              :return:
              """
              self.wave_obj[c4d.PRIM_FORMULA_TMIN] = value
      
          def set_end_point(self, value: float):
              """
              Устанавливает точку окончания волны
              :param value:
              :return:
              """
              self.wave_obj[c4d.PRIM_FORMULA_TMAX] = value
      
          def get_name(self):
              """
              Возвращает имя объекта волны.
              :return:
              """
              return self.wave_obj.GetName()
      
          def get_Yt(self):
              """
              Возвращает формулу из поля Yt.
              :return:
              """
              return self.wave_obj[c4d.PRIM_FORMULA_Y]
      
      
      if __name__ == '__main__':
          WaveMove()
      
          c4d.EventAdd()
      
      
      from typing import Optional
      import c4d
      import math
      import sys
      
      
      def main() -> None:
          doc: c4d.documents.BaseDocument = op.GetDocument()
      
          wave_obj: c4d.BaseObject =
          # рассчитываем фактор скорости
          velocity_factor = doc.GetTime().Get() * wave_obj[c4d.ID_USERDATA, 3] * 0.1
      
          # устанавливаем связи между пользовательскими данными и состоянием волны
          wave_obj[c4d.PRIM_FORMULA_Y] = f"{str(wave_obj[c4d.ID_USERDATA, 2])}*Sin((t+{str(math.radians(wave_obj[c4d.ID_USERDATA, 4]))})*{str(wave_obj[c4d.ID_USERDATA, 1] * 0.01)})"
          wave_obj[c4d.PRIM_FORMULA_TMIN] = velocity_factor
          wave_obj[c4d.PRIM_FORMULA_TMAX] = velocity_factor + 10
          # Update the scene
          c4d.EventAdd()
      
      1 Reply Last reply Reply Quote 0
      • L
        ll2pakll
        last edited by

        I found the answer on my own. It turned out that the methods of point management are in the parent class c4d.SplineObject - c4d.PointObject. It is also necessary to call the .Message(c4d.MSG_UPDATE) method, otherwise the spline is not updated.

        1 Reply Last reply Reply Quote 0
        • M
          m_adam
          last edited by ferdinand

          Hi @ll2pakll,

          I am glad that you solved your issue. Message(c4d.MSG_UPDATE) is indeed required to finalize your spline editing. If you want a complete example on spline editing please take a look at geometry_splineobject.py example.

          Cheers,
          Maxime.

          MAXON SDK Specialist

          Development Blog, MAXON Registered Developer

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