Solved Tag plugin general issues (crash, threading, message)

Hello 😭,
at the moment I try to create a tag plugin but it seems it is a bit more complicated than with an object plugin :-).

I added a small snippet of the plugin as testtrans.zip zipfile.
In the res-folder is the Cinema4d scene file (crashes) and a json-file.

In the layout res-file I created a simple Baselink, Filename , Button(I want avoid) and BaseTime

Function of the test plugin:

  • The user has to drag a pose morph tag into the Baselink.
  • Then he has to load the json1.json file as link

The user loads the *.json file (res-folder), I catch this via c4d.MSG_DESCRIPTION_POSTSETPARAMETER and this triggers the function load_transcript() and loads the *.json file content into a member variable self.data.

Questions:

  • Main Question: In the Execute(self, tag, doc, op, bt, priority, flags) method I want to to some actions. But here Cinema is crashing. Probably it has something to do with threading and that it asks the tags description.:-).
    I commented out the position where it is crashing. So if I replace the tag[PY_SYNC_START] simply with an integer it works

  • Is the method I used in the Message() method correct to detect the changes in the filename link? It doesn't work so I added a button to load it , then it works, but I want it to load as soon as the user changes the link to the json file. It calls the print() function in the POSTSETPARAMETER thing, but it doesn't trigger the load_transfile() method.

  • Another small bug maybe in Cinema 4D. When I set the ACCEPT parameter in the Baselink in the res-file to {Tposemorph;} he gives me a ressource error but when I type Tdisplay it works ??????

Here is the code:

import c4d
import os
from c4d import bitmaps, gui, plugins
import json

# be sure to use a unique ID obtained from www.plugincafe.com
PLUGIN_ID = 5554443  # just a test ID

# ID's
PY_MORPH_LINK = 10000
PY_TRANSCRIPT_LINK = 10001
PY_SYNC_START = 10018
PY_LOAD_TRANSCRIPT = 10021


# The Plugin Class
class TestTrans(plugins.TagData):

    def __init__(self):       

        self.data = None

    def Init(self, node):

        self.InitAttr(node, c4d.BaseList2D, PY_MORPH_LINK)
        # node[PY_MORPH_LINK] = None

        self.InitAttr(node, str, PY_TRANSCRIPT_LINK)
        # node[PY_TRANSCRIPT_LINK] = ""

        self.InitAttr(node, c4d.BaseTime, PY_SYNC_START)
        node[PY_SYNC_START] = c4d.BaseTime()

        return True
    
    def load_transcript(self, op):

        if op[PY_TRANSCRIPT_LINK] != "":
            with open(op[PY_TRANSCRIPT_LINK], "r") as transfile:
                self.data = json.load(transfile)

    def move2(self, tag, frame, value):
        print("2. Hello in function")

    def Execute(self, tag, doc, op, bt, priority, flags):

        if self.data:

            pose_morph = tag[PY_MORPH_LINK]

            if not pose_morph:
                return c4d.EXECUTIONRESULT_OK

            morph_count = pose_morph.GetMorphCount()
            print(pose_morph)
            print(self.data["words"])

            frame = doc.GetTime().GetFrame(doc.GetFps()) - tag[PY_SYNC_START]  #Here it is crashing
            print(frame)

            if frame == 10:
                print("1. Hello")

            self.move2(tag, frame, 1)

        return c4d.EXECUTIONRESULT_OK
   

    def Message(self, node, type, data):

        if type == c4d.MSG_DESCRIPTION_COMMAND:

            if data["id"][0].id == PY_LOAD_TRANSCRIPT:
                self.load_transcript(node)

        if type == c4d.MSG_DESCRIPTION_POSTSETPARAMETER:
            if data["descid"][0].id == PY_TRANSCRIPT_LINK:
                print("POSTSETPARAMETER")
                self.load_transcript(node)

        return True


