Solved Catch - 'c4d.BaseDraw' is not alive

Dear Developers;

Whats the best practice to handle these conditions where the c4d.BaseDraw is not alive anymore or the plugin probably has old data ?!?

My hunch is I have to reinitialize on restore / reload but I am clueless how to tackle this.

My environment:

User closes all documents (new unsaved document is displayed)
or the user creates new document - then clicks on plugin.

Code snipet:

    def __init__(self):

        self.doc = GetActiveDocument()
        
        try:
            self.rval_bd = self.doc.GetActiveBaseDraw()
        except ReferenceError as e:
            print("Error: ", e)
    """
    more code
    """

    def Restore(self, pluginid, sec_ref) :  
        
        # Set the pointers to the specific sub dialog here 
        # rather than in the CommandData section of this plugin  
        if sec_ref['subid'] == 1: 
            return self.sub_dialog1.Restore(pluginid, sec_ref)
        else: 
            return super(MainDialog, self).Restore(pluginid, sec_ref)

Where the error occurs:

self.AddCheckbox(ID_SAVEFRAME, flags=c4d.BFH_LEFT, initw=270, inith=0, name="Show Save Frame")  
self.SetBool(ID_SAVEFRAME, self.rval_bd[c4d.BASEDRAW_DATA_SHOWSAFEFRAME])

ReferenceError: the object 'c4d.BaseDraw' is not alive

The Try test above does nothing in this regard.
The only thing that works is reloading the python plugins ...

There is more code than this but this might be relevant ?

class MenuCommand(c4d.plugins.CommandData):
    dialog = None

    def Execute(self, doc):
        if self.dialog is None:
            self.dialog = MainDialog()
        return self.dialog.Open(c4d.DLG_TYPE_ASYNC, PLUGIN_ID, defaulth=100, defaultw=300)

    def RestoreLayout(self, sec_ref):
        if self.dialog is None:
            self.dialog = MainDialog()
        return self.dialog.Restore(PLUGIN_ID, sec_ref)

Kind regards
mogh

Hey @mogh,

Thank you for reaching out to us. The idea of a node, a c4d.C4DAtom not being alive means that the Python layer cannot find the C++ C4DAtom which the Python layer c4d.C4DAtom was referencing, usually because something has been reallocated in the background while someone was long-term storing a c4d.C4DAtom instance. We talked about it here in more detail.

Sometimes you then must write elaborate interfaces which recapture the same thing over their UUID, e.g., get hold of the same shader once its Python reference has gone bad. But that does not seem necessary here since you are interested only in the active viewport of the active document. It is in this case also not only the case that the viewport could have been reallocated, but also what is the active viewport could have changed (although that will likely cause viewports to be reallocated).

So, when you want to modify the active base draw, in fact any node, make sure to get a fresh reference when possible.

doc: c4d.document.BaseDocument = c4d.documents.GetActiveDocument()
bd: c4d.BaseDraw = doc.GetActiveBaseDraw()
self.AddCheckbox(ID_SAVEFRAME, flags=c4d.BFH_LEFT, initw=270, inith=0, name="Show Save Frame")  
self.SetBool(ID_SAVEFRAME, bd[c4d.BASEDRAW_DATA_SHOWSAFEFRAME])

If you need access to the active viewport of the active document very often, you could make it a property with some mild caching.

import c4d
import typing

class Foo:
    """Provides cached access to the active document and active viewport.
    """
    def __init__(self) -> None:
        self._activeDocument: typing.Optional[c4d.documents.BaseDocument] = None
        self._activeViewport: typing.Optional[c4d.BaseDraw] = None

    @property
    def ActiveDocument(self) -> c4d.documents.BaseDocument:
        """Returns the active document.
        """
        if self._activeDocument is None or not self._activeDocument.IsAlive():
            self._activeDocument = c4d.documents.GetActiveDocument()
        
        return self._activeDocument

    @property
    def ActiveBaseDraw(self) -> c4d.BaseDraw:
        """Returns the active viewport in the active document.
        """
        if self._activeViewport is None or not self._activeViewport.IsAlive():
            self._activeViewport = self.ActiveDocument.GetActiveBaseDraw()
        
        return self._activeViewport


    def Bar(self) -> None:
        """Makes use of the #ActiveBaseDraw property. 
        """
        self.ActiveBaseDraw[c4d.BASEDRAW_DATA_TEXTURES] = True
⚠ Although this also suffers from the problem that what is the active viewport could have changed. Caching anything that is "active" is a bad idea as what is active can change. Active entities should always be retrieved when required unless one can guarantee that they cannot change (by being in a modal dialog for example).

Cheers,
Ferdinand

MAXON SDK Specialist
developers.maxon.net

Hey @mogh,

Thank you for reaching out to us. The idea of a node, a c4d.C4DAtom not being alive means that the Python layer cannot find the C++ C4DAtom which the Python layer c4d.C4DAtom was referencing, usually because something has been reallocated in the background while someone was long-term storing a c4d.C4DAtom instance. We talked about it here in more detail.

Sometimes you then must write elaborate interfaces which recapture the same thing over their UUID, e.g., get hold of the same shader once its Python reference has gone bad. But that does not seem necessary here since you are interested only in the active viewport of the active document. It is in this case also not only the case that the viewport could have been reallocated, but also what is the active viewport could have changed (although that will likely cause viewports to be reallocated).

So, when you want to modify the active base draw, in fact any node, make sure to get a fresh reference when possible.

doc: c4d.document.BaseDocument = c4d.documents.GetActiveDocument()
bd: c4d.BaseDraw = doc.GetActiveBaseDraw()
self.AddCheckbox(ID_SAVEFRAME, flags=c4d.BFH_LEFT, initw=270, inith=0, name="Show Save Frame")  
self.SetBool(ID_SAVEFRAME, bd[c4d.BASEDRAW_DATA_SHOWSAFEFRAME])

If you need access to the active viewport of the active document very often, you could make it a property with some mild caching.

import c4d
import typing

class Foo:
    """Provides cached access to the active document and active viewport.
    """
    def __init__(self) -> None:
        self._activeDocument: typing.Optional[c4d.documents.BaseDocument] = None
        self._activeViewport: typing.Optional[c4d.BaseDraw] = None

    @property
    def ActiveDocument(self) -> c4d.documents.BaseDocument:
        """Returns the active document.
        """
        if self._activeDocument is None or not self._activeDocument.IsAlive():
            self._activeDocument = c4d.documents.GetActiveDocument()
        
        return self._activeDocument

    @property
    def ActiveBaseDraw(self) -> c4d.BaseDraw:
        """Returns the active viewport in the active document.
        """
        if self._activeViewport is None or not self._activeViewport.IsAlive():
            self._activeViewport = self.ActiveDocument.GetActiveBaseDraw()
        
        return self._activeViewport


    def Bar(self) -> None:
        """Makes use of the #ActiveBaseDraw property. 
        """
        self.ActiveBaseDraw[c4d.BASEDRAW_DATA_TEXTURES] = True
⚠ Although this also suffers from the problem that what is the active viewport could have changed. Caching anything that is "active" is a bad idea as what is active can change. Active entities should always be retrieved when required unless one can guarantee that they cannot change (by being in a modal dialog for example).

Cheers,
Ferdinand

MAXON SDK Specialist
developers.maxon.net