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.