ObjectData plugin, parent change in OM



  • On 27/01/2016 at 02:50, xxxxxxxx wrote:

    User Information:
    Cinema 4D Version:   17 
    Platform:   Windows  ;   
    Language(s) :     C++  ;

    ---------
    Hi guys,

    Is there a quick way to message an ObjectData plugin if its parent changes in the OM?

    For stability/UX reasons the object plugin I'm working on needs to call GetContour() if its parent in the OM changes, or if the parents of objects in its descriptions change.

    Changing the object's parent doesn't seem to trigger any dirty flags; DIRTYFLAGS_MATRIX won't trigger if the matrixes of the old and new parents are exactly the same, so it's not a reliable check. I also don't want to set my object's dirty flags permanently on for obvious performance reasons. My next option would be to try catching the parent change via Message(), but it doesn't have a good option either; MSG_DRAGANDDROP continuously triggers an event while dragging the object.

    There was a very old post from nine years ago about this problem, but it doesn't seem like there's a proper solution. ( https://plugincafe.maxon.net/topic/3236/2620_notification-of-change-of-parent&KW=parent+changed )

    Technically my plugin should remain stable since the object's old cache would be returned if its parent changes. But the behavior would look odd for the user while editing the scene.



  • On 27/01/2016 at 03:02, xxxxxxxx wrote:

    Maybe you could calculate the parent's GUID into the dirty count?

    Bool CheckDirty(BaseObject* op, BaseDocument* doc)
    {
      Int32 dirty = op->GetDirty(DIRTYFLAGS_DATA);
      if (op->GetUp())
        dirty += static_cast<Int32>(op->GetUp()->GetGUID());
      if (dirty != this->dirty) {
        this->dirty = dirty;
        return true;
      }
      return false;
    }
    


  • On 27/01/2016 at 05:17, xxxxxxxx wrote:

    Thanks Niklas, that seemed to do the trick. 😄

    I just had to place GetGUID() in a conditional in case GetUp() returns a nullptr. Also, I placed it in a while loop to check the whole parent chain instead of the immediate parent.

    void UberTracer02 :: CheckDirty ( BaseObject* op, BaseDocument* doc ) {  
      
      if ( IsInputDirty( op, doc ) ) {  
          op->SetDirty( DIRTYFLAGS_DATA );  
          GePrint( "dirty inexList" );  
      }  
      
      if ( op->GetDataInstance()->GetBool( USE_GLOBAL, false ) ) { // only validate self if in global mode  
          **BaseObject* parent    = op->GetUp();  
          UInt64      parentId = 0;  
          while ( parent ) {  
              parentId += parent->GetGUID();  
              parent    = parent->GetUp();  
          }**  
          UInt64      opDirtySum = op->GetDirty( DIRTYFLAGS_MATRIX | DIRTYFLAGS_DESCRIPTION ) + parentId;  
          if ( this->_opDirtySum_old != opDirtySum ) {  
              op->SetDirty( DIRTYFLAGS_DATA );  
                this->_opDirtySum_old = opDirtySum;  
              GePrint( "dirty op" );  
          }  
      }  
    }
    


  • On 27/01/2016 at 09:49, xxxxxxxx wrote:

    Hi,

    could somebody explain what a GUID is? I looked it up in the docs and it seems to be a unique ID working also for cache objects. Fair enough, but I would assume then it is always the same ID (for a normal object) no?
    If not (and it changes) on what is this change based?

    Also the docs say that if there is no marker (for 'real' objects) it won't return a GUID but it doesn't say what is returned instead.

    Would definetly like to know more about this. Haven't heard of it before.
    Thanks in advance.

    Concerning the topic, have you tried using GetHDirty() instead of GetDirty()? I remember it handles more cases somehow (though I don't really know what the exact difference is I kind of created the rule that I use GetHDirty() if GetDirty() doesn't work).



  • On 28/01/2016 at 00:45, xxxxxxxx wrote:

    EDIT: hit post by accident without typing anything.

    I just tested GetHDirty and HDIRTY_OBJECT_HIERARCHY only increments if that object's children is changed. It does nothing when its parents change, which I needed.

    As for GUID, I'm not 100% sure myself how it differs with the other means of identifying objects in Cinema (GUID, UniqueIP, Link, GeMarker, etc.) There's a lot of info in the documents, but I haven't found examples that make sense to me.



  • On 28/01/2016 at 02:08, xxxxxxxx wrote:

    Hello,

    the GUID returned by GetGUID() is a checksum based on several properties. In case of a object in a generator's cache it is based on the UniqueIP set by the generator. In case of non-cache objects it is based on the marker returned by GetMarker(). For polygon and point objects the polygon and point count is added to the calculation of the checksum.

    Best wishes,
    Sebastian



  • On 28/01/2016 at 04:50, xxxxxxxx wrote:

    Thanks Sebastian. So I assume if no Marker is available on a non-cache object it will return -1 (NOTOK)?



  • On 28/01/2016 at 04:52, xxxxxxxx wrote:

    Originally posted by xxxxxxxx

    I just tested GetHDirty and HDIRTY_OBJECT_HIERARCHY only increments if that object's children is changed. It does nothing when its parents change, which I needed.

    Ah ok, thanks for the info. By change you mean the position in the OM? Or the data/matrix of parent objects?



  • On 28/01/2016 at 05:15, xxxxxxxx wrote:

    The OM position.
    There are some modes where I don't need to check the parent's matrix/data, e.g. I just need to generate geo in local space, so I don't need to update my cache when the parent matrix changes.



  • On 28/01/2016 at 08:55, xxxxxxxx wrote:

    Hello,

    as said, the  GUID is a checksum. If no marker is presented, the marker simply does not influence the checksum calculation but other factors may. So if no marker is present the return value does not need to be -1.

    best wishes,
    Sebastian



  • On 28/01/2016 at 09:45, xxxxxxxx wrote:

    Sorry but I am still confused because the docs say that "if an object has no marker it will not return a GUID". So no GUID is still a checksum? Simply confuses me (this sentence in the docs. I do know now that it's a checksum but the question still arises when reading that sentence because it says the id is based on the marker and so I would logically assume that the id is NOT "filled" validly if there is no marker = no GUID returned).



  • On 28/01/2016 at 09:49, xxxxxxxx wrote:

    Originally posted by xxxxxxxx

    The OM position.
    There are some modes where I don't need to check the parent's matrix/data, e.g. I just need to generate geo in local space, so I don't need to update my cache when the parent matrix changes.

    Ah I see. Okay, thanks. Good to have this in mind if I ever come across this necessity. :)



  • On 29/01/2016 at 01:38, xxxxxxxx wrote:

    Hello,

    I think what the documentation is trying to say is that without a marker the returned number may not be unique. But the function will still return some number.

    Best wishes,
    Sebastian



  • On 29/01/2016 at 04:55, xxxxxxxx wrote:

    Ok, it would be great if the documentation would indeed say that. An update request this is then.

    Cheers



  • On 13/02/2016 at 04:58, xxxxxxxx wrote:

    Alrighty, here's the current code I have for handling my plugin's dirty checksum. It's much longer than I expected as there's some weird quirks with where and when the checksums actually increment.

    - Deformers and Effectors do not affect the dirty flags on an object; they affect the dirty checksums of a cached instance of the object.
    I need to get the modified objects via GetCache() or GetDeformCache() and check that returned object's dirty checksum. If the object creates a virtual hierarchy (Cloner, SubD, etc.) I have to do a recursive function to check the dirty checksums of each element.
    The VirtualDirtySum() function below does this.

    - Switching an object's parent in the OM does not increment dirty checksums. Moving an object's parent's matrix does not increment it either; DIRTY_MATRIX only increments changes with the local matrix.
    To check for changes in the parent's hierarchy + matrixes I need to do a recursion upwards to get their dirty matrix checksums, as well as the GUID of their parents.

    - My plugin can also read thinking particles from TP_PGroup. The whole TP system increments with every frame change, so to reduce dirty calls I only check for the particle system's dirty checksum if the group contains particles. If the group is empty, I skip the check.

    - Right now I'm using a BaseArray to store the checksums. I thought of just adding everything into one UInt64 variable, but I fear there might be some rare cases where I couldn't detect changes (e.g. checksums of the inputs increment but a generator object has fewer objects in the next frame, so the total checksum is the same.)
    I'll do a summation instead of an array if I can do enough tests to show it's safe.

    - Render Instances and the Sphere object's Render Perfect mode do not return any cached geo, so
    several of my checks are invalid for those cases. I fear that if I override those objects to generate geo during render time, it could cause unexpected changes in how the scene looks. For now I'll just ignore them as special cases; if a user wants to use those objects as input for my plugin, they'll have to turn off instancing.

    - There might still be some scenarios that I missed. I'll post updates if I find any more problems.

    #include "c4d.h"  
    #include "c4d_particles.h"          _// TP system_  
    #include "customgui_inexclude.h"    _// in/exclude methods_ **  
      
     _//----------------------------------------------------------------------------------------  
    ///Gets TP Master System for the document.  
    /// @param[in] doc  : BaseDocument the object is in  
    /// @return         : TP_MasterSystem of the document  
    /// @note           : Only one instance should exist for a document  
    //----------------------------------------------------------------------------------------_  
    TP_MasterSystem*  GetTpMasterSystem   ( const BaseDocument* doc )** {  
      if ( !doc ) {  
      return nullptr;  
      }  
      BaseSceneHook* hook = doc->FindSceneHook( ID_THINKINGPARTICLES );  
      if ( !hook || hook->GetType() != ID_THINKINGPARTICLES ) {  
      return nullptr;  
      }  
      return static_cast< TP_MasterSystem* >( hook );  
    } **  
      
     _//----------------------------------------------------------------------------------------  
    ///Loops through input object's virtual hierarchy, gets total dirty checksum of its elements  
    /// @param[out] dirtySum    : dirty checksum. make sure it's initialized before calling  
    /// @param[in]  obj         : input object to get virtual hierarchy from  
    /// @param[in]  INEX_FLAGS  : inex flags tied to object  
    /// @param[in]  ISTIP       : flag if object is tip of virtual hierarchy's branch  
    /// @param[in]  ISCACHE     : flag if recursion occurs  
    /// @return                 : none  
    //----------------------------------------------------------------------------------------_  
    void VirtualDirtySum( UInt64& dirtySum,  
                        BaseObject* obj, const Int32& INEX_FLAGS, Bool ISTIP = false, const Bool ISCACHE = false )** {  
      
      BaseObject* cachedObj = obj->GetDeformCache();  
      //------------------------------------------------------------------------------------------------------------------------------------------------------------  
      if ( !ISCACHE ) {                                                                   _// item passes first time, pre-emptively check if it has virtual children_  
      BaseObject *testParent = cachedObj;  
      if ( !testParent ) {                                                              _// no deform cache? check for regular cache_  
        testParent = obj->GetCache();  
      }  
      if ( testParent ) {                                                               _// deform cache or regular cache? check if cache has children_  
        ISTIP = ( testParent->GetDown() == nullptr );   
      }  
      else {                                                                            _// no cache at all? object force tip status  
        ISTIP = true;_  
      }  
      }  
      //------------------------------------------------------------------------------------------------------------------------------------------------------------  
      if ( cachedObj ) {                                                                  _// deformed cache exists? apply recursion_  
      VirtualDirtySum( dirtySum, cachedObj, INEX_FLAGS, ISTIP, true );  
      }  
      else {  
      cachedObj = obj->GetCache();  
      if ( cachedObj ) {                                                
        VirtualDirtySum( dirtySum, cachedObj, INEX_FLAGS, ISTIP, true );                _// regular cache exists? apply recursion_  
      }  
      else {                                                                            _// not cache anymore? get dirty sum_  
        if ( ISTIP || INEX_FLAGS & UT_READ_FULL_CHAIN ) {                               _// if flagged, check if object is tip of its hierarchy. skip if it's not._  
          if ( !obj->GetBit( BIT_CONTROLOBJECT ) ) {                                    _// if under generator, check if non-input. skip if used._  
            dirtySum += obj->GetDirty( DIRTYFLAGS_ALL );  
          }  
        }  
      }  
      }  
      //------------------------------------------------------------------------------------------------------------------------------------------------------------  
      if ( ISCACHE ) {                                                                    _// object is in virtual hierarchy? apply recursion_  
      for ( cachedObj = obj->GetDown(); cachedObj; cachedObj = cachedObj->GetNext() ) { _// get next object in virtual hierarchy_  
        ISTIP = ( cachedObj->GetDown() == nullptr );  
        VirtualDirtySum( dirtySum, cachedObj, INEX_FLAGS, ISTIP, true );  
      }  
      }  
    }  
      
     ** _//----------------------------------------------------------------------------------------  
    ///Iterates through inexclude list of input objects.  
    ///First entry of output array is the plugin object.  
    ///Successive array entries are from the input inexclude list.  
    /// @param[out] inexDirtySum_old  : array containing dirty checksums of input objects. Global variable from class  
    /// @param[in]  op                : object generated by plugin  
    /// @param[in]  doc               : document the plugin is in  
    /// @return                       : true if checksum changed. false if remains the same  
    //----------------------------------------------------------------------------------------_  
    Bool              IsInputDirty        ( maxon::BaseArray< UInt64 >& inexDirtySum_old,  
                                          BaseObject* op, BaseDocument* doc )** {  
      
      Bool                        isInputDirty    = false;  
      TP_MasterSystem*            tpMasterSystem  = GetTpMasterSystem( doc );  
      const InExcludeData*        inex            = ( InExcludeData* )op->GetDataInstance()->GetCustomDataType( INEX_INPUT, CUSTOMDATATYPE_INEXCLUDE_LIST );  
      const Int32                 inexCt          = inex->GetObjectCount();  
      BaseList2D*                 inexObj;  
      maxon::BaseArray< UInt64  > inexDirtySum;  
      inexDirtySum.Reset();  
      
      UInt64      dirtySumTemp  = 0,  
                parentId      = 0;  
      BaseObject* parent,  
              * child;  
      
      //------------------------------------------------------------------------------------------------------------------------------------------------------------  
      if ( op->GetDataInstance()->GetBool( USE_GLOBAL, false ) ) {                                _// if plugin object set to global mode, matrixes are checked_  
      parent = op->GetUp();  
      if ( parent ) {  
        parentId = parent->GetGUID();  
      }  
      inexDirtySum.Append( op->GetDirty( DIRTYFLAGS_MATRIX ) + parentId );                      _// plugin object's dirty matrix checksum plus parent's GUID_  
      
      for ( child = op->GetUp(); child; child = child->GetUp() ) {                              _// checksums of object's parent chain_  
        parent = child->GetUp();  
        if ( parent ) {  
          parentId = parent->GetGUID();  
        }  
        else {  
          parentId = 0;  
        }  
        inexDirtySum.Append( child->GetDirty( DIRTYFLAGS_MATRIX ) + parentId );                 _// for each parent up the chain, get dirty matrix flags_  
      }  
      }  
      else {                                                                                      _// if plugin set to local mode, only parent switching is checked_  
      parent = op->GetUp();  
      while ( parent ) {                                                                        _// recurse for parent's GUID_  
        inexDirtySum.Append( parent->GetGUID() );  
        parent = parent->GetUp();  
      }  
      }  
      
      //------------------------------------------------------------------------------------------------------------------------------------------------------------  
      for ( Int32 i = 0; i < inexCt; ++i ) {  
      inexObj = inex->ObjectFromIndex( doc, i );  
      
      if ( inexObj ) {  
      
        if ( inexObj->GetType() == ID_TP_PGROUP ) {                                                   _//input object is tp_pGroup_  
          if ( tpMasterSystem ) {  
            if ( tpMasterSystem->GetGroupParticleCount( static_cast< TP_PGroup* >( inexObj ), 0 ) ) { _// tp_pGroup has particles? read tpMasterSystem's dirty checksum_  
              inexDirtySum.Append( tpMasterSystem->GetDirty() + inexObj->GetDirty( DIRTYFLAGS_ALL ) );  
            }  
            else {  
              inexDirtySum.Append( inexObj->GetDirty( DIRTYFLAGS_ALL ) );                             _// no particles? just get flags from tp_pGroup_  
            }  
          }  
        }  
      
        else {  
          BaseObject* obj = static_cast< BaseObject* >( inexObj );                                    _// input object is baseobject_  
      
          BaseObject* parent,  
                    * child;  
          UInt64      parentId      = 0;  
          UInt64      dirtySumTemp  = 0;  
      
          parent = obj->GetUp();  
          if ( parent ) {  
            parentId = parent->GetGUID();  
          }  
      
          VirtualDirtySum( dirtySumTemp, obj, inex->GetFlags( i ) );                                  _// dirty checksum of object's virtual hierarchy_  
          inexDirtySum.Append( obj->GetDirty( DIRTYFLAGS_ALL ) + dirtySumTemp + parentId );           _// add parent's GUID, in case parent is changed_  
      
          for ( child = obj->GetUp(); child; child = child->GetUp() ) {                               _// checksums of object's parent chain_  
            parent = child->GetUp();  
            if ( parent ) {  
              parentId = parent->GetGUID();  
            }  
            else {  
              parentId = 0;  
            }  
            inexDirtySum.Append( child->GetDirty( DIRTYFLAGS_MATRIX ) + parentId );                   _// for each parent up the chain, get dirty matrix flags_  
          }  
        }  
      }  
      }  
      //------------------------------------------------------------------------------------------------------------------------------------------------------------  
      if ( inexDirtySum.GetCount() == inexDirtySum_old.GetCount() ) {                                   _// item count the same, check for checksum mismatches_  
      for ( Int32 i = 0; i < inexDirtySum.GetCount(); ++i ) {  
        if ( inexDirtySum[i] != inexDirtySum_old[i] ) {  
          isInputDirty = true;  
          break;  
        }  
      }  
      }  
      else {                                                                                            _// item count different, automatically assume dirty_  
      isInputDirty = true;  
      }  
      //------------------------------------------------------------------------------------------------------------------------------------------------------------  
      if ( isInputDirty ) {                                                                             _// transfer array_  
      inexDirtySum_old.Reset();  
      inexDirtySum_old.CopyFrom( inexDirtySum );  
      }  
      inexDirtySum.Reset();  
      //------------------------------------------------------------------------------------------------------------------------------------------------------------  
      _// colorizes object in viewport when dirty checksum changes. remove after all testing is okay._  
      ObjectColorProperties prop;  
      op->GetColorProperties( &prop );  
      if ( isInputDirty ) {   
      prop.color = Vector( 0.9, 0.2, 0.1 );  
      }  
      else {  
      prop.color = Vector( 1.0 );  
      }  
      if ( prop.usecolor != ID_BASEOBJECT_USECOLOR_ALWAYS ) {  
      prop.usecolor = ID_BASEOBJECT_USECOLOR_ALWAYS;  
      }  
      op->SetColorProperties( &prop );  
      //------------------------------------------------------------------------------------------------------------------------------------------------------------  
      
      return isInputDirty;  
    }  
      
     ** _//----------------------------------------------------------------------------------------  
    ///virtual Member from ObjectData class  
    /// @param[in]  op  : object generated by plugin  
    /// @param[in]  doc : document the plugin is in  
    /// @return         : none  
    /// @notes          : _inexDirtySum_old is a private global variable. stores array of dirty checksums  
    //----------------------------------------------------------------------------------------_  
    void          UberTracer02::CheckDirty            ( BaseObject* op, BaseDocument* doc )** {  
      if ( IsInputDirty( _inexDirtySum_old, op, doc ) ) { _// check input inex list for dirty checksum, plus plugin object itself_  
      op->SetDirty( DIRTYFLAGS_DATA );  
      }  
    }
    

Log in to reply