Find objects within GLTF-file by name and attach them to the scene.
-
Hi!
For a python plugin I need to use some GLTF files as "asset library" files. For example, I have a gltf with hundreds of meshes in it. Ideally I would be able to scan the gltf for specific meshes by name and attach these to the scene without having to load the complete gltf in memory.
Coming mostly from Blender plugin development, this is my first C4D plugin, so please forgive me that I am not quite fluent in C4D python yet and may have some stupid assumptions.
As far as I can see I need to use c4d.documents.LoadDocument and made some quick tests. I didn't run into anything major as far as I can tell, but I am not sure if I will run into a dead end with this approach.
Here, the first quick tryout:
import c4d def findChildByName(mom, name, recursive = True): """Naive function to find a child object by name. Can produce only one child and is therefore not very sexy.""" if hasattr(mom, 'GetObjects'): for child in mom.GetObjects(): if child.GetName() == name: return child elif recursive: c = findChildByName(child, name) if c: return c if hasattr(mom, 'GetChildren'): for child in mom.GetChildren(): if child.GetName() == name: return child elif recursive: c = findChildByName(child, name) if c: return c return None filepath = "C:/path/to/file.glb" f = c4d.documents.LoadDocument(filepath, c4d.SCENEFILTER_OBJECTS) doc = c4d.documents.GetActiveDocument() eberhardt = findChildByName(f, "Eberhardt") if eberhardt != None: doc.InsertObject(eberhardt.GetClone(c4d.COPYFLAGS_0))
Of course the whole glb file is loaded into memory, but otherwise it works. How would I go about without loading the whole file into memory? Do I need to adjust the gltf importer settings similar to the obj importer example somehow? I saw the
c4d.SCENEFILTER_IDENTIFY_ONLY
flag... is this one leading to the road of glory?Is it even possible to load files only partially, or do I need to write my own gltf importer?
I'd be super happy if someone could point me in the right direction!
-
Hello @uogygiuol,
Welcome to the Maxon developers forum and its 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 procedures. You did not do anything wrong, we point all new users to these rules.
- Forum Overview: Provides a broad overview of the fundamental structure and rules of this forum, such as the purpose of the different sub-forums or the fact that we will ban users who engage in hate speech or harassment.
- Support Procedures: Provides a more in detail overview of how we provide technical support for APIs here. This topic will tell you how to ask good questions and limits of our technical support.
- Forum Features: Provides an overview of the technical features of this forum, such as Markdown markup or file uploads.
It is strongly recommended to read the first two topics carefully, especially the section Support Procedures: Asking Questions.
About your First Question
You can search for objects with c4d.documents.BaseDocument.SearchObject.
doc: c4d.documents.BaseDocument = c4d.documents.GetActiveDocument() target: c4d.BaseObject | None = doc.SearchObject("Eberhardt") if target is None: raise RuntimeError("Could not find target.")
Apart from the fact that names are a poor way to identify objects, you can also not find multiple objects with the same name in this manner. With release 2025 we added abstracted scene iteration.
doc: c4d.documents.BaseDocument = c4d.documents.GetActiveDocument() op: c4d.BaseObject = doc.GetFirstObject() # Walk the obejct tree and look for objects with a given name. for node in mxutils.IterateTree(op, True, True, True): if node.GetName() == "Eberhardt": pass # There is also abstract scene iteration now, here we look for any node named "Eberhardt" in #doc. for node in mxutils.RecurseGraph(doc, yieldBranches=True, yieldHierarchy=True): if node.GetName() == "Eberhardt": pass # Or for objects and tags named "Eberhardt". Check the docs on #RecurseGraph, there are a lot of # options for finetuning the scene traversal. for node in mxutils.RecurseGraph(doc, yieldBranches=True, yieldHierarchy=True, nodeFilter=[c4d.Obase, c4d.Tbase]): if node.GetName() == "Eberhardt": pass
Last but not least, what you are doing in your example code, recursion (at least unbound recursion) should be avoided, as this can quite easily lead to stack overflows. See this thread for how to do scene traversal manually.
Cheers,
Ferdinand -
Dear Ferdinand,
thank you so much for your quick and helpful reply! Amazing how fast, super impressive and it really motivated me to keep going. I needed a bit more time to reply and verify I am not writing stupid things. I can't promise that there is nothing stupid left, but I did my best
:)
Before creating your next postings, we would recommend making yourself accustomed with our forum and support procedures. You did not do anything wrong, we point all new users to these rules.
This makes a lot of sense. I checked out the links you posted and found already the one or other thing I want to do better.
To clarify, this is my main question:
How can I search in a GLTF-file for specific objects, and load them selectively in the scene - without loading the whole file in memory?I have a gltf-file with hundreds of shapes, possibly gigabytes. They are structured like a flat database. Imagine it like a 3D font, with many, many individual letters. I don't want the whole character set for all languages and variations to be loaded, just because the user wants to write "hello world". It is by the way, exactly what this is going to be used for and will become relevant when we think about identifiers.
Maybe it is best to give an example of what I want to do:
- parse the nodes-index of the gltf-file
- find an object by name
- load this particular object in memory
- instantiate object in the scene
1) parse the nodes-index of the gltf-file
Consider this gltf-file, which holds various objects:
{ "meshes": [ ... ], ..., "nodes": [ { "mesh": 0, "name": "Eberhardt" }, { "mesh": 1, "name": "Edeltraudt" }, { "mesh": 2, "name": "Ebertraudt" }, ..., { "mesh": 65535, "name": "Eberhardt42" }, ] }
So here I want to parse "nodes", check the names and only load the meshes of the nodes that start with "Eberhardt".
2) find an object by name
I agree that names are usually not a good identifier. However, in this case it does make sense to me, because the individual nodes are characters for a 3D font. The first character of the string refers to the character the shape represents.
"A_1"
,"A_2"
,"Γ_42"
,"δΊΊ_1"
or"π_9"
can all be represented in json/gltf strings, which makes the name easy and straightforward to use. It doesn't matter if a character has the name"A_1.001"
or"A_1"
About your First Question
You can search for objects with c4d.documents.BaseDocument.SearchObject.
Maybe this is due to my limited understanding, but I couldn't manage yet to use it without loading the whole document in memory. Also, as described above I need to allow partial matches and it looks as if
SearchObject
only searches for exact matches. I realize that I didn't mention this limitation in my initial question...Apart from the fact that names are a poor way to identify objects, you can also not find multiple objects with the same name in this manner. With release 2025 we added abstracted scene iteration.
The abstracted scene iteration looks great! However, I need to stay compatible with
2024.x.x
.Last but not least, what you are doing in your example code, recursion (at least unbound recursion) should be avoided, as this can quite easily lead to stack overflows. See this thread for how to do scene traversal manually.
Noted and agreed. Thank you for your attentiveness! Also, a wonderfully helpful post in that thread. Isn't this a perfect
2024.x.x
replacement for what I'd usemxutils.RecurseGraph
in2025.x.x
for?such as:
for n in GetAllNodesIteratively(node): if n.GetName().startswith("Eberhardt"): pass
I guess the only real downside is that
mxutils.RecurseGraph
would be implemented in c++ and way more performant on large node graphs.3) load this particular object in memory
Ideally we have identified the matching nodes, without loading the whole gltf into memory. But we do need to load the matches selectively. I do not know yet how to do that, or if this is even possible with out-of-the-box functions. If we can manage to do
1)
and2)
, then this should be possible somehow. In any case I'd love to use existing functions and avoid custom acrobatics wherever possible.4) instantiate object in the scene
I realize also this may be a follow up question, but I guess it will lead me to c4d.InstanceObject and should be trivial in comparison.
I hope that my question makes sense, and thank you so much already for your help.
-
Hey @uogygiuol,
Sorry, I did overlook the 'without having to load the complete gltf in memory' part of your initial question. There is no mechanism in Cinema 4D with which you could load a file partially, i.e., some visitor/delegate pattern which asks you for each element of a to be imported file if you indeed want to load it. That would be too complex to realize for the many file formats Cinema 4D is implementing.
What you could do, given your GLTF file is in JSON format, is mangle it into place, given that Python has very nice JSON support. But making sure that this file stays valid, when you remove elements from it, might be not so trivial. I am also not sure if you will ever come out here with a net-win where mangling the file in Python first and then loading it outpaces just loading the full file.
Could you shed some light on where this becomes relevant? Are your files so large that loading is a bottleneck? I struggle to see a bit where this could become a scenario with GLTF.
When you have some file "input.gltf" which contains let's say ten elements you are interested in, and the rest is garbage, the workflow would be to load the whole file, and then just delete everything that you are not interested in, and then save that file. You could also take the inverse route, clone the elements you are interested in into new document. But there you will break links (e.g., a material tag linking to its material).
In the end this is all a bit hypothetical. You do not disclose if there are material, animations, etc., and most importantly how the scene hierarchy is, e.g., could an
Eberhardt{n}
hold descendant nodes that must be preserved, e.g.,Ebertraudt
is the first child ofEberhardt42
. This would of course make this all much more complicated.When you want to also support 2024, I would suggest that you just monkey patch
mxutils.IterateTree
(as this seems to be all you need). I have dumped the implementation of it at the end. Doing it like that, would have the benefit that you would native code for 2025.import c4d if c4d.GetC4DVersion() < 2024000: raise ImportError("This module requires Cinema 4D 2024 or later.") import mxutils # Etiher function form a local module, or define it here. with mxutils.LocalImportPath(__file__): from myLocalStuff import IterateTree # Monkey patch the function into earlier versions of Cinema 4D. if c4d.GetC4DVersion() < 2025000: mxutils.IterateTree = IterateTree
Cheers,
Ferdinanddef GetRootNode(node: c4d.GeListNode) -> c4d.GeListNode: """Rewinds the passed `node` to its root. """ # CheckType(node, c4d.GeListNode) while node.GetUp(): node = node.GetUp() return node def GetFirstSiblingNode(node: c4d.GeListNode) -> c4d.GeListNode: """Rewinds the passed `node` to its first sibling. """ # CheckType(node, c4d.GeListNode) while node.GetPred(): node = node.GetPred() return node def IterateTree(node: c4d.GeListNode | None, yieldSiblings: bool = False, rewindToFirstSibling: bool = False, rewindToRoot: bool = False) -> typing.Iterator[c4d.GeListNode]: """Yields nodes that are hierarchically related to `node`. """ def iter(node: c4d.GeListNode) -> typing.Iterator[c4d.GeListNode]: """Iterates over the descendants of the passed `node`. """ # The visited nodes and the node to stop iteration at. visited: dict[bytes, c4d.GeListNode] = {} terminal: c4d.GeListNode = node # Walking a tree iteratively is faster for medium to large trees than a recursive or # semi-recursive traversal. See: https://developers.maxon.net/forum/topic/13684 while node: # We could use GeListNode.__hash__ here, but it turns out that it is much slower than # doing it manually with MAXON_CREATOR_ID (sic!). Probably because of the overhead of # __hash__ hashing the UUID into an integer? nodeUuid: bytes = bytes(node.FindUniqueID(c4d.MAXON_CREATOR_ID)) if nodeUuid is None: raise RuntimeError(f"Could not retrieve UUID for {node}.") # Yield the node when it has not been encountered before. if visited.get(nodeUuid) is None: yield node visited[nodeUuid] = True # Walk the graph in a depth first fashion. getDown: c4d.GeListNode | None = node.GetDown() getNext: c4d.GeListNode | None = node.GetNext() getDownUuid: bytes | None = getDown.FindUniqueID(c4d.MAXON_CREATOR_ID) if getDown else None if getDown and getDownUuid is None: raise RuntimeError(f"Could not retrieve UUID for {getDown}.") if getDown and visited.get(bytes(getDownUuid)) is None: node = getDown elif node == terminal: break elif getNext: node = getNext else: node = node.GetUp() # --- End of iter() ---------------------------------------------------------------------------- if not isinstance(node, c4d.GeListNode): return # Set the starting node. if rewindToRoot: node = GetRootNode(node) if rewindToFirstSibling: node = GetFirstSiblingNode(node) # Traverse the hierarchy. while node: yield from iter(node) if not yieldSiblings: return node = node.GetNext()
-
@ferdinand said in Find objects within GLTF-file by name and attach them to the scene.:
Sorry, I did overlook the 'without having to load the complete gltf in memory' part of your initial question.
No worries I also understand that Cinema4D cannot implement a mechanism as I describe for all possible file formats.
What you could do, given your GLTF file is in JSON format, is mangle it into place, given that Python has very nice JSON support. But making sure that this file stays valid, when you remove elements from it, might be not so trivial. I am also not sure if you will ever come out here with a net-win where mangling the file in Python first and then loading it outpaces just loading the full file.
I have to look into that. Given that loading the whole file can easily take 5 minutes, even an approach like this might be a winner. However, you're absolutely right about introducing quite some potential instability when mangling with the file. Also, I did write
GLTF
everywhere, but there may be alsoGLB
files, which should be treated the same way.Could you shed some light on where this becomes relevant? Are your files so large that loading is a bottleneck? I struggle to see a bit where this could become a scenario with GLTF.
In the end this is all a bit hypothetical. You do not disclose if there are material, animations, etc., and most importantly how the scene hierarchy is, e.g., could an
Eberhardt{n}
hold descendant nodes that must be preserved, e.g.,Ebertraudt
is the first child ofEberhardt42
. This would of course make this all much more complicated.Of course! I'm happy to share, I just didn't want to blast too many details in this post to avoid distraction from the main question. In the end this will be a basic plugin for placing 3D text along curves.
Imagine a font with 3D letters. This font is saved in a single GLTF file. Each character is a node with a single mesh. No materials, no animations, no nested meshes.* The nodes are not nested,
Eberhardt
cannot be a child ofEbertraut
. I do already have a couple of these files and also an already working plugin for Blender. In Blender, I copied the internal function to load gltf's and interjected some code between indexing and loading the file. When the index is created, I reiterate over it and build a new scene tree with only the matching objects, and then continue the normal loading process with the new scene tree. So this is very similar to your file-mangling approach, and it's super fast. I could take this function and plug it in, but I'd need to disentangle it from the internal Blender specific dependencies which I assume to be a significant effort.The reason why I don't want to load the whole file into memory is because these files can be a gigabyte or even more. If the whole file is loaded into RAM, then it occupies a lot of space there. However, we may write just a couple of letters, so the most of the occupied RAM could be free. It does need to be in the file though! Also, loading a file like this may take 5 literal Minutes, which is - combined with the loss of RAM - an inconvenience I'd love to avoid. What is important, is that the user will have their own project to work with, and whatever this plugin is doing is running on the side. This is why I want to occupy the least amount of RAM possible.
*At least for now a simple mesh per character is fine. In a happy future it would be great to add support for materials, animations, etc - but this seems already complicated enough to get working.
When you want to also support 2024, I would suggest that you just monkey patch
mxutils.IterateTree
(as this seems to be all you need). I have dumped the implementation of it at the end. Doing it like that, would have the benefit that you would native code for 2025.Beautiful, thank you!
I hope that this cleared up some question marks. I get the feeling that I won't get around writing a custom gltf/glb loader. As you said, I can't expect that c4d already provides me with the functions I need, but I still wanted to ask just in case I am overlooking something.