SOLVED Touch input objects withot resetting its cache

Hi,

I'd like to know if there's a chance to hide objects the same way it's done in GetAndCheckHierarchyClone() method.
Generally GACHC is hiding children geometry without destroying their cache, and it don't reset DIRTYFLAGS_CACHE.
I'd like to do the similar thing to the dependencies, which aren't children.

Currently I know only Touch() method, but it reset caches and enforce all generators to be recalculated when touching the chain.
I seen many similar topics were related to the Touch() method, but there were no solution.

Also are there any flags to know if object is made hidden by GetAndCheckHierarchyClone?
At least I try to skip Touching for these objects.
GetCache() is returning some value, testing BIT_CONTROLOBJECT is not relevant as it set for both Generator and Input objects.

Hello @baca,

Thank you for reaching out to us. I am struggling a bit with understanding what you want to achieve here. So, you have a setup like this:

P
+--A
|   +--A1
+---B
    +--B1

And you want A1 and B1 to be hidden in addition to A and B? Are you using OBJECT_INPUT as a flag to register your plugin? But I think the flag only works for direct children, if you want to also hide grandchildren or any type of descendants, you will have to do that manually. You can do this with c4d.GeListNode.ChangeNbit() and the field c4d.NBIT_EHIDE. Maybe you could also just manually flag objects as BIT_CONTROLOBJECT, and by that mute their cache output, but that would be more of a hack and I would have to try myself if this would work (I doubt it will).

Cheers,
Ferdinand

@ferdinand thanks for suggestion, I'll try it as well.

Here's setup:

SCENE_ROOT
+--SOME_SOURCE (whole tree has to be hidden)
|  +--SOURCE_CHILD_1
|  |  +--SOURCE_GRANDCHILD_1_1
|  |  +--...
|  |  +--SOURCE_GRANDCHILD_1_N
|  +--SOURCE_CHILD_N
|  |  +--SOURCE_GRANDCHILD_N_1
|  |  +--...
|  |  +--SOURCE_GRANDCHILD_N_N
+--...
+--MY_GENERATOR (has object link pointing to SOME_SOURCE)

The idea is to create similar functionality as Connect object has - object link as a mesh source, and this source (whole tree) has to be hidden.
Touch is resetting object cache, what I want to avoid.
While GetAndCheckHierarchyClone is able to hide whole subtree, keeping it's cache.

Hello @baca,

Touch is resetting object cache, what I want to avoid. While GetAndCheckHierarchyClone is able to hide whole subtree, keeping it's cache.

I would say the last sentence is not true. GetAndCheckHierarchyClone is just a wrapper around GetHierarchyClone, which after a scenic tour through our backend, will call Touch in the case the argument dirty is True. In these cases, GetHierarchyClone will build the caches of objects, clone them and immediately after that call Touch. Touch does three things, it sets the flag BIT_CONTROLOBJECT, it flushes the caches of the object, and it resets the dirty counter. So, in the case of the argument dirty being True, the cache is being built and directly after that flushed. Setting the flag BIT_CONTROLOBJECT plus the flushing of cashes is IMHO what realizes an object being 'hidden'. No caches will prevent the object from being drawn, and BIT_CONTROLOBJECT will prevent the caches from being rebuilt automatically. What happens in the case when dirty is False, i.e., the default case, is a bit harder to assess, as the code to cover then becomes much larger, and I do not have the time to cover it all. My assumption would be, however, that caches are also flushed, as this is the only way I can see this hiding to work.

The underlying problem here is that the classic API scene graph has no true dependency graph for its scene elements, the dirty system is what fulfils that function, but it does not tell you what is related to what. Which has led to the problem that things that might seem trivial are not, because everything has its little algorithm on top of algorithms to determine its dependencies.

