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:
- 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:
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).

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)