Solved Crash when apply a Tag plugin.

Hello everyone,

I am working on a tag plugin with cloner, I get a copy of teh Github py-look at camera.pyp example and start my first step with a tag plugin, the basic goal is stiky a link object to a index of a cloner object and also can be offset.

But I modifiy the example and test it, cinema always crash when apply the tag to an object. even I just return exe_ok ,
I don't know what is happend.

I already read some topic:

Here is the little file of the plugin.tag test.zip

By the way, is there another way to create UI in python? the res solution is herrible to use and no document to search😢

Cheers~

Hello @Dunhou,

I checked the code you provided. There was a wrong line in your res file.

 INCLUDE Taligncloner; 

has to be:

 INCLUDE Texpression;
// The description defintion of the tag Taligncloner.
CONTAINER Taligncloner
{
    NAME Taligncloner;
    INCLUDE Texpression;

    // The main "Tag" tab of the tag.
    GROUP ID_TAGPROPERTIES
    {
        LINK LINK_NODE { FIT_H; ACCEPT { Obase; } }
        REAL CLONER_INDEX  { UNIT METER; STEP 1; }
        BOOL STIKY_ON_CLONER { }
    }
}

Besides the fact that the object which is supposed to stick to the cloner jumps around each refresh, it now works for me in S26. 😉

But you are right, creation and editing of the res file is a royal pain in the a**. 😄

Hope this helped.

Cheers,
Sebastian

Hey @HerrMay ,Thanks for that!

What is the INCLUDE Texpression; means? Could you explain a little deeper, I did't find a document for those things😧

And honestly, The .res file code style is no document, I had to search in the resoure folder and copy and guess how it works,
such painful...

And yes , jumps around each refresh has it is also a problem , I test on a python tag first, it also happened, This my first time to a node plugin but not a command plugin , it is an adventure😵

Cheers~
DunHou

Hello @Dunhou,

I'm not super sure about what INCLUDE Texpression means in regards to c4ds deeper resource implementation. I guess it's just the way to say how your tag plugin should be treated. That it should be calculated as an expression in c4ds execution pipeline, if that makes sense.
Maybe someone from the sdk team can jump in here and provide a little more elaborate answer. 😄

By the way, a nice idea for a tag plugin. 😉
I took the freedom to investigate the sticking respectively the refreshing issue that is. And I made the index parameter loop as well. 😉

Find below a commented version of your code that works like a charm for me.

Cheers,
Sebastian

import os
import c4d
from c4d.modules import mograph as mo

# Be sure to use a unique ID obtained from www.plugincafe.com
PLUGIN_ID = 1060757


class LookAtCamera(c4d.plugins.TagData):
    """Look at Camera"""
    
    def Init(self, node: c4d.GeListNode):
        """Called when Cinema 4D Initialize the TagData (used to define, default values).

        Args:
            node (c4d.GeListNode): The instance of the TagData.

        Returns:
            True on success, otherwise False.
        """
        self.InitAttr(node, int, c4d.CLONER_INDEX)
        self.InitAttr(node, c4d.BaseObject, c4d.LINK_NODE)
        self.InitAttr(node, bool, c4d.STIKY_ON_CLONER)
        
        node[c4d.CLONER_INDEX] = 0
        node[c4d.LINK_NODE] = None
        node[c4d.STIKY_ON_CLONER] = False

        return True
    
    def Execute(self, tag, doc, op, bt, priority, flags):
        """Called by Cinema 4D at each Scene Execution, this is the place where calculation should take place.

        Args:
            tag (c4d.BaseTag): The instance of the TagData.
            doc (c4d.documents.BaseDocument): The host document of the tag's object.
            op (c4d.BaseObject): The host object of the tag.
            bt (c4d.threading.BaseThread): The Thread that execute the this TagData.
            priority (EXECUTIONPRIORITY): Information about the execution priority of this TagData.
            flags (EXECUTIONFLAGS): Information about when this TagData is executed.
        """

        stick = tag[c4d.STIKY_ON_CLONER] # Should the linked node stick?
        link_node = tag[c4d.LINK_NODE] # The linked node

        # If there is no linked node or stick is not enabled there is no need for computation. Return early.
        if not link_node or not stick:
            return c4d.EXECUTIONRESULT_OK

        # Check whether op is a cloner object
        if not op.CheckType(1018544): # I like to use op.CheckType here over op.GetType() because it directly returns a boolean value I can compare with.
            return c4d.EXECUTIONRESULT_OK
        
        # Get MoData
        md = mo.GeGetMoData(op)
        if not md:
            return c4d.EXECUTIONRESULT_OK
        
        user_index = tag[c4d.CLONER_INDEX] # The index chosen by the user
        count = md.GetCount() # Get the clone count of our cloner
        marr = md.GetArray(c4d.MODATA_MATRIX) # Get the MoData array which holds the matrices of each clone
        index = user_index % count # Make the index loop so that you get back to e.g. index 0 if index is e.g. greater than your clone count. 

        cloner_mg = op.GetMg() # Get the global matrix of the cloner
        clone_mg = marr[index] # Get the matrix of the clone we want to stick our linked node to
        link_node.SetMg(cloner_mg*clone_mg) # Multiply both matrices and set the global matrix of the linked node to the matrix of the clone we get over our index
        link_node.Message(c4d.MSG_UPDATE) # Inform the linked node that it should update its matrix

        return c4d.EXECUTIONRESULT_OK

