<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Thread safety when handling CTrack in TagData.Message() on button click]]></title><description><![CDATA[<p dir="auto">Hi,<br />
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.</p>
<p dir="auto">Download Plugin:<br />
<a href="%5B%5Berror:parse-error%5D%5D" target="_blank" rel="noopener noreferrer nofollow ugc">test_tag_plugin.zip</a></p>
<p dir="auto"><strong>Quick explanation:</strong></p>
<ul>
<li>tag plugin with a button and a link field</li>
<li>button click has to load a sound</li>
<li>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.)</li>
<li>If no track exists, one must be created.</li>
<li>I handle this within the  <code>Message</code>  Method as usual</li>
<li>and check using <code>c4d.threading.GeIsMainThread()</code>.</li>
</ul>
<p dir="auto">Is this the right approach, or are there other factors to consider?<br />
Should I perform the check inside the <code>add_sound</code> method instead,<br />
or would it be better to check using <code>c4d.GeIsMainThreadAndNoDrawThread</code>?</p>
<p dir="auto">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.</p>
<p dir="auto"><strong>Code:</strong></p>
<pre><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) -&gt; 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) -&gt; 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)

</code></pre>
<p dir="auto"><strong>An additional question regarding the tag:</strong><br />
I’m also curious about a scenario where I launch a <code>GeDialog</code> from the tag via a button and  pass the tag to it and toggle the sound from within the dialog as well. Would using <code>c4d.SpecialEventAdd()</code> keep me on the safe side—for instance. So if the dialog runs asynchronously, stores the tag as a member variable,<br />
retrieves the class instance via <code>GetNodeData</code>, and then calls the <code>add_sound</code> function?<br />
Is that a viable approach? It works in the official plugin.</p>
<p dir="auto">Thanks in advance.<br />
T.B.</p>
]]></description><link>http://developers.maxon.net/forum/topic/16421/thread-safety-when-handling-ctrack-in-tagdata.message-on-button-click</link><generator>RSS for Node</generator><lastBuildDate>Sun, 14 Jun 2026 01:52:15 GMT</lastBuildDate><atom:link href="http://developers.maxon.net/forum/topic/16421.rss" rel="self" type="application/rss+xml"/><pubDate>Fri, 12 Jun 2026 20:36:25 GMT</pubDate><ttl>60</ttl></channel></rss>