Solved [Solved] How to setup a CTrack on TAG plugin UI slider? [with extended details]

Hello there,

I'm currently working on a plugin development in Python under R26 and I'm struggling to reach a resource ID on the GUI to assign it an animation track.
I tried to implement what I was using for the objects hoping it will worked, but it isn't - I'm getting error 5350 doing so.

Reading the SDK, it seems, as far as I understand, that BaseTag inherit of BaseList2D and should so inherit of CTrack; but there should be something wrong here in the code or SDK which I do not understand. As the plugin is declared as a plugins.RegisterTagPlugin() shouldn't it
be inherit of BaseList2D?

Here below the excerpt of the code for a better understanding,

mAXIS_X = 1550
mAXIS_Y = 1551
mAXIS_Z = 1552

def Init(self, node):
        mmObj = c4d.documents.GetActiveDocument().GetActiveTag()
        if mmObj is not None:
            print ( f"mmObj: { mmObj }" )
            print ( f"Object: { mmObj.GetObject() }" )
            print ( f"tagName : {mmObj} / {mmObj.GetObject().GetName()} plugin ID: { mPLUGIN_ID }" )
            descID = c4d.DescID( c4d.DescLevel(c4d.ID_BASEOBJECT_ROTATION, c4d.DTYPE_VECTOR, 0), c4d.DescLevel(c4d.VECTOR_X, c4d.DTYPE_REAL, 0) )
            c4d.CTrack(mmObj, descID )

gives the following console log and error,

mmObj: <c4d.BaseTag object called mMover/mMover with ID 1059303 at 11778301312>
Object: <c4d.BaseObject object called mMover/Null with ID 5140 at 11778302720>
tagName : <c4d.BaseTag object called mMover/mMover with ID 1059303 at 11778301312> / mMover plugin ID: 1059303
Traceback (most recent call last):
  File "~/Library/C4D_Plugins/C4D_mLab_Plugins_R23/mMover.pyp", line 266, in Init
    c4d.CTrack(mmObj, descID )
BaseException: the plugin 'c4d.CTrack' (ID 5350) is missing. Could not allocate instance

The pict below shows the UI on which 3 sliders exists (mAXIS_XX, ID from 1550 to 1552). That those sliders on which I would like to create a CTrack. Once done, the button "Clear all animation tracks" will remove them.

Screenshot 2022-12-16 at 16.43.40.png

I setup a .res resource file with this,

           GROUP {         
                DEFAULT 1;
                COLUMNS 2;

                REAL mAXIS_X           { MIN -180.0; MAX 180.0; UNIT DEGREE; STEP 0.05; CUSTOMGUI REALSLIDER; SCALE_H; }
                BOOL mEN_X             { ANIM OFF; }
                STATICTEXT              { JOINENDSCALE; }

                REAL mAXIS_Y           { MIN -180.0; MAX 180.0; UNIT DEGREE; STEP 0.05; CUSTOMGUI REALSLIDER; SCALE_H; }
                BOOL mEN_Y             { ANIM OFF; }
                STATICTEXT              { JOINENDSCALE; }

                REAL mAXIS_Z           { MIN -180.0; MAX 180.0; UNIT DEGREE; STEP 0.05; CUSTOMGUI REALSLIDER; SCALE_H; }
                BOOL mEN_Z             { ANIM OFF; }
                STATICTEXT              { JOINENDSCALE; }

            }

The sliders have the following IDs no .h file,

 mAXIS_X = 1550,
 mAXIS_Y,
 mAXIS_Z,

I tried many ways to reach the right ID (1550 here), but I never succeed; and I never got the Animation track created in any way for this slider.

When using the Execute method of the plugin I can easily reach the sliders values, but again, I can't succeed to assign a CTrack on them,

def Execute(self, tag, doc, op, bt, priority, flags): 
        print ( f"X Slider value: { c4d.utils.RadToDeg(tag[mAXIS_X]) }")