Long story short:

  1. I would question the necessity of your problem/requirements in the first place. I know that I do recommend this often, but the best design is the simplest one, not the one that takes the hardest route. Do you REALLY need this whole hierarchy to be hidden? Must you REALLY avoid touching the input objects? Is there a simpler pattern for what you want to achieve?
  2. I spent some time with BIT_CONTROLOBJECT and poking around in the backend of Cinema 4D. I tried writing my own GetAndCheckHierarchyClone so to speak, which goes deeper than just one level, and I also tried calling GetAndCheckHierarchyClone recursively on all descendants of an input. While you can get some success here with stuff being hidden, you also quickly run into the wall that is dependency handling of the classic API and when a scene graph contains objects which are flagged as BIT_CONTROLOBJECT in a pattern which seems not to be intended, stuff also gets buggy and glitchy, likely because all these assumptions of custom dependency evaluations then do fail.

I would still recommend NBIT_EHIDE as a solution to your problem and also have provided an example for that at the end. The problem here is that while everything works fine and dandy while some object is an input object, it does not anymore when the users removes the object from that relation:

SceneRoot
+--Plugin
   +--A [hidden]
   |  +--B [hidden]
   +--C [hidden]

Plugin considers A, B, and C its input objects and has hidden them manually with NBIT_EHIDE, everything is working fine, yay! But wait, now the users moves B outside of the scope of what Plugin considers to be its inputs:

SceneRoot
+--Plugin
   +--A [hidden]
   +--C [hidden]
+--B [hidden]

B is still hidden, but it should not be anymore. There is also no event being published inside of Plugin which would signal B being moved out of Plugin. I have solved this with a tag, but this is also a very hacky solution.

I would assess if these requirements of yours are indeed irremovable requirements. If I would be implementing this, I would simply cut features to avoid having to deal with this specific scenario.

Cheers,
Ferdinand

The result:

The example 'implements' a loft object which accepts a hierarchy of spline objects as inputs (the hierarchy is flattened depth first and fed into a normal loft object). The hiding and unhiding of the input objects is realized NBIT_EHIDE and controller tags on each object (which are hidden themselves).

input_object.gif

The code:

import c4d

UNHIDER_TAG_CODE = """
'''Implements a tag which unhides its host object when it is not (indirectly) parented anymore to an
Oinputobject node.

It therefore realizes the counterpart to the hiding done in InputObjectData.GetInputs(). This is 
still a hack and I would not recommend doing it. If done more nicely, one should implement it as
its own TagData plugin and spend more time on testing/assuring that an object cannot "glitch out"
than I did here, as the result will be an unusable scene for the user.
'''
import c4d
Oinputobject = 1059626

def main() -> None:
    '''Unhides the object this tag is attached to if none of the ancestors of the object is an 
    Oinputobject node.
    '''
    def is_input_object(node: c4d.GeListNode) -> bool:
        '''Determines of any of the ancestors of #node is an Oinputobject node.
        '''
        while node:
            if node.CheckType(Oinputobject):
                return True
            node = node.GetUp()
        return False

    # Get the object the tag is attached to and unhide the object when none of its ancestors is
    # a node of type Oinputobject.
    host = op.GetMain()
    if not is_input_object(host):
        host.ChangeNBit(c4d.NBIT_EHIDE, c4d.NBITCONTROL_CLEAR)
"""


