Generator object's children visibility
-
Hello,
I would like to check if an object is hidden when attached to a generator object.
Example:
- Array
-- Cube
I tried using:
- CheckEditorVisibility(),
- CheckDisplayFilter() with GENERATOR flag,
- GetEditorMode() != MODE_OFF,
- GetDeformMode(),
but without any success.
I loop through a selection (in this case a parent(Array) and a child (Cube))
and, as I mentioned before, I want to check if an object is hidden by a generator object. I would appreciate it if you could tell me if there is a way to check it. - Array
-
Hello @konradsawicki,
Welcome to the Plugin Café forum and the Cinema 4D development community, it is great to have you with us!
Getting Started
Before creating your next postings, we would recommend making yourself accustomed with our Forum and Support Guidelines, as they line out details about the Maxon SDK Group support procedures. Of special importance are:
- Support Procedures: Scope of Support: Lines out the things we will do and what we will not do.
- Support Procedures: Confidential Data: Most questions should be accompanied by code but code cannot always be shared publicly. This section explains how to share code confidentially with Maxon.
- Forum Structure and Features: Lines out how the forum works.
- Structure of a Question: Lines out how to ask a good technical question. It is not mandatory to follow this exactly, but you should follow the idea of keeping things short and mentioning your primary question in a clear manner.
About your First Question
Your question is slightly ambiguous, let me dissect two things first:
- I assume with visible/hidden you are referring to the render-visibility flags of an object as reflected by the green/gray/red dots in the Object Manager.
- One could interpret a lot into the conjunction 'when' you did choose in your sentence. One could understand it as a hierarchical condition (check only objects that are parented to my generator) or as a temporal condition (check objects when they are parented to my generator or when they change).
And now let me answer these two points:
- The editor and render visibility of an object are determined by the parameters with the symbols
ID_BASEOBJECT_VISIBILITY_EDITOR
andID_BASEOBJECT_VISIBILITY_RENDER
. The visibility of an object in the object manger is determined by the NBITNBIT_OHIDE
. - Only checking the children is easy, the causal/temporal restriction is not, because a parent node P is not always informed when one of its children or grandchildren has changed. There are methods like BaseList2D.AddEventNotification which can help here, but the method is private for a reason and stuff like this can get very complicated. Normally you should not build relations between data like this.
It sounds a bit like you are trying to modify the scene your generator is contained in from one of its methods. Note that the threading restrictions do apply here and ignoring them can lead to crashes. Cheers,
FerdinandResult
Before:
After:
'Cube': node[c4d.ID_BASEOBJECT_VISIBILITY_EDITOR] = 0 'Cube': node[c4d.ID_BASEOBJECT_VISIBILITY_RENDER] = 2 'Cube.1': node[c4d.ID_BASEOBJECT_VISIBILITY_EDITOR] = 2 'Cube.1': node[c4d.ID_BASEOBJECT_VISIBILITY_RENDER] = 0 'Cube.2': node[c4d.ID_BASEOBJECT_VISIBILITY_EDITOR] = 2 'Cube.2': node[c4d.ID_BASEOBJECT_VISIBILITY_RENDER] = 2 'Cube.3': node[c4d.ID_BASEOBJECT_VISIBILITY_EDITOR] = 1 'Cube.3': node[c4d.ID_BASEOBJECT_VISIBILITY_RENDER] = 2 'Cube.4': node[c4d.ID_BASEOBJECT_VISIBILITY_EDITOR] = 2 'Cube.4': node[c4d.ID_BASEOBJECT_VISIBILITY_RENDER] = 2 >>>
Code
"""Demonstrates different concepts of visibility on the first object in a scene and its descendants. WARNING: This script will 'brick' a scene by permanently hiding the first object in it from the Object Manger. Run the script again on the scene to 'unbrick' it. """ import c4d import typing doc: c4d.documents.BaseDocument # The active document. def WalkTree(node: c4d.GeListNode) -> typing.Iterator[c4d.GeListNode]: """Yields all descendants of #node in depth first manner. Includes #node itself in the output. """ if not isinstance(node, c4d.GeListNode): return yield node for child in node.GetChildren(): for descendant in WalkTree(child): yield descendant def main() -> None: """ """ # Get the op: c4d.BaseObject = doc.GetFirstObject() if not op: return # Print out the editor and render visibility of each node below #op, including #op itself. for node in WalkTree(op): print(f"'{node.GetName()}': {node[c4d.ID_BASEOBJECT_VISIBILITY_EDITOR] = }") print(f"'{node.GetName()}': {node[c4d.ID_BASEOBJECT_VISIBILITY_RENDER] = }") # Toggle the NBIT that controls the visibility of the first object in the Object Manager itself. op.ChangeNBit(c4d.NBIT_OHIDE, c4d.NBITCONTROL_TOGGLE) c4d.EventAdd() if __name__ == "__main__": main()
-
Hi @ferdinand,
Thank you for your detailed answer. I am sorry for the ambiguity. I'll try to explain my problem more thoroughly.
By a generator object I meant, for example, an 'Array' or 'Symmetry':
My goal is to calculate the extents of an objects' selection.
Now let's assume such situation:
You can see that that the selection consists of 'Array' (parent) and 'Cube' (child). When I attached 'Cube' to 'Array', C4D automatically hid 'Cube' in the scene.
Here you can see that only 'Cube' is selected, but only its local coordinate system is visible (it was hidden by C4D).
So the problem is that functions such:
- CheckEditorVisibility(),
- CheckDisplayFilter() with GENERATOR flag,
- GetEditorMode(),
- GetDeformMode(),
- GetNBit with OHIDE flag (checked after @ferdinand answer)
don't provide useful info whether 'Cube' was hidden by C4D in the scene (due to 'Cube' being a child of 'Array'), thus I cannot exclude those hidden objects from extents.
I am sorry for all the ambiguities, please let me know if I can explain it more clearly.
-
Hey @konradsawicki,
There is no need to be sorry, it is in the nature of asking technical questions that ambiguities come up. But at the same time, I also must point them out relatively unceremoniously as we otherwise might risk talking about different things.
I understand now better what you are trying to do, and it also explains why you were trying all these methods which did not make too much sense for me. There are multiple ways how geometry can be hidden from the users eyes other than the user facing gray/green/red toggle dots. Most of them are NBIT flags, e.g.,
NBIT_EHIDE
orNBIT_LOD_HIDE
, see our documentation for details. Another way how objects can be hidden, is them being BaseObject.Touch'ed. This unfortunately named method marks objects as input objects for a generator. It cleans their caches and sets the BIT flagBIT_CONTROLOBJECT
.Combine all of these, and you have a relatively reliable predictor for if something is hidden in the viewport or not.
Cheers,
FerdinandResult
For this input and running the script first on "Extrude" and then on "Array":
We get this output. Note that generators do not use any of the NBIT flags to hide their inputs. But other things might. It depends a bit on what you want to implement, in what you would have to respect in your code. Do you just want to cover "generator-induced-invisibility", or do also want to cover other cases as for example tools (temporarily) hiding things.
'Extrude': node.GetNBit(c4d.NBIT_EHIDE) = 0 node.GetNBit(c4d.NBIT_HIDEEXCEPTVIEWSELECT) = 0 node.GetNBit(c4d.NBIT_LOD_HIDE) = 0 node.GetNBit(c4d.NBIT_SUBOBJECT_AM) = 0 node.GetBit(c4d.BIT_CONTROLOBJECT) = True 'Rectangle': node.GetNBit(c4d.NBIT_EHIDE) = 0 node.GetNBit(c4d.NBIT_HIDEEXCEPTVIEWSELECT) = 0 node.GetNBit(c4d.NBIT_LOD_HIDE) = 0 node.GetNBit(c4d.NBIT_SUBOBJECT_AM) = 0 node.GetBit(c4d.BIT_CONTROLOBJECT) = True 'Array': node.GetNBit(c4d.NBIT_EHIDE) = 0 node.GetNBit(c4d.NBIT_HIDEEXCEPTVIEWSELECT) = 0 node.GetNBit(c4d.NBIT_LOD_HIDE) = 0 node.GetNBit(c4d.NBIT_SUBOBJECT_AM) = 0 node.GetBit(c4d.BIT_CONTROLOBJECT) = True 'Cube': node.GetNBit(c4d.NBIT_EHIDE) = 0 node.GetNBit(c4d.NBIT_HIDEEXCEPTVIEWSELECT) = 0 node.GetNBit(c4d.NBIT_LOD_HIDE) = 0 node.GetNBit(c4d.NBIT_SUBOBJECT_AM) = 0 node.GetBit(c4d.BIT_CONTROLOBJECT) = True
Code
"""Demonsttrates diffrents concepts of viewport visibilty on the selected object in a scene and its descendants. """ import c4d import typing doc: c4d.documents.BaseDocument # The active document. op: c4d.BaseObject | None # The currently selected object, can be #None. def WalkTree(node: c4d.GeListNode) -> typing.Iterator[c4d.GeListNode]: """Yields all descendants of #node in depth first manner. Includes #node itself in the output. """ if not isinstance(node, c4d.GeListNode): return yield node for child in node.GetChildren(): for descendant in WalkTree(child): yield descendant def main() -> None: """ """ if not op: return for node in WalkTree(op): print(f"'{node.GetName()}':") print(f"\t{node.GetNBit(c4d.NBIT_EHIDE) = }") print(f"\t{node.GetNBit(c4d.NBIT_HIDEEXCEPTVIEWSELECT) = }") print(f"\t{node.GetNBit(c4d.NBIT_LOD_HIDE) = }") print(f"\t{node.GetNBit(c4d.NBIT_SUBOBJECT_AM) = }") print(f"\t{node.GetBit(c4d.BIT_CONTROLOBJECT) = }") if __name__ == "__main__": main()
-
Hi @ferdinand,
Thank you for the detailed answer once again. It cleared a lot of things for me.
From what I understood, I should use BIT_CONTROLBOJECT to detect if the object is 'Touched' by a generator object (in my sample case: child 'Cube' hidden by parent 'Array').
I tried doing so, but it looks like BIT_CONTROLOBJECT of 'Cube' is true regardless if it is attached to 'Array'.
Please see the screenshots below.
For this input:
I get the following console output:
---Cube--- OHIDE: false EHIDE: false HIDEEXCEPTVIEWSELECT: false LOD_HIDE: false SUBOBJECT_AM: false CONTROLOBJECT: true
You can see that the BIT_CONTROLOBJECT is true when 'Cube' is not attached to anything.
my pseudo code:
// Loop through selection. for (Int32 i = 0; i < selection->GetCount(); ++i) { auto obj = (BaseObject*)selection->GetIndex(i); std::unordered_set<BaseObject*> objs; objs.insert(obj); // Collect child objects recursively. CollectChildren(obj, objs); for (auto o : objs) { DiagnosticOutput("---@---", o->GetName()); DiagnosticOutput("OHIDE: @", o->GetNBit(NBIT::OHIDE)); DiagnosticOutput("EHIDE: @", o->GetNBit(NBIT::EHIDE)); DiagnosticOutput("HIDEEXCEPTVIEWSELECT: @", o->GetNBit(NBIT::HIDEEXCEPTVIEWSELECT)); DiagnosticOutput("LOD_HIDE: @", o->GetNBit(NBIT::LOD_HIDE)); DiagnosticOutput("SUBOBJECT_AM: @", o->GetNBit(NBIT::SUBOBJECT_AM)); DiagnosticOutput("CONTROLOBJECT: @", o->GetBit(BIT_CONTROLOBJECT)); // If o is visible, then calculate extents. } }
I also recreated analogous scenario that you provided ('Array' as a parent and 'Cube' as a child) and got the same console output as you did.
@ferdinand said in Generator object's children visibility:
'Array':
node.GetNBit(c4d.NBIT_EHIDE) = 0
node.GetNBit(c4d.NBIT_HIDEEXCEPTVIEWSELECT) = 0
node.GetNBit(c4d.NBIT_LOD_HIDE) = 0
node.GetNBit(c4d.NBIT_SUBOBJECT_AM) = 0
node.GetBit(c4d.BIT_CONTROLOBJECT) = True
'Cube':
node.GetNBit(c4d.NBIT_EHIDE) = 0
node.GetNBit(c4d.NBIT_HIDEEXCEPTVIEWSELECT) = 0
node.GetNBit(c4d.NBIT_LOD_HIDE) = 0
node.GetNBit(c4d.NBIT_SUBOBJECT_AM) = 0
node.GetBit(c4d.BIT_CONTROLOBJECT) = TrueYou can see that BIT_CONTROLOBJECT is true when 'Cube' is attached to 'Array', so I cannot differentiate if the object is invisible due to generator object, because before attaching it to a generator object, BIT_CONTROLOBJECT was already true.
Please let me know, if I am misunderstanding the things that you wrote.
-
-
Hello @konradsawicki,
yes, you are right
BIT_CONTROLOBJECT
is a poor indicator. There are more flags which are used for driving the visibility of objects, but they won't help you either [1].Internally, this has been implemented quite unevenly, which in the end makes it impossible for users of the public API to properly react to this.
As said before,
BIT_CONTROLOBJECT
is only the front facing part of some beingBaseObject::Touch
'ed. The result of this is then that the object has this bit flag set and its caches being flushed. So, when you take your array/cube example, you can see that the Cube object will returnnullptr
when itsBaseObject::GetCache()
is being called, while the same cube will return its polygonal cache when outisde of the array object (check this in case you are a bit lost, as I talked there by chance exactly about this array/cube case). And when the cache of a generator object is empty, then there is also nothing to draw.The problem is, that this all does not hold true for splines as input objects, when you for example put a circle spline below an Extrude object, it will still return something other than
nullptr
for::GetCache()
. This thread (which I had totally forgotten) sheds some light indirectly on the subject. For splines to be hidden in this manner, one must callGetHierarchyClone
orGetAndCheckHierarchyClone
on these input objects (other than for a polygon object generator which can just touch its inputs). As I wrote there, this method also calls::Touch
in the end, but for splines this is nor permanent and there is also non-public data involved. Which is then used by the super custom drawing routines (not) drawing such splines.What you could do is, check with
GeListNode::GetInfo()
if an object has the flagOBJECT_ISSPLINE
and then check if its parent has the flagOBJECT_INPUT
, i.e., something like this:const BaseObject* const op; if (!op) return NOTOK; const BaseObject* const parent = op->GetUp(); if (!parent) return NOTOK; if ((op->GetInfo() & OBJECT_ISSPLINE) & (op->GetInfo() & OBJECT_INPUT)) // Do something under the assumption that #op is a spline input for a generator that is hidden.
But as explained above, this is not the actual mechanism which hides things, and you will run into many false positives and false negatives with this, because how custom all the spline input generators are with their inputs.
Long story short, checking if an object O is a currently hidden input object for a generator object is unfortunately not possible to do in the public API. For an upcoming API we have added a data dependency system which will make it easier to determine this, but for now you are out of luck.
Maybe you and I can find an alternative approach? What you want to do is a bit unusual and to me it is not clear why you are trying to do this. Could you elaborate why this is useful and how you want to use it?
Cheers,
Ferdinand[1] I used this to explore how the flags behave on generator object input objects and their caches. Simply select an object an run this script, it will dump a little tree to the console.
"""Prints the hierarchy and cache hierachy of the currently selected object and dumps visibility information for each node in that hierarchy. """ import c4d # The primary selected object in the scene, can be `None`. op: c4d.BaseObject | None def PrintSceneTree(node: c4d.BaseObject, indent: int = 0) -> None: """Prints the hierarchy and cache hierachy of #node. """ if not isinstance(node, c4d.BaseObject): return nodeName: str = f"{node.GetName()} ({node.GetRealType()})" tab: str = " " * indent tabtab: str = " " * (indent + 1) nodeString: str = f"{tab}{nodeName} (" space: str = " " * len(nodeString) bits: list[tuple(str, int)] = [ ("BIT_CONTROLOBJECT", c4d.BIT_CONTROLOBJECT), ("BIT_IGNOREDRAW", c4d.BIT_IGNOREDRAW), ("BIT_TEMPDRAW_VISIBLE_CACHECHILD", c4d.BIT_TEMPDRAW_VISIBLE_CACHECHILD), ("BIT_TEMPDRAW_VISIBLE_DEFCACHECHILD", c4d.BIT_TEMPDRAW_VISIBLE_DEFCACHECHILD), ("BIT_TEMPDRAW_VISIBLE_CHILD", c4d.BIT_TEMPDRAW_VISIBLE_CHILD) ] nbits: list[tuple(str, int)] = [ ("NBIT_EHIDE", c4d.NBIT_EHIDE), ("NBIT_HIDEEXCEPTVIEWSELECT", c4d.NBIT_HIDEEXCEPTVIEWSELECT), ] for i, (symbol, bitValue) in enumerate(bits): nodeString += f"{space if i != 0 else ''}{symbol}: {node.GetBit(bitValue)}\n" for symbol, bitValue in nbits: nodeString += f"{space}{symbol}: {node.GetNBit(bitValue)}\n" nodeString = nodeString[:-1] + ")" print(nodeString) for funcName, cache in ((f.__name__, f()) for f in [node.GetCache, node.GetDeformCache] if f()): print(f"{tabtab}{nodeName} returns for {funcName}():") PrintSceneTree(cache, indent + 2) if not node.GetChildren(): return print(f"{tabtab}{nodeName} returns for GetChildren():") for child in node.GetChildren(): PrintSceneTree(child, indent + 2) if __name__ == "__main__": PrintSceneTree(op) print ("-" * 100)
-
Hi @ferdinand,
What I am trying to do, is to calculate the bounding box's extents of selected objects by the user. Before, I was using BaseObject::GetCache() to calculate the extents of the selection and it worked, because as you said:
So, when you take your array/cube example, you can see that the Cube object will return nullptr when its BaseObject::GetCache() is being called, while the same cube will return its polygonal cache when outisde of the array object.
So the hidden object wasn't included because its cache is nullptr.
But the problem is that, later I need to call on the selected objects BaseObject::GetMg() and then BaseObject::SetMg(). Documentation says: "Only valid if the object is attached to a document. Virtual objects in caches and deform caches are not attached to a document, so this cannot be used for those objects."
Additionally, I need to include objects which don't have Opolygon objects in their cache (e.g. empty generator object, light, camera, rectangle - not sure if they even have a cache).So after examining possible solutions, I was left with the one that I mentioned before:
@konradsawicki said in Generator object's children visibility:
// Loop through selection.
for (Int32 i = 0; i < selection->GetCount(); ++i)
{
auto obj = (BaseObject*)selection->GetIndex(i);std::unordered_set<BaseObject*> objs; objs.insert(obj); // Collect child objects recursively. CollectChildren(obj, objs); for (auto o : objs) { DiagnosticOutput("---@---", o->GetName()); DiagnosticOutput("OHIDE: @", o->GetNBit(NBIT::OHIDE)); DiagnosticOutput("EHIDE: @", o->GetNBit(NBIT::EHIDE)); DiagnosticOutput("HIDEEXCEPTVIEWSELECT: @", o->GetNBit(NBIT::HIDEEXCEPTVIEWSELECT)); DiagnosticOutput("LOD_HIDE: @", o->GetNBit(NBIT::LOD_HIDE)); DiagnosticOutput("SUBOBJECT_AM: @", o->GetNBit(NBIT::SUBOBJECT_AM)); DiagnosticOutput("CONTROLOBJECT: @", o->GetBit(BIT_CONTROLOBJECT)); // If o is visible, then calculate extents. }
}
-
Hi @konradsawicki,
first of all, there already exist methods for retrieving the bounding box of an object and and they should respect empty caches correctly, see
BaseObject::GetRad
,::GetMp
, and::GetDimension
for details. Check also this therad for recursive bounding box computation.Secondly, you seem to have misconceptions regarding caches. I would recommend having a look at either the
ActiveObject
plugin from the C++ SDK or this thread I already linked to above. A few satements:- The generator cache of an
BaseObject
is the output theObjectData::GetVirtualObjects
of its corresponding plugin hook. It is basically a hidden hierarchy of objects. - Caches of objects are built when an object is part of a document and
BaseDocument::ExecutePasses
is called on that document (with at least the generator/cache pass enabled). - Freshly instantiated objects or touched objects will have no caches, even when being part of a document.
- You can set as many parameters as you want on an object with no cache, e.g., its global position. It will be correctly reflected, and for some parameters, e.g., the side length of a cube, it will cause caches to be built once you add an event or manually execute the passes yourself.
But overall, I currently do not see the necessity to go down this cache and hidden object rabbit hole in the first place for computing the bounding box of things. But I might be overlooking something here.
Cheers,
Ferdinand - The generator cache of an
-
Hello @konradsawicki ,
without further questions or postings, we will consider this topic as solved by Friday, the 11th of august 2023 and flag it accordingly.
Thank you for your understanding,
Maxon SDK Group