# main
if __name__ == "__main__":
    # load icon.tif from res into bmp
    bmp = bitmaps.BaseBitmap()
    dir, file = os.path.split(__file__)
    fn = os.path.join(dir, "res", "icon.tif")
    bmp.InitWith(fn)

    new_bmp = c4d.bitmaps.InitResourceBitmap(1051133)

    # register the plugin
    c4d.plugins.RegisterTagPlugin(id=PLUGIN_ID,
                                  str="Test-Trans",
                                  info=c4d.TAG_EXPRESSION | c4d.TAG_VISIBLE,
                                  description="testtrans",
                                  g=TestTrans, icon=bmp)

res-file:

CONTAINER Testtrans
{
	NAME TESTTRANS;
	INCLUDE Texpression;

	GROUP ID_TAGPROPERTIES
	{
        LINK PY_MORPH_LINK
        {
            ACCEPT { Tbase; }
        }

        FILENAME PY_TRANSCRIPT_LINK
        {
          ANIM ON;
        }

        BASETIME PY_SYNC_START {}


	}
}

header-file:

#ifndef _TESTTRANS_H_
#define _TESTTRANS_H_
enum
{
    PY_MORPH_LINK           = 10000,
    PY_TRANSCRIPT_LINK      = 10001,
    PY_SYNC_START           = 10018,

	//GV_XPEMIT_NODE_DUMMY
};
#endif // ${FILE_NAME}

string-file:

STRINGTABLE Testtrans // derselbe Name wie res-file
{
	TESTTRANS                       "Test-Trans";
	PY_MORPH_LINK                 "Pose-Morph-Tag";
	PY_TRANSCRIPT_LINK            "Transcript";
       PY_SYNC_START                 "Start";

}

Best Regards
Tom

Hey @ThomasB,

Thank you for reaching out to us. Thank you for your very clear posting, but your three questions should have been at least two or better three topics because they are all entirely different subjects. I did not fork your topic in this case because you did put so much work into your posting, but I would want to give you a gentle reminder that topics should only contain a singular topic.

About Your Questions

Crash in TagData.Execute

No, this has nothing to do with threading and your code is (mostly) correct. There is a bug in 2023 in BaseTime.__sub__, it has already been fixed in the API of the upcoming release.

Note that we support BaseTime.__sub__ with other being an int; this has been added in 2023 and is the cause of the bug. But we do not support __rsub__. I.e., BaseTime - int (__sub__) is supported but int - BaseTime (__rsub__) is not. The int support has also not yet been documented.

When you call frame = doc.GetTime().GetFrame(doc.GetFps()) - tag[PY_SYNC_START] you invoke __rsub__ which then defaults to __sub__ and triggers the bug. When subtracting an int from a BaseTime, the int is also interpreted as a value in seconds and not as frame value, because a BaseTime has no inherent fps.

>> import c4d
>> bt: c4d.BaseTime = c4d.BaseTime(50, 25)
>> bt.Get()
2.0
>> # 1 will be subtracted from the value in seconds which represented by #bt.
>> (bt - 1).Get()
1.0

So, you probably meant here:

fps: int = doc.GetFps()
frame: int = doc.GetTime().GetFrame(fps) - tag[PY_SYNC_START].GetFrame(fps)

Does not Invoke load_transcript

Well, when your statement print("POSTSETPARAMETER") is being hit, so will be the line after it. The interpreter will not magically not call something. Please also do understand that we cannot debug your code for you.

What can happen here is that it looks like that load_transcript is not running because:

  1. Your condition op[PY_TRANSCRIPT_LINK] != "" is not being met.
  2. Whatever relies on self.data only later reacts to it being updated or never reacts at all.
  3. You get on NodeData.Message calls that are not on the main thread. All file operations are not thread-safe, you cannot have two things accessing one file at the same time. You should wrap all file operations in a c4d.threading.GeIsMainThread condition.

In the end, you probably have to do a bit more debugging here.