descID = c4d.DescID( c4d.DescLevel(c4d.ID_BASEOBJECT_ROTATION, c4d.DTYPE_VECTOR, 0), c4d.DescLevel(c4d.VECTOR_X, c4d.DTYPE_REAL, 0) )
        xTrack = c4d.CTrack(tag, descID )
        xTrack.GetCurve()

returns the following,

X Slider value:: 0.0
mMover/Null with ID 5140 at 11746145920>
Traceback (most recent call last):
  File "~/Library/C4D_Plugins/C4D_mLab_Plugins_R23/mMover/mMover.pyp", line 313, in Execute
    xTrck = c4d.CTrack(tag, descID )
BaseException: the plugin 'c4d.CTrack' (ID 5350) is missing. Could not allocate instance

If someone knows how to get rid of this problem it would help me a lot, as well as maybe the whole community.

Thanks!
Christophe

Hey @mocoloco,

good morning 🙂

Is that possible to force a GUI redraw from a TAG?

You are bound there to the same threading restrictions as with adding or removing things. But other than for modifying the scene, which Cinema 4D will carry out outside of the main thread and then possibly crash, raising events, as for example a redraw event, is not possible at all from outside of the main thread. The first thing methods like c4d.DrawViews do, is check for being on the main (drawing) thread and when they are not, they get out.

So, it is possible, but you must be on the main thread. This will never happen on TagData.Execute, but it will on TagData.Message for example. So you can force a redraw when a user interacted with a parameter for example.

Cheers,
Ferdinand

MAXON SDK Specialist
developers.maxon.net

Hey @mocoloco,

Thank you for reaching out to us and thank you for your extensive documentation efforts.

There is unfortunately no executable code which makes it always hard to understand and find problems, but what stands out to me is:

  • It is not clear to me at whose Init method we are looking at, but from the context I assume it is mMoverTagData.Init
  • mmObj = c4d.documents.GetActiveDocument().GetActiveTag() should not be used in a NodeData plugin. You should use node passed into the function when you want to reference the tag instance itself. When mmObj is not meant to be the tag itself, then you should get the node document with node.GetDocument() and search there for your tag.
  • mmObj is either a BaseTag or None.
  • You construct a DescID for (ID_BASEOBJECT_ROTATION, VECTOR_X) which expands to (904, 1000).
  • A BaseTag has no vector parameters where a BaseObject has its base type vector parameters as for example ID_BASEOBJECT_ROTATION, etc. (a tag has no transform).
  • The float parameters mAXIS_X, mAXIS_Y, and mAXIS_Z in your plugin start at 1550.
  • With c4d.CTrack(mmObj, descID):
    • You are trying to write into an non-existent parameter in mmObj (unless you defined a vector at 904 and did not show that).
    • It is a bit ambiguous what you want to do here, but I assume you want to create a track for your mAXIS_X parameter.
    • But your DescId is for a float component inside a vector and you define a float, not a vector.

You seem to misunderstand how DescID work, which is not too surprising since they are sparsely documented. Find below an example or look at this posting for more information.

# This DescID addresses the X component of a vector parameter called ID_M_MOVE_AXIS.
descId: c4d.DescID= c4d.DescID(
    # The first desc level, we are addressing here the vector itself.
    c4d.DescLevel(   
        c4d.ID_M_MOVE_AXIS, # The ID of the parameter.
        c4d.DTYPE_VECTOR,   # Its data type.
        0),                 # The creator ID, 0 means undefined.
    # The second desc level, here we are addressing a sub-channel/parameter within a parameter, here
    # the X component of #ID_M_MOVE_AXIS.
    c4d.DescLevel(
        c4d.VECTOR_X,      # The ID
        c4d.DTYPE_REAL,    # The data type. 
        0)                 # The creator ID.
    )

So, when you want to create a track for the mAXIS_X parameter on an instance of your tag plugin, its ID would be:

