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
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
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:
- 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?
- 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:
| +--B [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:
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.
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).
UNHIDER_TAG_CODE = """
'''Implements a tag which unhides its host object when it is not (indirectly) parented anymore to an
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.
Oinputobject = 1059626
def main() -> None:
'''Unhides the object this tag is attached to if none of the ancestors of the object is an
def is_input_object(node: c4d.GeListNode) -> bool:
'''Determines of any of the ancestors of #node is an Oinputobject node.
node = node.GetUp()
# 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):
"""Implements a generator object which hides all its descendant nodes from the viewport.
PLUGIN_ID = 1059626
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:
for descendant in iter_node(node.GetDown()):
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)
# 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:
# 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
# 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:
# 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:
if __name__ == "__main__":
id = InputObjectData.PLUGIN_ID,
str = "Input Object Host",
g = InputObjectData,
description = "oinputobject",
icon = None,
info = c4d.OBJECT_GENERATOR)