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
    • Recent
    • Tags
    • Users
    • Register
    • Login

    Thread safety when handling CTrack in TagData.Message() on button click

    Scheduled Pinned Locked Moved Cinema 4D SDK
    2026pythonwindows
    1 Posts 1 Posters 6 Views
    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.
    • ThomasBT Offline
      ThomasB
      last edited by ThomasB

      Hi,
      I have a small plugin example of a Tag Plugin and I want to know if I handle the threading correctly, because in my production version , I encountered some freezes, espacially during a running animation or when Redhift Render View was running.

      Download Plugin:
      test_tag_plugin.zip

      Quick explanation:

      • tag plugin with a button and a link field
      • button click has to load a sound
      • When the button is clicked, I want to alternate between loading a sound into the track specified in the link field (In the official release, I calculate a sound, save it to the hard drive, and load it into the track.)
      • If no track exists, one must be created.
      • I handle this within the Message Method as usual
      • and check using c4d.threading.GeIsMainThread().

      Is this the right approach, or are there other factors to consider?
      Should I perform the check inside the add_sound method instead,
      or would it be better to check using c4d.GeIsMainThreadAndNoDrawThread?

      Please bear in mind that I threw this together quickly; you shouldn't expect perfection in the code—such as error handling—as my only concern right now is thread safety.

      Code:

      """
      Simple CTrack Sound Loader
      """
      
      import c4d
      import os
      from c4d import bitmaps, plugins
      
      # be sure to use a unique ID obtained from www.plugincafe.com
      PLUGIN_ID = 123456789
      
      PY_ADD_AUDIO = 1000
      PY_TRACK = 1001
      
      class TestTagPlugin(plugins.TagData):
          def __init__(self):
      
              self.current_sound: str | None = None
      
          def Init(self, node, isCloneInit):
      
              if not isCloneInit:
                  self.InitAttr(node, c4d.BaseList2D, c4d.DescID(PY_TRACK))
      
      
                  node[PY_TRACK] = None
              return True
      
          def Execute(self, tag, doc, op, bt, priority, flags):
              return c4d.EXECUTIONRESULT_OK
      
          @staticmethod
          def check_track(node, sound_path):
              """
              Checks if there is a track in the audiotrack slot, if not it creates one and puts the sound into this track
              Has to be called from the MainThread
              :param node: the tag instance
              :param sound_path: str - The path to the sound file
              """
              track = node[PY_TRACK]
      
              if not track or not track.GetType() == c4d.CTsound:
      
                  # create special Audio Track
                  obj: c4d.BaseObject = node.GetObject()
      
                  track = c4d.CTrack(obj, c4d.DescID(c4d.DescLevel(c4d.CTsound, c4d.CTsound, 0)))
      
                  obj.InsertTrackSorted(track)
      
                  node[PY_TRACK] = track
                  track.SetName("Audio_Track")
      
              node[PY_TRACK][c4d.CID_SOUND_NAME] = ""
      
              node[PY_TRACK][c4d.CID_SOUND_NAME] = sound_path
      
      
          def msg_add_audio(self, node) -> bool:
              """
              Loads the audio into the track
              :param node: the tag instance
              :return: bool
              """
              was_animation_running = False
              if c4d.CheckIsRunning(c4d.CHECKISRUNNING_ANIMATIONRUNNING):
                  # save animation state and stop animation
      
                  was_animation_running = True
                  c4d.CallCommand(12412)
      
              if self.current_sound == "base_1.wav":
                  self.current_sound = "base_2.wav"
              else:
                  self.current_sound = "base_1.wav"
      
              path = os.path.join(sample_path, self.current_sound)
      
              if not os.path.exists(path):
                  return False
      
              self.check_track(node, path)
      
              if was_animation_running:
                  # restart stopped Animation
      
                  c4d.CallCommand(12412)
      
              return True
      
          def Message(self, node: c4d.GeListNode, type: int, data: object) -> bool:
      
              if type == c4d.MSG_DESCRIPTION_COMMAND:
      
                  if data["id"][0].id == PY_ADD_AUDIO:
                      if c4d.threading.GeIsMainThread():
                          self.msg_add_audio(node)
      
                      return True
      
              return True
      
      # main
      if __name__ == "__main__":
      
          bmp = bitmaps.BaseBitmap()
          p_dir, file = os.path.split(__file__)
      
          fn = os.path.join(p_dir, "res", "icon.tif")
          bmp.InitWith(fn)
      
          sample_path = os.path.join(p_dir, "res", "samples")
      
          c4d.plugins.RegisterTagPlugin(id=PLUGIN_ID,
                                        str="test_tag_plugin",
                                        info=c4d.TAG_EXPRESSION | c4d.TAG_VISIBLE,
                                        description="test_tag_plugin",
                                        g=TestTagPlugin, icon=bmp)
      
      

      An additional question regarding the tag:
      I’m also curious about a scenario where I launch a GeDialog from the tag via a button and pass the tag to it and toggle the sound from within the dialog as well. Would using c4d.SpecialEventAdd() keep me on the safe side—for instance. So if the dialog runs asynchronously, stores the tag as a member variable,
      retrieves the class instance via GetNodeData, and then calls the add_sound function?
      Is that a viable approach? It works in the official plugin.

      Thanks in advance.
      T.B.

      Thanks,
      T.B

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