Issues Modifying Cloned Document



  • Hello! I'm attempting to make Cinema 4D's FBX export a bit more robust for our pipeline. We're dealing with lots of instancing of non-polygonal objects which C4D seems to have trouble outputting to FBX. Specifically we're seeing issues with Symmetry, Cloners and Subdivision Surfaces.

    I wrote a script that worked well, but it would modify the current document and I felt it would be better to modify a clone of the document. In order to do that I built a class to handle the document preparation. Unfortunately, this seems to have broken something fundamental in the logic but I can't figure out why.

    The plugin is supposed to find all instanced subdivision surfaces, create a group null below the subdivision, point all instances that had previously referred to the sds to that null, and then place each of the instances into a new subdivision surface (so that the instance is now the cage mesh). This is the only way I can find to get instances and subdivision surfaces to work together in an FBX export. However, in it's new form the plugin seems to end up with the instances inside of two nulls instead of inside a single subdivision surface. In addition some of the instances and subdivision surfaces are switching locations in the hierarchy. It's a mess!

    Perhaps I've bitten off more than I can handle at the moment. Below is my full code. Any help with cleaning this up would be much appreciated!

    import c4d
    import c4d.utils
    import os
    import os.path
    
    blacklist = {"persp","top","side","front"}
    FBX_EXPORTER_ID = 1026370
    PLUGIN_ID = 1053631
    
    class ExportFBX():
        def __init__(self, doc, flags):
            if doc:
                self.doc = doc.GetClone()
                self.name = os.path.splitext(doc.GetDocumentName())[0]
            self.flags = flags
            self.status = False
    
        def __del__(self):
            if self.doc:
                c4d.documents.KillDocument(self.doc)
    
        def _GetAllObjects(self, obj):
            while obj:
                yield obj
                for o in self._GetAllObjects(obj.GetDown()):
                    yield o
                obj = obj.GetNext()
    
        def _UpdateName(self, obj):
            name = obj.GetName()
            if name in blacklist:
                obj.SetName("{}_geo".format(name))
    
        def _UpdateAxis(self, obj):
            if obj:
                null = c4d.BaseObject(c4d.Onull)
                null.SetName("{}_grp".format(obj.GetName()))
                self.doc.InsertObject(null)
                null.InsertBefore(obj)
                obj.InsertUnder(null)
                return null
    
        def _UpdateSdsInstance(self, obj):
            sds = obj.GetReferenceObject(self.doc)
            parent = None
            child = sds.GetDown()
            pred = obj.GetPred()
            if not pred:
                parent = obj.GetUp()
    
            obj.SetReferenceObject(child)
    
            new_sds = c4d.BaseObject(c4d.Osds)
            if sds.GetName().startswith("Subdivision"):
                new_sds.SetName("{0}_subdivision".format(child.GetName()))
            else:
                new_sds.SetName(sds.GetName())
    
            new_sds.SetData(sds.GetData())
            self.doc.InsertObject(new_sds, parent, pred, checknames=True)
            obj.InsertUnder(new_sds)
    
    
        def Prepare(self):
            if self.doc:
                objs = list(self._GetAllObjects(self.doc.GetFirstObject()))
                instances = [o for o in objs if o.IsInstanceOf(c4d.Oinstance)]
                instance_sources = [i.GetReferenceObject(self.doc) for i in instances]
                sds_list = [o for o in objs if o.IsInstanceOf(c4d.Osds) and o in instance_sources]
                convert_editable = [o for o in objs if o.IsInstanceOf(c4d.Osymmetry) or o.IsInstanceOf(1018544)]
                c4d.StopAllThreads()
                c4d.utils.SendModelingCommand(c4d.MCOMMAND_MAKEEDITABLE, convert_editable, doc=self.doc, flags=c4d.MODELINGCOMMANDFLAGS_CREATEUNDO)
                _ = [self._UpdateName(o) for o in objs]
                _ = [self._UpdateAxis(sds.GetDown()) for sds in sds_list]
                _ = [self._UpdateSdsInstance(i) for sds in sds_list for i in instances if i.GetReferenceObject(self.doc) == sds]
    
        def Export(self):
            if self.doc:
                self.status = c4d.documents.SaveDocument(self.doc,
                                os.path.join(self.doc.GetDocumentPath(), self.name),
                                self.flags,
                                FBX_EXPORTER_ID)
                                
        def GetStatus(self):
            return self.status
    
    
    class ExportFBXCommandData(c4d.plugins.CommandData):
        def Execute(self, doc):
            flags = c4d.SAVEDOCUMENTFLAGS_EXPORTDIALOG|c4d.SAVEDOCUMENTFLAGS_DONTADDTORECENTLIST|c4d.SAVEDOCUMENTFLAGS_DIALOGSALLOWED|c4d.SAVEDOCUMENTFLAGS_SAVEAS
    
            fbx_export = ExportFBX(doc, flags)
    
            fbx_export.Prepare()
            fbx_export.Export()
            
            return fbx_export.GetStatus()
            
    
    # Execute main()
    if __name__=='__main__':
        directory, _ = os.path.split(__file__)
        fn = os.path.join(directory, "res", "export_fbx.tif")
    
        bmp = c4d.bitmaps.BaseBitmap()
        if bmp is None:
            raise MemoryError("Failed to create a BaseBitmap")
    
        if bmp.InitWith(fn)[0] != c4d.IMAGERESULT_OK:
            raise MemoryError("Failed to initialize a BaseBitmap")  
    
        c4d.plugins.RegisterCommandPlugin(id=PLUGIN_ID,
                                            str="Export FBX...",
                                            help="An FBX export with enhanced support for instances.",
                                            info=0,
                                            dat=ExportFBXCommandData(),
                                            icon=bmp)
    


  • Just wanted to provide an update here.

    I stand corrected, the above plugin creates an FBX that works perfectly when imported into Maya. It appears that Cinema 4D isn't able to understand what's going on in the FBX and I was checking the export by importing into Cinema 4D.

    I suppose the lesson here is to follow the actual pipeline when testing ☺

    ...and the other lesson is that Cinema's FBX export and import are not to be trusted ☠



  • Hello,

    Really sorry about this issue.

    Would it be possible to send us a scene and some screenshot maybe to reproduce the behaviour ? That would be really appreciate. I will send that to our devs.

    You can use our mailbox sdk_support@maxon.net

    Thanks and cheers,
    Manuel


Log in to reply