That MSG_DESCRIPTION_POSTSETPARAMETER is not broadcasted for parameter changes of a node that is linked is normal, because you listen here to the messages of your node and not the messages of the linked node.

Tposemorph Symbol

Yes, you are right, the symbol is not exposed in resources, that is an oversight or bug if you will. You must use the numeric value of Tposemorph instead:

LINK PY_MORPH_LINK
{
  ANIM OFF;
  ACCEPT { 1024237; } // Value for Tposemorph
}

Cheers,
Ferdinand

MAXON SDK Specialist
developers.maxon.net

Hey @ThomasB,

Thank you for reaching out to us. Thank you for your very clear posting, but your three questions should have been at least two or better three topics because they are all entirely different subjects. I did not fork your topic in this case because you did put so much work into your posting, but I would want to give you a gentle reminder that topics should only contain a singular topic.

About Your Questions

Crash in TagData.Execute

No, this has nothing to do with threading and your code is (mostly) correct. There is a bug in 2023 in BaseTime.__sub__, it has already been fixed in the API of the upcoming release.

Note that we support BaseTime.__sub__ with other being an int; this has been added in 2023 and is the cause of the bug. But we do not support __rsub__. I.e., BaseTime - int (__sub__) is supported but int - BaseTime (__rsub__) is not. The int support has also not yet been documented.

When you call frame = doc.GetTime().GetFrame(doc.GetFps()) - tag[PY_SYNC_START] you invoke __rsub__ which then defaults to __sub__ and triggers the bug. When subtracting an int from a BaseTime, the int is also interpreted as a value in seconds and not as frame value, because a BaseTime has no inherent fps.

>> import c4d
>> bt: c4d.BaseTime = c4d.BaseTime(50, 25)
>> bt.Get()
2.0
>> # 1 will be subtracted from the value in seconds which represented by #bt.
>> (bt - 1).Get()
1.0

So, you probably meant here:

fps: int = doc.GetFps()
frame: int = doc.GetTime().GetFrame(fps) - tag[PY_SYNC_START].GetFrame(fps)

Does not Invoke load_transcript

Well, when your statement print("POSTSETPARAMETER") is being hit, so will be the line after it. The interpreter will not magically not call something. Please also do understand that we cannot debug your code for you.

What can happen here is that it looks like that load_transcript is not running because:

  1. Your condition op[PY_TRANSCRIPT_LINK] != "" is not being met.
  2. Whatever relies on self.data only later reacts to it being updated or never reacts at all.
  3. You get on NodeData.Message calls that are not on the main thread. All file operations are not thread-safe, you cannot have two things accessing one file at the same time. You should wrap all file operations in a c4d.threading.GeIsMainThread condition.

In the end, you probably have to do a bit more debugging here.

That MSG_DESCRIPTION_POSTSETPARAMETER is not broadcasted for parameter changes of a node that is linked is normal, because you listen here to the messages of your node and not the messages of the linked node.

Tposemorph Symbol

Yes, you are right, the symbol is not exposed in resources, that is an oversight or bug if you will. You must use the numeric value of Tposemorph instead:

LINK PY_MORPH_LINK
{
  ANIM OFF;
  ACCEPT { 1024237; } // Value for Tposemorph
}

Cheers,
Ferdinand

MAXON SDK Specialist
developers.maxon.net

@ferdinand

Thank you Ferdinand for your leniency regarding the three questions..
Will take note of that in the future.

=====BaseTime=====
Which just surprises me about the BaseTime problem is, that it worked in the Python tag. I didn't get an error message and the prototype worked very well.
So I also tried instead of BaseTime int type value in the plugin, but it crashed. It confused me a bit. So I try your suggestion.

# In the python tag it worked with int
# starf_frame was an int
 frame = doc.GetTime().GetFrame(doc.GetFps()) - op[c4d.ID_USERDATA,start_frame]

====PoseMorph Symbol====
Regarding the posmorph ID, I should have figured that out myself

Thanks for your quick respond

Tom

This post is deleted!