xAxisId: c4d.DescID = c4d.DescID(c4d.DescLevel(c4d.mAXIS_X, c4d.DTYPE_REAL, 0))

Cheers,
Ferdinand

MAXON SDK Specialist
developers.maxon.net

Hi @ferdinand,

Thanks a lot for the whole explanation. Indeed, it's a bit confuse on how to point some ID. I'm sorry to not share the whole source code but it's under NDA, so I can only share some excerpt, and that do not help to have the big picture, sorry for that.

I went a bit in-depth and tried some stuff after reading your explanations, and the posting on DescID (even if I still don't get why I have to write c4d.mAXIS_X instead of mAXIS_X).

Let me rephrase some part to be on the same page and to remove a the confusions.

First, as I was stuck on getting what I tried on Execute(), which was the place where I would like to set the CTrack, I indeed tried to get it set in the mMoverTagData.Init() - which I agree, is maybe not the right way to do.

Then, mAXIS_X is indeed a real and not a vector which is displayed in a slider form.

So, I went back to my first idea to set the CTrack inside the Execute(self, tag, doc, op, bt, priority, flags) method and made the DescID from what I understood. To be clear I want to create a CTrack for the mAXIS_X parameter with ID 1550

I wrote this,

mPLUGIN_ID = 1059303 #Unique ID
mTAG_NAME = "mMover"
mAXIS_A1 = 1550

class mMover ( plugins.TagData ):

    def __init__ (self):
        super(mMover, self).__init__()
        pass


    def Execute(self, tag, doc, op, bt, priority, flags): 
        print ( f"GetTags {op.GetTag( mPLUGIN_ID )}" )
        currentTAG = op.GetTag( mPLUGIN_ID )
        
        descID = c4d.DescID(c4d.DescLevel(c4d.mAXIS_X, c4d.DTYPE_REAL, 0))
        # descID = c4d.DescID( c4d.DescLevel( c4d.ID_BASEOBJECT_ROTATION, c4d.DTYPE_VECTOR, 0), c4d.DescLevel(c4d.VECTOR_X, c4d.DTYPE_REAL, 0) )
        xTrck = c4d.CTrack( currentTAG, descID )
        xCrv = xTrck.GetCurve()


# Plug-in initialisation and declaration
if __name__ == "__main__":
    # Load the plugin icon
    icon_absolute_path = os.path.join(os.path.dirname(__file__), 'res/icons', 'icon.png')
    plugin_icon = c4d.bitmaps.BaseBitmap()

    if plugin_icon is None:
        raise RuntimeError("Failed to load icon resource.")
    
    if plugin_icon.InitWith(icon_absolute_path)[0] != c4d.IMAGERESULT_OK: 
        raise RuntimeError("Failed to initialize icon resource.")

    # Register the plugin
    plugins.RegisterTagPlugin(
        id = mPLUGIN_ID,
        str = mTAG_NAME,
        g = mMover,
        description = 'mMover',
        info = c4d.TAG_EXPRESSION | c4d.TAG_VISIBLE | c4d.TAG_IMPLEMENTS_DRAW_FUNCTION | c4d.TAG_HIERARCHICAL,
        icon = plugin_icon
    )

This code show in console,

    GetTags <c4d.BaseTag object called mMover/mMover with ID 1059303 at 4892750336>

No more error, but no CTrack created, at list it does not appears in the Dope Sheet; so I should still makes something wrong somewhere.
Does my pointing is now correct or still wrong for my DescID?

Does this helps to point the problem?

Thanks in advance,
Christophe

Small addition to my previous post, the CTrack seems exists but do not appears in the Dope Sheet, is that normal, and if so why?

Doing this,

def Execute(self, tag, doc, op, bt, priority, flags): 
        print ( f"GetTags {op.GetTag( mPLUGIN_ID )}" )
        currentTAG = op.GetTag( mPLUGIN_ID )
        
        descID = c4d.DescID(c4d.DescLevel(c4d.mAXIS_X, c4d.DTYPE_REAL, 0))
        # descID = c4d.DescID( c4d.DescLevel( c4d.ID_BASEOBJECT_ROTATION, c4d.DTYPE_VECTOR, 0), c4d.DescLevel(c4d.VECTOR_X, c4d.DTYPE_REAL, 0) )
        xTrck = c4d.CTrack( currentTAG, descID )
        xCrv = xTrck.GetCurve()
        print(f"CTrack . { xTrck }")

Returns,

CTrack . <c4d.CTrack object called axis_X/Track with ID 5350 at 4892757504>

If I'm creating keyframes, will they be visibles in the Dope Sheet viewer or will they remains invisible?

Hi @mocoloco,

I still don't get why I have to write c4d.mAXIS_X instead of mAXIS_X

You do not have to but it is convenient. An ID ist just a number and integer symbols like c4d.ID_USERDATA are just aliases for such number. 700 and ID_USERDATA do the same thing. When you provide a resource file, here one for your tag, Cinema 4D automatically exposes all resource symbols in the c4d namespace. You can also just redefine that symbol yourself, it is all the same. But it is extra work 😉

# These are all the same.
bobIsYourUncle: int = 1550

mMoverTag[c4d.mAXIS_X] = 1.0
mMoverTag[1550] = 1.0
mMoverTag[bobIsYourUncle] = 1.0

No more error, but no CTrack created, at list it does not appears in the Dope Sheet; so I should still makes something wrong somewhere. Does my pointing is now correct or still wrong for my DescID?

Your DescID is correct, the problem is that you never add the track to your tag, you never call tag.InsertTrackSorted(xTrack). But you should not do this in TagData.Execute, as lined out below.

First, as I was stuck on getting what I tried on Execute(), which was the place where I would like to set the CTrack, I indeed tried to get it set in the mMoverTagData.Init() - which I agree, is maybe not the right way to do.

I never intended to imply that you should use TagData.Execute. You cannot add or remove elements from a scene as for example objects, tags, tracks or points in this method. Because you are outside of the main thread in Execute and bound to the threading restrictions. Init would be sort of okay, but a node has not yet been attached to a document when it is called, which probably would complicate things.

But I think I understand what you want to do generally. Although I still do not understand why you want to create a track on your mAXIS_X parameter itself, which then leads to weird feedback loops when the parameter is used to both define the values of a track but is also driven by it.

The other problem is that what you want to do in general, modify a scene from a NodeData plugin is not the greatest design pattern. Doing this will often lead to trouble in Cinema 4D. It is okay to do this in the form of a button and when the user clicks the button, something is added or removed. But in the dynamic fashion you want to do it, this is not a good path. If you want to dynamically generate animations based on a set of parameters, you should implement a CommandData plugin instead.

I have provided nonetheless an example for both driving an x-position animation of an object hosted by a plugin tag with tracks, as well as driving the same animation procedurally. It is only the latter which is intended to be done with tags. If you want to to do this, you must offload your scene modifications to NodeData.Message, as this method is commonly called on the main thread. But you must still check for being there.

Cheers,
Ferdinand

Plugin: pc14315.zip

Result: I am first driving the animation of the cube procedurally and then in the second half with tracks. In both cases the x-axis position of the object hosting the tag is driven by the parameters in the tag.
test.gif

Code:

"""Implements a tag which drives the position of a host object procedurally or via tracks.

The tag implements two modes, a "Procedural" and a "Track" mode. In procedural mode, the tag does
what tags are supposed to do, it drives procedurally parameters of the scene, here the x-axis
position of its host object. In track mode, it generates the same animation for its host object,
but uses a key framed animation instead which is generated every time the user interacts with the
GUI.

Note:
    It is not recommended to use NodeData plugins to add or remove elements (e.g., objects, 
    materials, tags, shaders, tracks, keys, points, etc.) from a scene in the manner shown here. 
    
    It is okay to do this based on non-animateable parameters, e.g., a button. But as this is based
    on a set of animateable parameters #P, one can run into problems. This can happen when the 
    document is rendered and #P is also animated. The only safe way to add and remove elements are
    therefore buttons in NodeData plugins.

    Respected must be in any case the threading restrictions of TagData.Execute, it is never allowed
    to add or remove scene elements from there. This solution will not crash, but is bound to the
    main thread which can cause the plugin to (intentionally) stop operating in the mentioned 
    scenario.
"""
import c4d

class mMoverTagData(c4d.plugins.TagData):
    """Implements a tag which drives the position of a host object procedurally or via tracks.
    """
    # The plugin ID and a precise definition of the relative x-axis position parameter of an object.
    ID_PLUGIN: int = 1059303
    DID_OBJECT_REL_POSITION_X: c4d.DescID = c4d.DescID(
        c4d.DescLevel(c4d.ID_BASEOBJECT_REL_POSITION, c4d.DTYPE_VECTOR, 0),
        c4d.DescLevel(c4d.VECTOR_X, c4d.DTYPE_REAL, 0))
        
    def Init(self, node: c4d.GeListNode) -> bool:
        """Called to initialize a mMover tag instance.

        Args:
            node: The BaseTag instance representing this plugin object.
        """
        self.InitAttr(node, int, c4d.ID_MMOVER_MODE)
        self.InitAttr(node, c4d.BaseTime, c4d.ID_MMOVER_ANIMATION_MIN)
        self.InitAttr(node, c4d.BaseTime, c4d.ID_MMOVER_ANIMATION_MAX)
        self.InitAttr(node, float, c4d.ID_MMOVER_AXIS_X_MIN)
        self.InitAttr(node, float, c4d.ID_MMOVER_AXIS_X_MAX)

        node[c4d.ID_MMOVER_MODE] = c4d.ID_MMOVER_MODE_PROCEDURAL
        node[c4d.ID_MMOVER_ANIMATION_MIN] = c4d.BaseTime(0)
        node[c4d.ID_MMOVER_ANIMATION_MAX] = c4d.BaseTime(1)
        node[c4d.ID_MMOVER_AXIS_X_MIN] = 0.
        node[c4d.ID_MMOVER_AXIS_X_MAX] = 1000.

        return True

    def Execute(self, tag: c4d.BaseTag, doc: c4d.documents.BaseDocument, op: c4d.BaseObject,
                bt: c4d.threading.BaseThread, priority: int, flags: int) -> int:
        """Called when expressions are evaluated to let a tag modify a scene.

        It is important to understand that tags are also bound by threading restrictions in this
        method, we are here NOT on the main thread. I.e., we cannot add or remove nodes from the
        scene as for example tracks. All we can do, is change parameters or the values of other
        data. We can for example change the point positions of a point object, but we cannot add
        or remove points.

        Args:
            tag: The BaseTag instance representing this plugin object.
            op: The BaseObject hosting the tag.
            doc: The document both the tag and object are attached to.
        """
        # Bail when the tag is not in procedural mode.
        if tag[c4d.ID_MMOVER_MODE] != c4d.ID_MMOVER_MODE_PROCEDURAL:
            return c4d.EXECUTIONRESULT_OK

        # Set the x axis position of the tag host for the current time.
        mg: c4d.Matrix = op.GetMg()
        v: float = mMoverTagData._GetMoveOffset(tag, doc.GetTime())
        mg.off = c4d.Vector(v, mg.off.y, mg.off.z)
        op.SetMg(mg)

        return c4d.EXECUTIONRESULT_OK

    def Message(self, node: c4d.GeListNode, mid: int, data: object) -> bool:
        """Called to signal events to the tag.

        Used in this case to react to parameter changes, inducing track modifications on the host
        object of the tag when the tag is in "Track" mode.

        Args:
            node: The BaseTag instance representing this plugin object.
            mid: The ID of the raised event.
            data: The data associated with the event.
        """
        # Bail when the tag is not attached to a document or an object as we will need both.
        doc: c4d.documents.BaseDocument = node.GetDocument()
        obj: c4d.BaseObject = node.GetObject()
        if None in (doc, obj):
            return True

        # Bail when this is not an event for a parameter value being changed.
        if mid != c4d.MSG_DESCRIPTION_POSTSETPARAMETER:
            return True

        # Get the DescID of the parameter which has changed and get the ID of the first desc level.
        pid: int = data["descid"][0].id

        # Bail when the mode of the tag is "Procedural" and it was not the mode which has changed.
        isProceduralMode: bool = node[c4d.ID_MMOVER_MODE] == c4d.ID_MMOVER_MODE_PROCEDURAL
        if pid != c4d.ID_MMOVER_MODE and isProceduralMode:
            return True
        
        # Bail when this is not being called on the main thread, as all modifications we intend to
        # do below require use being on the main thread (adding or removing tracks or keys).
        if not c4d.threading.GeIsMainThreadAndNoDrawThread():
            print ("Warning: Failed to execute 'mMover' tag logic due to threading restrictions.")
            return True
        
        # The mode switched from "Track" to "Procedural", we attempt to remove an existing track.
        if pid == c4d.ID_MMOVER_MODE and isProceduralMode:
            mMoverTagData._RemoveTrack(obj)
        # The user either turned on track mode or changed one of the parameters while being in
        # track mode, we add or modify the track.
        else:
            mMoverTagData._AddOrModifyTrack(obj, node)

        return True
    
    # --- Custom Methods ---------------------------------------------------------------------------

    @staticmethod
    def _GetMoveOffset(tag: c4d.BaseTag, t: c4d.BaseTime) -> float:
        """Computes a new x-axis offset based on the data in #tag and the given time #t.

        This is a custom method of this plugin and not part of the TagData interface.

        Args:
            tag: The tag to compute the offset for.
            t: The time at which to compute the offset.
        """
        # Bail when the tag is not attached to a document.
        doc: c4d.documents.BaseDocument = tag.GetDocument()
        if not doc:
            raise RuntimeError(f"{tag} is not attached to a document.")

        # Get the min, max, and current frame for the animation described by the tag data.
        fps: int = doc.GetFps()
        minFrame: int = tag[c4d.ID_MMOVER_ANIMATION_MIN].GetFrame(fps)
        maxFrame: int = tag[c4d.ID_MMOVER_ANIMATION_MAX].GetFrame(fps)
        currentFrame: int = t.GetFrame(fps)

        # Get the min and max offset values and compute with them and the time values the offset for
        # the given time #t.
        minValue: float = tag[c4d.ID_MMOVER_AXIS_X_MIN]
        maxValue: float = tag[c4d.ID_MMOVER_AXIS_X_MAX]
        v: float = c4d.utils.RangeMap(currentFrame, minFrame, maxFrame, minValue, maxValue, True)

        return v

    @staticmethod
    def _AddOrModifyTrack(obj: c4d.BaseObject, tag: c4d.BaseTag) -> None:
        """Adds or modifies the track for a relative x-position animation on #obj.

        This is a custom method of this plugin and not part of the TagData interface.

        Args:
            obj: The object to add or modify the track for.
            tag: The mMover tag carrying the data.
        """
        # Attempt to retrieve an existing track or create a new one.
        track: c4d.CTrack = obj.FindCTrack(mMoverTagData.DID_OBJECT_REL_POSITION_X)
        if track is None:
            track = c4d.CTrack(obj, mMoverTagData.DID_OBJECT_REL_POSITION_X)
            obj.InsertTrackSorted(track)
        
        # Flush the keys of the curve associated with the track in case we are dealing with an 
        # already existing track.
        curve: c4d.CCurve = track.GetCurve()
        curve.FlushKeys()

        # Get the time and animation values and write them into two keys.
        minTime: c4d.BaseTime = tag[c4d.ID_MMOVER_ANIMATION_MIN]
        maxTime: c4d.BaseTime = tag[c4d.ID_MMOVER_ANIMATION_MAX]
        minValue: float = mMoverTagData._GetMoveOffset(tag, minTime)
        maxValue: float = mMoverTagData._GetMoveOffset(tag, maxTime)

        for time, value in ((minTime, minValue), (maxTime, maxValue)):
            key: c4d.CKey = curve.AddKey(time)["key"]
            if key:
                key.SetValue(curve, value)

    @staticmethod
    def _RemoveTrack(obj: c4d.BaseObject) -> None:
        """Removes the track for a relative x-position animation on #obj.

        This is a custom method of this plugin and not part of the TagData interface.

        Args:
            obj: The object to attempt to remove the track from.
        """
        track: c4d.CTrack = obj.FindCTrack(mMoverTagData.DID_OBJECT_REL_POSITION_X)
        if track:
            track.Remove()


def RegisterPlugins() -> None:
    """Registers the plugins contained in this file.
    """
    if not c4d.plugins.RegisterTagPlugin(
            id=mMoverTagData.ID_PLUGIN,
            str="mMover Tag",
            info=c4d.TAG_EXPRESSION | c4d.TAG_VISIBLE,
            g=mMoverTagData, description="tmmover",
            icon=c4d.bitmaps.InitResourceBitmap(c4d.ID_MODELING_MOVE)):
        print("Warning: Failed to register 'mMover' tag plugin.")


if __name__ == "__main__":
    RegisterPlugins()

MAXON SDK Specialist
developers.maxon.net

Hi @ferdinand,

I went a bit further yesterday and finally found the whole way to get it work.
The code part is here below for the community.

I'm still stuck on the force redraw of the GUI when creating CTrack or removing them. c4d.EventAdd() seems not working fine, and c4d.gui.GeUpdateUI() can only be invoked from main thread. Is that possible to force a GUI redraw from a TAG?

This is the excerpt from the whole code for the CTrack only.
You have to use this code by adapting it in your own context

# Create CTrack when not available
currentTAG = op.GetTag( mPLUGIN_ID )
descID = c4d.DescID(c4d.DescLevel(c4d.mAXIS_X, c4d.DTYPE_REAL, 0))
if currentTAG.FindCTrack(descID) is None:
     trck_X = c4d.CTrack( currentTAG, descID )
     currentTAG.InsertTrackSorted(trck_X)
     print( "CTrack created" )
     c4d.EventAdd()


# Remove all the CTracks
currentTAG = op.GetTag( mPLUGIN_ID )
if (len (currentTAG.GetCTracks()) > 0):
       for track in currentTAG.GetCTracks():
              track.FlushData()
              track.Remove()
              print( "CTracks removed" )

Thanks,
Christophe

Hi @ferdinand

I haven't refreshed the thread prior posting my last message, so I missed your reply with the whole explanation and example. Thanks a lot, this fulfil the missing gap.

Thanks a lot,
Christophe

Hey @mocoloco,

good morning 🙂

Is that possible to force a GUI redraw from a TAG?

You are bound there to the same threading restrictions as with adding or removing things. But other than for modifying the scene, which Cinema 4D will carry out outside of the main thread and then possibly crash, raising events, as for example a redraw event, is not possible at all from outside of the main thread. The first thing methods like c4d.DrawViews do, is check for being on the main (drawing) thread and when they are not, they get out.

So, it is possible, but you must be on the main thread. This will never happen on TagData.Execute, but it will on TagData.Message for example. So you can force a redraw when a user interacted with a parameter for example.

Cheers,
Ferdinand

MAXON SDK Specialist
developers.maxon.net

Good morning @ferdinand!

I modified the whole code to always do my changes inside TagData.Message now instead of doing them in TagData.Execute, which should prevent any crash - even if I didn't got one, it seems far better to stick on the good rules and habit.

Have a good day!
Christophe