if __name__ == "__main__":
    # Retrieves the icon path
    directory, _ = os.path.split(__file__)
    fn = os.path.join(directory, "res", "icon.png")

    # Creates a BaseBitmap
    bmp = c4d.bitmaps.BaseBitmap()
    if bmp is None:
        raise MemoryError("Failed to create a BaseBitmap.")

    # Init the BaseBitmap with the icon
    if bmp.InitWith(fn)[0] != c4d.IMAGERESULT_OK:
        raise MemoryError("Failed to initialize the BaseBitmap.")

    c4d.plugins.RegisterTagPlugin(id=PLUGIN_ID,
                                  str="Taligncloner",
                                  info=c4d.TAG_EXPRESSION | c4d.TAG_VISIBLE,
                                  g=LookAtCamera,
                                  description="Taligncloner",
                                  icon=bmp)

Ohhh thanks dude, @HerrMay Great work for the fix, I have a test on script manager, but the plugin loading failed, anyway , thanks for your help, it real works.

yes, actually need a document or something with the gui with res file. but I think it is way loooooong in the todo list😧

Cheers~
DunHou

Hello @Dunhou,

Thank you for reaching out to us and thank you at @HerrMay for providing what I would think is the correct answer.

At least on my machine, nothing is crashing here, it is just that the description of the tag is malformed on line five.

6ff23335-b6c4-4d15-9ef4-47f3a84ca4b2-image.png

In this case, it is such a severe error, that Cinema 4D fails to load the description at all because it is cyclically referencing itself. The keyword INCLUDE in a resource allows us to include another resource in that resource. And when we try to reference the resource, we are defining, Cinema 4D is understandably confused. For less severe descriptions errors, one usually is able to then lick OK a few times to get Cinema 4D to load what it does understand. But that does not work here, and one is caught in an endless loop of self-reference.

In practice, INCLUDE is usually used to load in the base description of something. So, when we take the simple description of the opydoublecircle example plugin which only defines one parameter named PYCIRCLEOBJECT_RAD,

CONTAINER Opydoublecircle
{
	NAME Opydoublecircle;
	INCLUDE Obase; // Loads 

	GROUP ID_OBJECTPROPERTIES
	{
		REAL PYCIRCLEOBJECT_RAD { UNIT METER; MIN 0.0; }
	}
	INCLUDE Osplineprimitive;
}

we can see that Obase loaded the Basic and Coordinates tabs into that object description that every object must have. And Osplineprimitive loaded in the parameters which are shared by all spline primitives, as for example Plane, Reverse, etc.

c2a597a9-df44-439e-aa0c-62908933dbeb-image.png

When we remove Osplineprimitive, all these parameters will not be added to our object.

CONTAINER Opydoublecircle
{
	NAME Opydoublecircle;
	INCLUDE Obase; // Loads 

	GROUP ID_OBJECTPROPERTIES
	{
		REAL PYCIRCLEOBJECT_RAD { UNIT METER; MIN 0.0; }
	}
}

7a26bad0-db17-4ad5-ba7e-0de7490bc743-image.png

What slightly complicates this, is that (allmost) all NodeData derived plugin types must include at least their base description, e.g., Obase for objects, Tbase for tags, Mbase for materials, Xbase for shaders, and so on.

In some cases speccializations are allowed, as for example including Texpression instead of Tbase. Texpression extends Tbase and while Tbase is used for generic tags which usually do not modify the scene, e.g., the "Display" tag Tdisplay is based on Tbase:

CONTAINER Tdisplay
{
	NAME Tdisplay;
	INCLUDE Tbase;

	GROUP ID_TAGPROPERTIES
	{
		COLUMNS 2;
		BOOL DISPLAYTAG_AFFECT_DISPLAYMODE { }
		LONG DISPLAYTAG_SDISPLAYMODE
		{

Texpression is usually used by tags which do modify a scene in some shape or form, e.g, the "Look at Camera" tag Tlookatcamera:

CONTAINER Tlookatcamera
{
	NAME Tlookatcamera;
	INCLUDE Texpression;

	GROUP ID_TAGPROPERTIES
	{
		BOOL LOOKATCAMERA_PITCH { }
	}
}

The difference is then simply that such tags whill have the expression paramaters in their 'Basic' tab.

a91e446d-4b0d-4fcc-a0a4-72179491d19c-image.png

As said before, I already have updating the GUI manuals on my desk, it will be one of the next things I do, as they are factually non-existent for Python and a bit outdated and assumptious regarding what users will easily understand for C++. But I would still recommend Python users having a look at the C++ docs, as the information provided there is directly transferable to Python.

As lined out in other threads, greping the Cinema 4D resource folder, e.g., C:\Program Files\Maxon\2023.1.3\resource\ is a good way to gather knowledge on your own right now. I like using Agent Ransack for doing things like this, but any grep-like tool, including these built into text editors, will do:

afde649f-398b-439c-b596-12ffc6b12967-image.png

Cheers,
Ferdinand

MAXON SDK Specialist
developers.maxon.net

Hey @ferdinand ,Thanks for your detailed explains forever.

Now it is clear to read the res files , and you show me the Agent Ransack last year, I use this software for learn something , and the C++ document is also good for the res file, but some of the data is distribute anywhere , like BITMAPMUTTON , I remember that I search for no fading option for a long time, but in python document it has a user friendly position in sdk.

Maybe a document improvement or more Github examples are both welcomed( seems lots of works😲 )

Thanks for your patience!

Cheers~
DunHou