GvOperatorData Examples?



  • On 11/07/2016 at 19:25, xxxxxxxx wrote:

    User Information:
    Cinema 4D Version:   R17 
    Platform:    Mac  ;  
    Language(s) :     C++  ;

    ---------
    Are there any examples kicking around for creating GvOperatorData based plugins?

    I'm interested in creating a bunch of tiny nodes for some stuff that I find myself forever cloning across a wide variety of projects (things like min/max nodes for floating point values, an atan2 node, hpb2matrix, etc).

    What I'm not entirely clear on is how a lot of things are supposed to work. For example, a min (or max) node will need multiple real inputs, since it determines the min (or max) of those values and passes it through to the output node. At the same time, atan2 only requires two inputs (Y and X) and nothing more. hpb2matrix would need three inputs (H, P, and B), with an optional fourth specifying the rotation order. Etc.

    I'm not entirely clear on how the GvOperatorData class handles all these things, and the plugincafe C++ project doesn't seem to have any examples of a custom Xpresso node at all. Are there any examples floating around out there? Just seeing the source code for something as simple as the existing Xpresso Math node or Matrix2HPB node would be extremely helpful.

    -CMPX



  • On 12/07/2016 at 13:11, xxxxxxxx wrote:

    My XPress-Effector plugin is open source, maybe that helps you a bit. I had a hard time figuring
    out all the GraphView SDK stuff. Don't judge me on the code though, it's from pretty long ago.

    https://github.com/nr-plugins/xpresso-effector



  • On 13/07/2016 at 19:26, xxxxxxxx wrote:

    Wow.

    That is nowhere near as simple as I thought it would be.

    Thanks for the example!

    -CMPX



  • On 15/07/2016 at 02:22, xxxxxxxx wrote:

    Hi,

    sorry, it took me a bit longer to come up with an example. It's not supposed to compete with Niklas' great stuff, but will eventually be added to the SDK examples.

    The .res file with a few explanations:

    CONTAINER Gvoperatordataexample
    {
      NAME Gvoperatordataexample;
      INCLUDE GVbase;  // use if no dynamic ports are involved
      
      GROUP ID_GVPROPERTIES
      {
        BOOL GVOPERATORDATAEXAMPLE_PROP_BOOL { }
      }
      GROUP ID_GVPORTS
      {
        // CREATEPORT [n]: Creates the port on node creation, port is added by default, n may be used with MULTIPLE
        // STATICPORT: User can not remove the port
        // EDITPORT: The port value can be edited in Attribute Manager, even if the port is not added to the node
        // PORTONLY: No parameter in Attribute Manager (overrides EDITPORT)
        // MULTIPLE: Multiple instances of the port can be added to the node
        // NEEDCONNECTION: Currently not used
        // NOTMOVEABLE: Currently not used
        LONG    GVOPERATORDATAEXAMPLE_INDEX         {INPORT; STATICPORT; CREATEPORT;}
        LONG    GVOPERATORDATAEXAMPLE_LONG_1        {INPORT; PORTONLY; NEEDCONNECTION; CREATEPORT;}
        LONG    GVOPERATORDATAEXAMPLE_LONG_2        {INPORT; EDITPORT;}
        VECTOR  GVOPERATORDATAEXAMPLE_VECTOR_MULTI  {INPORT; MULTIPLE;}
        LONG    GVOPERATORDATAEXAMPLE_OUT_LONG      {OUTPORT; STATICPORT; CREATEPORT;}
        VECTOR  GVOPERATORDATAEXAMPLE_OUT_VECTOR    {OUTPORT; STATICPORT; CREATEPORT;}
      }
    }
    

    A few private members used in this example:

    GvValuesInfo  _ports;       // Stores the input and output ports during a calculation
      
    Bool          _calcVector;  // To avoid reading property from BaseContainer during calculation
    Int32         _iterVal;     // used to store temporary result in case of iteration
    

    And these are all the function (and Alloc of course) you really need to implement:

    Bool GvOperatorDataExample::iCreateOperator(GvNode *bn)
    {
      BaseContainer* data = bn->GetOpContainerInstance();
      if (!data)
        return false;
      // Set general node parameters (these are located in the node's BaseContainer, not the OpContainer)
      bn->SetParameter(ID_GVBASE_COLOR, Vector(0.8, 0.8, 0.8), DESCFLAGS_SET_0); // set a title color
      // Init node properties (description params in ID_GVPROPERTIES) here
      data->SetBool(GVOPERATORDATAEXAMPLE_PROP_BOOL, true);
      
      return SUPER::iCreateOperator(bn);
    }
      
    // Use for custom selection or assuring a certain order of values/ports in GvBuildValuesTable()
    // The values info table will be sorted and indexed like this array (see the enums below, defining the indexes for later use in Calculate())
    static Int32 inputIds[] = { GVOPERATORDATAEXAMPLE_INDEX, GVOPERATORDATAEXAMPLE_LONG_1, GVOPERATORDATAEXAMPLE_LONG_2, GVOPERATORDATAEXAMPLE_VECTOR_MULTI, 0 };
    enum { IDX_GVOPERATORDATAEXAMPLE_INDEX, IDX_GVOPERATORDATAEXAMPLE_LONG_1, IDX_GVOPERATORDATAEXAMPLE_LONG_2, IDX_GVOPERATORDATAEXAMPLE_VECTOR_MULTI };
    enum { IDX_GVOPERATORDATAEXAMPLE_OUT_LONG, IDX_GVOPERATORDATAEXAMPLE_OUT_VECTOR };
      
    Bool GvOperatorDataExample::InitCalculation(GvNode *bn, GvCalc *calc, GvRun *run)
    {
      if (!GvBuildValuesTable(bn, _ports, calc, run, inputIds)) // or GV_EXISTING_PORTS or GV_DEFINED_PORTS instead of input_ids
        return false;
      
      BaseContainer* const bc = bn->GetOpContainerInstance();
      _calcVector = bc->GetBool(GVOPERATORDATAEXAMPLE_PROP_BOOL);
      _iterVal = 0; // reset temp storage for iterator path
      return true;
    }
      
    void GvOperatorDataExample::FreeCalculation(GvNode *bn, GvCalc *calc)
    {
      // Don't forget to free the values table(s) and dynamic data at the end of the calculation!
      GvFreeValuesTable(bn, _ports);
    }
      
    Bool GvOperatorDataExample::Calculate(GvNode *bn, GvPort *port, GvRun *run, GvCalc *calc)
    {
      Bool result = false;
      
      // First get all input values calculated
      // Note: In-values may also be calculated separately,
      //       for example if one needs only a few of them calculated depending on a mode parameter.
      //       In this case use _ports.in_values[idx]->Calculate()
      if (!GvCalculateInValuesTable(bn, run, calc, _ports))
        return false;
      
      // With multiple output ports, Calculate() may be called multiple times per calculation
      //   port == nullptr means all ports requested
      if (!port || (port->GetMainID() == GVOPERATORDATAEXAMPLE_OUT_LONG))
      {
        // Now get all ports needed for calculation
        // Note: Another option would be to do a switch (_ports.in_values[idxPort]->GetMainID()),
        //       be warned though, this won't work with ports defined with MULTIPLE
        GvPort* const portIdx = _ports.in_values[IDX_GVOPERATORDATAEXAMPLE_INDEX]->GetPort();
        GvPort* const portL1 = _ports.in_values[IDX_GVOPERATORDATAEXAMPLE_LONG_1]->GetPort();
        GvPort* const portL2 = _ports.in_values[IDX_GVOPERATORDATAEXAMPLE_LONG_2]->GetPort();
        // Ports defined with MULTIPLE are handled a bit differently, see a bit further down the vector output calculation
      
        if (!portIdx || !portL1 || !portL2)
        {
          GePrint("Error: There's a port missing for sum calculation");
          return false;
        }
      
        // Get port values
        Int32 idx, l1, l2;
        if (!portIdx->GetInteger(&idx, run))
          return false;
        if (!portL1->GetInteger(&l1, run))
          return false;
        if (!portL2->GetInteger(&l2, run))
          return false;
      
        // Do the sum calculation for the long output
        //if (run->IsIterationPath())
        //  GePrint("Node is connected to an iterator"); // maybe want to act special here
        _iterVal += (l1 + l2) * (idx + 1); // storing in a member variable to use the result in next iteration (if any)
        result = _ports.out_ports[IDX_GVOPERATORDATAEXAMPLE_OUT_LONG]->SetInteger(_iterVal, run);
        if (!result)
        {
          GePrint("Error: Failed to set sum output port");
          return false;
        }
      }
      
      if (!port || (port->GetMainID() == GVOPERATORDATAEXAMPLE_OUT_VECTOR))
      {
        // Now turn to the MULTIPLE ports and calculate the vector output
        Vector vecResult = Vector(42.0);
        if (_calcVector)
        {
          GvValue* const valMulti = _ports.in_values[IDX_GVOPERATORDATAEXAMPLE_VECTOR_MULTI];
          if (valMulti)
          {
            Int32 numMultiPorts = valMulti->NrOfPorts();
      
            vecResult = Vector(0.0);
            for (Int32 idxVal = 0; idxVal < numMultiPorts; ++idxVal)
            {
              // In this example GvCalculateInValuesTable() at the beginning already did the work,
              // but in case in-values are requested separately, one would do it like so:
              //if (!valMulti->Calculate(bn, GV_PORT_INPUT, run, calc, idxVal))
              //  return false;
              Vector vec;
              if (!valMulti->GetPort(idxVal)->GetVector(&vec, run))
                return false;
              vecResult += vec;
            }
            vecResult = vecResult / numMultiPorts;
          }
        }
        result = _ports.out_ports[IDX_GVOPERATORDATAEXAMPLE_OUT_VECTOR]->SetVector(vecResult, run);
        if (!result)
        {
          GePrint("Error: Failed to set vector output port");
          return false;
        }
      }
      
      if (!result)
        GePrint("Error: Either got calculation request for an unknown out port or failed to set an output!");
      return result;
    }
    

    Note: AddToCalculationTable() and SetRecalculate() are not needed for "normal" operators.
    AddToCalculationTable() is used to have your operator being calculated regardless of any output port requests.
    SetRecalculate() is mainly used for iterators and TP operators.



  • On 15/07/2016 at 05:38, xxxxxxxx wrote:

    Awesome!

    Thank you both for the great examples!

    -CMPX


Log in to reply