Regarding DescIDs and Set/GetParameter



  • On 01/06/2015 at 05:44, xxxxxxxx wrote:

    User Information:
    Cinema 4D Version:    
    Platform:      
    Language(s) :

    ---------
    Hey! Is there a way to work around the DescID simply ignoring a DescLevel with ID zero and
    everything that follows? Why is that? 😢 I have to add 1 to everything to make sure that no
    zero is passed in to a DescLevel...

    DescID descid(MY_PLUGIN_DATA_GET, index + 1, subindex + 1);
    

    And in my GetDParameter(), i have to subtract the 1 again.

    Int32 index = descid[1].id - 1;
    Int32 subindex = descid[2].id - 1;
    


  • On 01/06/2015 at 05:54, xxxxxxxx wrote:

    Oh and is there a way to make GetParameter() not do any checks with the Description and stuff? It seems
    like when I use something like DescID(4005, 1, 1)  it will first call GetDParameter() with DescID(4005)  and
    then DescID(4005, 1).

    Thanks!

    Update : Maybe this is a Python thing? I set the parameter from Python to test the handling of the
    value in GetDParameter(). Here's some debug output maybe so you can see what happens:

    [OK] Points[c4d.PROCEDURAL_CHANNEL_PARAM_DATA, 2]
    PROCEDURAL_CHANNEL_PARAM_DATA: DescID({4005, 0, 0})
    PROCEDURAL_CHANNEL_PARAM_DATA: DescID({4005, 23, 0})
    PROCEDURAL_CHANNEL_PARAM_DATA: DescID({4005, 23, 0},{2, 23, 0})
    Vector(-5.207, 145.789, 20)
    

    Currently, in GetDParameter(), I accept DescIDs that have not the required depth and simply
    use a default value for the missing levels instead. Also , it would be nice if GetDParameter() was
    really only called once due to performance reasons.

    	Int32 GetIndexFromDescID(const DescID& id) const
    	{
    		CriticalAssert(mItemLength >= 1 && mItemLength <= PROCEDURAL_CHANNEL_MAXITEMLENGTH);
    		const Int32 depth = id.GetDepth();
    		if (depth > 3) return -1;
    		Int32 elementIndex = (depth >= 2 ? id[1].id - 1 : 0);  // There can not be a DescLevel with id zero,
    		Int32 itemIndex = (depth >= 3 ? id[2].id - 1 : 0);     // we expect the index to be offset by 1.
    		if (elementIndex < 0 || elementIndex >= mCount) return -1;
    		if (itemIndex < 0 || itemIndex >= mItemLength) return -1;
    		return elementIndex * mItemLength + itemIndex;
    	}
    


  • On 01/06/2015 at 06:43, xxxxxxxx wrote:

    [SOLVED]

    Another question: Is there a way to prevent SetDParameter() to call C4DAtom::SetDirty(DIRTYFLAGS_DATA)? 😠

    Update : I've worked around this by hooking the Cinema 4D API.

    /// **************************************************************************
    /// **************************************************************************
    class ChannelApiHook
    {
    public:
      /// This hooked API call ensures that, for a Channel Node, a DescID with \var
      /// PROCEDURAL_CHANNEL_PARAM_DATA as the first ID will be routed \em directly
      /// to the underlying \ref NodeData and that \c SetDirty(DIRTYFLAGS_DATA)
      /// will \em not be called on the node for this parameter!
      Bool SetParameter(const DescID& id, const GeData& data, DESCFLAGS_SET flags);
      
      static Bool Register();
    private:
      static decltype(C4DOS.At->SetParameter) s_SetParameter;
    };
      
    DECLMEMBER(ChannelApiHook, s_SetParameter);
      
    /// **************************************************************************
    /// **************************************************************************
    Bool ChannelApiHook::SetParameter(const DescID& id, const GeData& data, DESCFLAGS_SET flags)
    {
      C4DAtom* atom = reinterpret_cast<C4DAtom*>(this);
      if (atom->GetType() == PROCEDURAL_CHANNEL_ID && id[0].id == PROCEDURAL_CHANNEL_PARAM_DATA) {
        GeListNode* node = static_cast<GeListNode*>(atom);
        NodeData* nodeData = node->GetNodeData();
        NODEPLUGIN* table = C4DOS.Bl->RetrieveTableX(nodeData, 0);
        if (nodeData && table) {
          return (nodeData->*table->SetDParameter)(node, id, data, flags);
        }
      }
      return (atom->*s_SetParameter)(id, data, flags);
    }
      
    /// **************************************************************************
    /// **************************************************************************
    Bool ChannelApiHook::Register()
    {
      s_SetParameter = C4DOS.At->SetParameter;
      C4DOS.At->SetParameter = (decltype(s_SetParameter)) &ChannelApiHook::SetParameter;
      return true;
    }
    


  • On 01/06/2015 at 07:49, xxxxxxxx wrote:

    Hello,

    could you please provide us with some code or some context that shows what are you doing and what exactly fails?

    Best wishes,
    Sebastian



  • On 01/06/2015 at 08:00, xxxxxxxx wrote:

    I'll try to explain it, first, and if it will still be helpful, I can create a stripped down version of the
    code tomorrow :-)

    The Dirty-Count problem is sort of solved. Remaining:

    • DescID() does not accept zero-ID DescLevels
    • GetDParameter() is called multiple times for a single parameter

    I guess the first is a limitation of the current implementation of the DescID class. There might have been
    some reason to it, but if it won't break anything, I would greatly appreciate if this would be changed some day. To give just a little more detail, the following is the problem:

    DescID(3, 0) == DescID(3) && DescID(3, 0 9) == DescID(3);
    DescID(3, 0).GetDepth() == 1;
    

    The latter appears when using the op[id] = value  Syntax in Python, but I have yet to test it using
    C4DAtom::GetParameter()  from C++. If I use an ID that has multiple levels, like DescID(4005, 9, 2) ,
    NodeData::GetDParameter()  is not called once  for the ID, but three times. And if during any of those
    three calls, one does fail to retrieve a value, the others will never be invoked. What you in
    GetDParameter() is

    • DescID(4005)
    • DescID(4005, 9)
    • DescID(4005, 9, 2)

    I want to use a DescID as an array subscript for the data in my plugin and return the data at that index.
    The format I use currently is DescID(4005, row + 1, column + 1). The thing here is that before I get
    passed the DescID that I expect, I get two "invalid" DescIDs. And I have to return a valid value for them,
    otherwise I will never get the third, correct  DescID in NodeData::GetDParameter().

    Mainly this is a performance bottleneck and secondly, I want it to be an error to use a DescID that is
    ill formed (so only DescID(4005)  or DescID(4005, 9)  should not succeed in retrieving a value, but
    DescID(4005, 9, 2)  should).

    Thanks for your help,
    -Niklas



  • On 01/06/2015 at 11:25, xxxxxxxx wrote:

    Hello,

    abusing the DescID in such ways seems to be a strange hack; what are you really trying to do? It is a feature of Python that GetParameter() is called multiple times when the DTYPE is not defined. I cannot confirm that a DescID with zeros is ignored. Without code it's hard so say anything.

    Best wishes,
    Sebastian



  • On 01/06/2015 at 12:26, xxxxxxxx wrote:

    Hi Sebastian,

    it doesn't feel too abusing to me. Basically, my plugin is a data container. I want the data to be read
    and writable via GetParameter() because I will not need to write additional code for Python in order
    to read and write data to the plugin.

    It is a feature of Python that GetParameter() is called multiple times when the DTYPE is not defined.

    Thanks, I'll try it with specifying the datatype!

    I cannot confirm that a DescID with zeros is ignored. Without code it's hard so say anything.

    It might as well only be Python behaviour, maybe?

    c4d.DescID(c4d.DescLevel(3), c4d.DescLevel(0), c4d.DescLevel(9)).GetDepth()
    

    Thanks,
    Niklas



  • On 02/06/2015 at 01:27, xxxxxxxx wrote:

    Hello,

    I still cannot confirm the described behavior. Please provide more context or code.

    From what you tell I'm wondering why you are not simply using the parameter ID itself as your array subscript. This parameter ID could be calculated on the "coordinates" you use like the index of a pixel in an image with a given width and height.

    Best wishes,
    Sebastian



  • On 02/06/2015 at 01:49, xxxxxxxx wrote:

    Hi Sebastian,

    what does it print out for your if you run this line in the console? Does it print 1? That is what it
    prints for me. And that means the second and third level of the DescID are simply ignored. Or am I
    missing something?

    c4d.DescID(c4d.DescLevel(3), c4d.DescLevel(0), c4d.DescLevel(9)).GetDepth()
    

    From what you tell I'm wondering why you are not simply using the parameter ID itself as your array subscript. This parameter ID could be calculated on the "coordinates" you use like the index of a pixel in an image with a given width and height.

    I'm not sure if I understand you correctly. You mean a single ID that is decomposed in two numbers
    that is then used as array subscript?

    Please provide more context or code.

    This is my GetDParameter() and SetDParameter() implementation:

    /// **************************************************************************
    /// **************************************************************************
    Bool ChannelPlugin::GetDParameter(
      GeListNode* node, const DescID& id, GeData& data, DESCFLAGS_GET& flags)
    {
      switch (id[0].id) {
        // ...
        // ...
        case PROCEDURAL_CHANNEL_PARAM_DATA:
          // If this parmaeter is accessed, a second description ID level
          // must be present representing the index of the element to be
          // accessed. Subtract by one because a zero DescLevel does not exist.
          if (GetElement(id, data)) {
            flags |= DESCFLAGS_GET_PARAM_GET;
            return true;
          }
          else {
            const String name = static_cast<BaseObject*>(node)->GetName();
            GePrint("[" + name + "]: Get PARAM_DATA: invalid DescID " + nr::ToString(id));
          }
          return false;
        case PROCEDURAL_CHANNEL_UI_MEMINFO:
          data.SetString(mData.ToString());
          flags |= DESCFLAGS_GET_PARAM_GET;
          return true;
      }
      return SUPER::GetDParameter(node, id, data, flags);
    }
      
    /// **************************************************************************
    /// **************************************************************************
    Bool ChannelPlugin::SetDParameter(
      GeListNode* node, const DescID& id,
      const GeData& data, DESCFLAGS_SET& flags)
    {
      switch (id[0].id) {
        // ...
        // ...
        case PROCEDURAL_CHANNEL_PARAM_DATA:
          if (mLocked) return false;
          if (SetElement(id, data)) {
            flags |= DESCFLAGS_SET_PARAM_SET;
            return true;
          }
          else {
            const String name = static_cast<BaseObject*>(node)->GetName();
            GePrint("[" + name + "]: Set PARAM_DATA: invalid DescID " + nr::ToString(id));
          }
          return false;
      }
      return SUPER::SetDParameter(node, id, data, flags);
    }
    

    Here's what GetElement() and SetElement() looks like (also class members).
    The overloads of these methods that are called are not of interest.

     inline Bool GetElement(const DescID& id, GeData& data) const
      {
        Int32 index = GetIndexFromDescID(id);
        if (index >= 0) return GetElement(index, data);
        return false;
      }
      
      inline Bool SetElement(const DescID& id, const GeData& data) const
      {
        Int32 index = GetIndexFromDescID(id);
        if (index >= 0) return SetElement(index, data);
        return false;
      }
      
      /// Computes a linear index from a \class DescID that attempts to access
      /// a sub-element of the channel. Returns a negative value if the DescID
      /// is invalid or out of range.
      Int32 GetIndexFromDescID(const DescID& id) const
      {
        CriticalAssert(mItemLength >= 1 && mItemLength <= PROCEDURAL_CHANNEL_MAXITEMLENGTH);
        const Int32 depth = id.GetDepth();
        if (depth > 3) return -1;
        Int32 elementIndex = (depth >= 2 ? id[1].id - 1 : 0);  // There can not be a DescLevel with id zero,
        Int32 itemIndex = (depth >= 3 ? id[2].id - 1 : 0);     // we expect the index to be offset by 1.
        if (elementIndex < 0 || elementIndex >= mCount) return -1;
        if (itemIndex < 0 || itemIndex >= mItemLength) return -1;
        return elementIndex * mItemLength + itemIndex;
      }
    

    Best regards,
    Niklas



  • On 02/06/2015 at 10:37, xxxxxxxx wrote:

    Hello,

    that GetDepth() returns 1 seems to be no malfunction but intended behavior.

    As said, I guess the best approach would be to use the actual parameter ID as the index. Based on that index you could create you coordinates, like here.

    Best wishes,
    Sebastian


Log in to reply