Solved Best practice getting all objects in a certain Null

Thanks @m_magalhaes

yes I can get the name (of the first Object) and check that and getup till I reach it, still I am worried that I can not say for certain what "objecst" c4d.documents.MergeDocument() gave me ...

"certain Null" is not defined other than it is inserted at the top from C4D ... so i guess I have to get the Null by listing all root nulls bevore and after the import.

Sorry I was a bit unclear and its 2 question in one as I am still "brainstoring"

kind regards
mogh

hi,

In that case, i would open the document in a memory file structure, run my cleaning functions, save it on the memory file structure and merge the saved document.

if you really need to merge first, i would retrieve the first object of the document before merging the documents and check if i hit it.

Cheers,
Manuel

MAXON SDK Specialist

MAXON Registered Developer

As I am already running into problems with my current cleanup which proceses the tree more than once -> I think i have to open the import into "memory file structure" but I am not really clear what you mean from a functional standpoint.

There are several interesting builtin function.

c4d.documents.MergeDocument(doc, name, loadflags, thread) # <- used ATM

c4d.documents.LoadFile(name)
c4d.documents.LoadDocument(name, loadflags, thread=None)

c4d.documents.IsolateObjects(doc, t_objects) # whats the 

Whats the difference between MergeDocument and LoadDocument with SCENEFILTER_MERGESCENE flags ?
Why has MergeDocument a SCENEFILTER_MERGESCENE Flag ?

are my asumption right:

  1. create a new DOC e.g. tempdoc
  2. LoadFile/ LoadDocument / MergeDocument into that - which one ?
  3. copy objects or merge doc again ?

thanks in advance
mogh

my WIP prototype, but the generated cubes are not "present" in the merged document yet.

import c4d
from c4d import gui
#from c4d.documents import GetActiveDocument


def create_obj(doc):
    obj = c4d.BaseObject(c4d.Ocube)
    obj.SetName("importedcube")
    doc.InsertObject(obj)
    c4d.EventAdd()

def main():
        doc = c4d.documents.GetActiveDocument()
        print("Active Doc: ", doc.GetDocumentName())
        
        dirpath = r"c:\\"
        doctemp = None
       
        if doctemp==None:
            doctemp = c4d.documents.BaseDocument()
            doctemp.SetDocumentName("temp_import.c4d")
            doctemp.SetDocumentPath(dirpath)
            
            c4d.documents.InsertBaseDocument(doctemp) # calls set active allready
            #c4d.documents.SetActiveDocument(doctemp)

        create_obj(doctemp)
        create_obj(doctemp)
        
        """
        obj = doc.GetActiveObject()
        if obj is None:
            print("No active object")
            c4d.documents.KillDocument(doctemp)
            return        
        isolateDoc = c4d.documents.IsolateObjects(doctemp, [obj])
        """

        mfs = c4d.storage.MemoryFileStruct()
        mfs.SetMemoryWriteMode()
        c4d.documents.SaveDocument(doctemp, mfs, c4d.SAVEDOCUMENTFLAGS_DONTADDTORECENTLIST, c4d.FORMAT_C4DEXPORT)
        data = mfs.GetData()
        mfs.SetMemoryReadMode(data[0], data[1] )

        
        if c4d.documents.MergeDocument(doc, mfs, loadflags=c4d.SCENEFILTER_MERGESCENE, thread=None ): #  c4d.SCENEFILTER_MERGESCENE | c4d.SCENEFILTER_NOUNDO, 
            print("merged",  mfs)

        
        #doctemp.Flush()
        c4d.documents.KillDocument(doctemp)
        
        #c4d.documents.SetActiveDocument(doc)



# Execute main()
if __name__=='__main__':
    c4d.CallCommand(13957) # clear concole
    main()

Hello @mogh,

@m_magalhaes is on a brief vacation, I will quickly jump in here.

Whats the difference between MergeDocument and LoadDocument with SCENEFILTER_MERGESCENE flags ? Why has MergeDocument a SCENEFILTER_MERGESCENE Flag?

As stated in the docs, SCENEFILTER_MERGESCENE, primarily meant for plugins. Meant are here importer plugins, the FBX importer plugin will for example make some different decisions when the flag signals that the document to be loaded is meant to be merged with an existing scene. The key flags in SCENEFILTER are OBJECTS and MATERIALS when one wants to load in only that aspect of a document. You can also pass in additionally MERGESCENE, e.g., in Python c4d.SCENEFILTER_MERGESCENE | c4d.SCENEFILTER_OBJECTS, but it does not make a difference in most cases. The SCENEFILTER enum is also used in other contexts than MergeDocument().

LoadFile/ LoadDocument / MergeDocument into that - which one ?

That does depend on what you want to do. In principle I would favour them in the order you mentioned them. When possible, I would use LoadFile(), as this will give you all the file handling that Cinema 4D has, i.e., you can also open a bitmap in this way. It also perseveres the full scene state of a Cinema 4D file. But it will not give directly a BaseDocument (one can of course grab it manually via c4d.documents.GetFirstDocument() and then seraching for it) and will cause the file to be shown in Cinema 4D. When one requires a BaseDocument and does not want the file to be loaded by Cinema 4D, one should use LoadDocument(). Finally, when one must merge two files or wants to load only parts of a file, for example its materials, one should use MergeDocument(), but it will not preserve the active camera of the loaded in file, even when one indicates via the flags that everything should be loaded.

Cheers,
Ferdinand

MAXON SDK Specialist
developers.maxon.net