class InputObjectData(c4d.plugins.ObjectData):
    """Implements a generator object which hides all its descendant nodes from the viewport.
    """
    PLUGIN_ID = 1059626

    @classmethod
    def GetInputs(cls, node: c4d.BaseObject, hh: any) -> tuple[c4d.BaseObject]:
        """Returns a list of clones of all descendants of #node and hides the original nodes.
        """
        def iter_node(node: c4d.GeListNode) -> c4d.GeListNode:
            """Walks the sub-graph of a node depth-first.
            """
            if node is None:
                return

            while node:
                yield node
                for descendant in iter_node(node.GetDown()):
                    yield descendant

                node = node.GetNext()
                
        def callback(node: c4d.BaseObject) -> c4d.BaseObject:
            """Clones a node, hides the source node from the viewport, and attaches a tag which 
            ensures that the object unhides itself once it is not an input object anymore.
            """
            clone = node.GetClone(c4d.COPYFLAGS_NO_HIERARCHY)
            node.ChangeNBit(c4d.NBIT_EHIDE, c4d.NBITCONTROL_SET)

            # Exit when the node has already an "unhider" tag.
            tag = node.GetTag(c4d.Tpython)
            if isinstance(tag, c4d.BaseTag) and tag[c4d.TPYTHON_CODE] == UNHIDER_TAG_CODE:
                return clone

            # For more production ready code, I would recommend implementing the tag as a TagData 
            # plugin. This would also make identifying an already existing "unhider" tag robust 
            # via its plugin id.
            
            # Create the "unhider" tag, set the Python code and hide the tag from the users view.
            tag = node.MakeTag(c4d.Tpython)
            tag[c4d.TPYTHON_CODE] = UNHIDER_TAG_CODE
            tag.ChangeNBit(c4d.NBIT_OHIDE, c4d.NBITCONTROL_SET)

            return clone

        # Collapse all descendants of #node to a list of clones and hide each source node.
        return tuple(callback(n) for n in iter_node(node) if n is not node)


    def GetVirtualObjects(self, op, hh):
        """Implements a loft object which takes into account all its descendants for building the 
        loft and not only the direct children.

        This is not fully fleshed out and serves only as an example for the case of hiding inputs.
        """
        # Get and handle the input objects; or get out when there are not enough inputs.
        profiles = InputObjectData.GetInputs(op, hh)
        if len(profiles) < 2:
            return c4d.BaseList2D(c4d.Onull)

        # Create the loft object and parent the flattened inputs as direct children.
        loftNode = c4d.BaseList2D(c4d.Oloft)
        loftNode[c4d.CAPSANDBEVELS_CAP_ENABLE_START] = False
        loftNode[c4d.CAPSANDBEVELS_CAP_ENABLE_END] = False
        for item in profiles:
            item.InsertUnder(loftNode)

        return loftNode

if __name__ == "__main__":
    c4d.plugins.RegisterObjectPlugin(
        id = InputObjectData.PLUGIN_ID,
        str = "Input Object Host", 
        g = InputObjectData, 
        description = "oinputobject", 
        icon = None, 
        info = c4d.OBJECT_GENERATOR)

@ferdinand said in Touch input objects withot resetting its cache:

NBIT_EHIDE

Sorry Ferdinand,
I might be not a great explainer... But that not I actually need.
This bit only hides object in Editor, in Render it's still visible.

So Touch() is the only way to truly hide the object.
My only issue is that I'm trying to hide all nested objects with Touch().
And when I'm doing it I'm also touching the grandchildren, and this cause their parent to be rebuilt.
I'm looking for a way to skip objects which already touched by their parent generator.

To be clear, here's the use case:
+--Null whole tree has to be hidden
| +--Loft has BIT_CONTROLOBJECT, has OBJECT_INPUT — not hidden yet, has to be hidden
| | +--Circle has BIT_CONTROLOBJECT — already hidden by parent Loft, has to be skipped
| +--Loft has BIT_CONTROLOBJECT, has OBJECT_INPUT — not hidden yet, has to be hidden
| | +--Cube has BIT_CONTROLOBJECT — not hidden by parent Loft, has to be hidden
+--...
+--MY_GENERATOR has object link pointing to Null, it wants to hide this Null

If I'll try to Touch() Circle spline, then it's parent Loft would be continuously regenerating, so MY_GENERATOR would be also continuously regenerating, because source dirtiness is changed.

If I'll try to skip Touch() children of objects with OBJECT_INPUT set, then Cube under Loft would be visible - because it wasn't touched by its parent Loft as Cube is not a spline object.

So either I go into regeneration loop, either I'm not hiding objects.
But I want both performance and hide.

Hey @baca,

as I said, the control object structure you want is not possible. I tried and wrote the necessary code when I answered here initially, and Cinema 4D will really start to freak out. You cannot have nested hierarchies of muted caches. If you really want to do this, then you must use the approach proposed by me. If you want to also hide the objects in renderings, then you will have to also set that parameter.

If you poke around long enough, you might find a solution by muting the caches, but this is out of scope of support. As I said, I did already try when I wrote my first posting, maybe you have more luck. But as also stated in my first posting, you are poking here the sleeping bear called "classic API node dependencies".

Cheers,
Ferdinand