UNSOLVED Detect Hirarchy Change in a tag

ok, well. I guess i have to find another way.

is there a way for a tag plugin to notice, that the object it is on has changed within the hierarchy? like the MSG_CHANGE? That could help me 🙂

Hi @cgweasel I forked your topic, in the future please create a new topic each time you have a question with a topic that is unrelated to the current discussion.

With that said there is no 100% reliable way to do it, the best way would be to store the neighbor hierarchy (GetUp,GetPred,GetNext) and then add an execution step as Initial so within the Execute method you can check for these objects.

Find bellow and example adapted from py-look-at-camera example. The important methods to check are:

  • __init__ to initialize the cache value
  • AddToExecution to add the Initial state execution
  • isHierarchyChanged to check if the cache changed
  • updateCachedHierarchy to update the cache
  • Execute with priority == c4d.EXECUTIONPRIORITY_INITIAL that will check the cache, and print a message when the hierarchy changed.
import os
import c4d

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


class LookAtCamera(c4d.plugins.TagData):
    """Look at Camera"""

    def __init__(self):
        self._up = None
        self._pred = None
        self._next = None

    def Init(self, node):
        """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, bool, c4d.PYLOOKATCAMERA_PITCH)
        node[c4d.PYLOOKATCAMERA_PITCH] = True

        pd = c4d.PriorityData()
        if pd is None:
            raise MemoryError("Failed to create a priority data.")

        pd.SetPriorityValue(c4d.PRIORITYVALUE_CAMERADEPENDENT, True)
        node[c4d.EXPRESSION_PRIORITY] = pd
        return True

    def AddToExecution(self, tag, prioList):
        """Called By Cinema 4D to determine when in the Execution Pipeline the Execute method of this tag should be called.

        Args:
            tag (c4d.BaseTag): The instance of the TagData.
            prioList (c4d.plugins.PriorityList): The priority list to add your tag’s execution points to.

        Returns:
            True if the hierarchy changed, otherwise False.
        """

        # Retrieve the user defined priority
        storedPriority = tag[c4d.EXPRESSION_PRIORITY]
        storedPriorityValue = storedPriority.GetPriorityValue(c4d.PRIORITYVALUE_MODE) + \
                              storedPriority.GetPriorityValue(c4d.PRIORITYVALUE_PRIORITY)

        # Add an execution during the Initial phase, this is when we will check for hierarchy change
        prioList.Add(tag, c4d.EXECUTIONPRIORITY_INITIAL, 0)

        # Add the user defined execution phase, therefor the Execute method will be called 2 times.
        prioList.Add(tag, storedPriorityValue, 0)
        return True

    def isHierarchyChanged(self, obj):
        """Check if any of the cached neighbor hierarchy is different from the actual one.

        Args:
            obj (c4d.BaseObject): The host object to compare the hierarchy from.
            
        Returns:
            True if the hierarchy changed, otherwise False.
        """
        up = obj.GetUp()
        if up != self._up:
            return True

        pred = obj.GetPred()
        if pred != self._pred:
            return True

        next = obj.GetNext()
        if next != self._next:
            return True

        return False

    def updateCachedHierarchy(self, obj):
        """Update the cached neighbor hierarchy.

        Args:
            obj (c4d.BaseObject): The host object to retrieve the hierarchy from.
        """
        self._up = obj.GetUp()
        self._pred = obj.GetPred()
        self._next = obj.GetNext()

    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.
        """
        if priority == c4d.EXECUTIONPRIORITY_INITIAL:
            if self.isHierarchyChanged(op):
                self.updateCachedHierarchy(op)
                print("Hierarchy Changed")

                # We don't want to execute the logic of our tag during this phase except if the user asked for it
                storedPriority = tag[c4d.EXPRESSION_PRIORITY]
                storedPriorityValue = storedPriority.GetPriorityValue(c4d.PRIORITYVALUE_MODE) + \
                                      storedPriority.GetPriorityValue(c4d.PRIORITYVALUE_PRIORITY)
                if storedPriorityValue != c4d.EXECUTIONPRIORITY_INITIAL:
                    return c4d.EXECUTIONRESULT_OK

        # Retrieves the current active base draw
        bd = doc.GetRenderBaseDraw()
        if bd is None:
            return c4d.EXECUTIONRESULT_OK

        # Retrieves the active camera
        cp = bd.GetSceneCamera(doc) if bd.GetSceneCamera(doc) is not None else bd.GetEditorCamera()
        if cp is None:
            return c4d.EXECUTIONRESULT_OK

        # Calculates the position to target
        local = cp.GetMg().off * (~(op.GetUpMg() * op.GetFrozenMln())) - op.GetRelPos()

        # Calculates the rotation to target
        hpb = c4d.utils.VectorToHPB(local)

        if not tag[c4d.PYLOOKATCAMERA_PITCH]:
            hpb.y = op.GetRelRot().y
        hpb.z = op.GetRelRot().z

        # Defines the rotation
        op.SetRelRot(hpb)
        
        return c4d.EXECUTIONRESULT_OK


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

    # 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="Py - LookAtCamera",
                                  info=c4d.TAG_EXPRESSION | c4d.TAG_VISIBLE,
                                  g=LookAtCamera,
                                  description="Tpylookatcamera",
                                  icon=bmp)

Side note, we will be away the 26th and 27th December, so don't be surprise by the delay if you have follow-up questions, for more information see No support on 26/12 and 27/12.

Cheers,
Maxime.