@ferdinand Thanks, you pointed me to my error:

The above code needs these scene flags to work properly ....
as the MergeDocument() merges only with certain flags ... (c4d.SCENEFILTER_MERGESCENE will not work this is a gotcha ...)

c4d.documents.MergeDocument(doc, mfs, loadflags=c4d.SCENEFILTER_OBJECTS , thread=None ):

So to update my blueprint:

  1. LoadDocument() to tempdoc (need to test this ...)
  2. Cleanup
  3. save tempdoc to MemoryFileStruct()
  4. merge "open/active" c4d File with MemoryFileStruct() (similar to prototype above)

@SDKTeam
I will update and set to solved when I do get a prototype working. please be patient.

thanks
mogh

Hey mogh,

I also had a look at your code, and I cleaned up some things. This works for me as I would expect it to work.

Cheers,
Ferdinand

import c4d

def create_obj(doc):
    """
    """
    obj = c4d.BaseObject(c4d.Ocube)
    obj.SetName("importedcube")
    doc.InsertObject(obj)

def main():
    """
    """
    # I removed a lot of clutter here.
    temp = c4d.documents.BaseDocument()
    create_obj(temp)

    mfs = c4d.storage.MemoryFileStruct()
    mfs.SetMemoryWriteMode()
    c4d.documents.SaveDocument(temp, mfs, 
        c4d.SAVEDOCUMENTFLAGS_DONTADDTORECENTLIST, c4d.FORMAT_C4DEXPORT)
    data = mfs.GetData()
    mfs.SetMemoryReadMode(data[0], data[1])

    # In this case you want to load in the objects and materials I assume.
    flags = c4d.SCENEFILTER_MERGESCENE | c4.SCENEFILTER_OBJECTS | c4d.SCENEFILTER_MATERIALS
    if c4d.documents.MergeDocument(doc, mfs, loadflags=flags):
        print()

    # You were missing an event add.
    c4d.EventAdd()

# Execute main()
if __name__ == '__main__':
    c4d.CallCommand(13957)  # clear concole
    main()

MAXON SDK Specialist
developers.maxon.net

How do I catch / prevent a "ReferenceError: the object 'c4d.documents.BaseDocument' is not alive"
when the user has an unsaved / unclicked / unactive doc as master ?

my problem ist the

if not c4d.documents.MergeDocument(doc, mfs, loadflags=flags):

it fails while your

if c4d.documents.MergeDocument(doc, mfs, loadflags=flags):

is error free

thanks

Hello @mogh,

@mogh said in Best practice getting all objects in a certain Null:

I will update and set to solved when I do get a prototype working. please be patient.

There is no need to feel rushed. We close threads after 14 days of inactivity, but when users say they want to keep them open when we request a closing, we keep them open much longer. It is only that we will ask every 14 days.

my problem ist the

if not c4d.documents.MergeDocument(doc, mfs, loadflags=flags):

it fails while your

if c4d.documents.MergeDocument(doc, mfs, loadflags=flags):

The negation operator is certainly not the culprit, at least I could not imagine how. A Python object that is not alive anymore, means that the Python bindings and the C++ backend have gone out of sync. Or in other words, there is still a reference to an object in Python but the data it is wrapping living in the C++ backend has been deallocated. You can test an object for being still alive with c4d.C4DAtom.IsAlive().

However, when I looked at your script, I first ran into similar problems. The culprit must have been the document inside the MFS, because when I tested doc it was still alive, as it was still open in the editor (c4d.documents.BaseDocument is also a node, i.e., derived from C4DAtom). But then the problem vanished, and I could not reproduce it anymore. I do not think that your code is or my code was the issue. I would recommend restarting Cinema 4D and see if the problem then does vanish. Otherwise we have to see if either the MFS or MergeDocument Python wrappers produces dangling pointers in some cases.

FYI: I just ran the code below, i.e., with a not, and it runs fine for me.

Cheers,
Ferdinand

import c4d

def create_obj(doc):
    """
    """
    obj = c4d.BaseObject(c4d.Ocube)
    obj.SetName("importedcube")
    doc.InsertObject(obj)

def main():
    """
    """
    # I removed a lot of clutter here.
    temp = c4d.documents.BaseDocument()
    create_obj(temp)

    mfs = c4d.storage.MemoryFileStruct()
    mfs.SetMemoryWriteMode()
    c4d.documents.SaveDocument(temp, mfs, 
        c4d.SAVEDOCUMENTFLAGS_DONTADDTORECENTLIST, c4d.FORMAT_C4DEXPORT)
    data = mfs.GetData()
    mfs.SetMemoryReadMode(data[0], data[1])

    # In this case you want to load in the objects and materials I assume.
    flags = c4d.SCENEFILTER_MERGESCENE | c4d.SCENEFILTER_OBJECTS | c4d.SCENEFILTER_MATERIALS
    if not c4d.documents.MergeDocument(doc, mfs, loadflags=flags):
        print("Blah")

    # You were missing an event add.
    c4d.EventAdd()

# Execute main()
if __name__ == '__main__':
    c4d.CallCommand(13957)  # clear concole
    main()

MAXON SDK Specialist
developers.maxon.net

Found the problem I tried to Kill a document which was not alive, don't know if this is necessary with a merge.

    if c4d.C4DAtom.IsAlive(temp):
        c4d.documents.KillDocument(temp)

And another gotcha c4d.documents.SetActiveDocument(temp) seems to be mandatory if you want to use CallCommand() -> and do not to forget to set the doc back to active after your routine.

Thank you