How to expose an object function done in C++



  • On 15/07/2016 at 07:13, xxxxxxxx wrote:

    Hi!

    Sorry for the late reply. It's possible to define new Python functions using the library defined in c4d_libs/lib_py.h
    This library definitions are not documented currenly and many are meant for internal use only.

    To expose a Python function a new module has to be initialized with PythonLibrary::InitModule().
    Here is some code:

    // Global table of c4d.extendpyapi functions for PythonLibrary::InitModule()
    static maxon::BaseArray<PythonMethodData>* g_extendpyapi_functions = nullptr;
      
    // Prints "Hello from Python!" to the console
    static _PyObject *extendpyapi_HelloPython(_PyObject *self)
    {
      PythonLibrary pylib;
      GePrint("Hello from Python!");
      return pylib.ReturnPyNone();
    }
      
    // Initializes c4d.extendpyapi module
    void InitExtendPython()
    {
      PythonLibrary pylib;
      
      // Allocates c4d.extendpyapi module functions
      g_extendpyapi_functions = NewObjClear(maxon::BaseArray<PythonMethodData>);
      if (g_extendpyapi_functions == nullptr)
        return;
      
      // Initializes c4d.extendpyapi module functions
      g_extendpyapi_functions->Resize(2);
      PythonMethodData* moduleFunctions = g_extendpyapi_functions->GetFirst();
      moduleFunctions[0].Init("HelloPython", (PyFn)extendpyapi_HelloPython, PYFN_FLAGS_NOARGS, "HelloPython() - Extend Python API");
      moduleFunctions[1].Init(String(), nullptr, PYFN_FLAGS_0, String()); // Last dummy element!
      
      // Initializes c4d.extendpyapi module
      if (pylib.InitModule("c4d.extendpyapi", moduleFunctions, "Extend Python API"))
        GePrint("\'c4d.extendpyapi\' module successfully initialized");
    }
     
    // Frees the module global function table
    void FreeExtendPython()
    {
      DeleteObj(g_extendpyapi_functions);
    }
    

    InitExtendPython()/FreeExtendPython() have to called from within PluginMessage() C4DPL_PYINITTYPES/C4DPL_PYFINALIZE:

    Bool PluginMessage(Int32 id, void* data)
    {
      case C4DPL_PYINITTYPES:
      {
        InitExtendPython();
        return true;
      }
      case C4DPL_PYFINALIZE:
      {
        FreeExtendPython();
        return true;
      }
      
      return false;
    }
    

    To pass basic parameters like string, integer or float use PythonLibrary::ParseTupleAndKeywords() like shown in the following code snippet:

    static _PyObject *extendpyapi_PassParameters(_PyObject *self, _PyObject *args, _PyObject *keywords)
    {
      PythonLibrary pylib;
     
      String str;
      Int32 integer = 0;
      Float real = 0.0f;
      
      const Char *kwlist[] = {"str", "integer", "real", nullptr};
      if (!pylib.ParseTupleAndKeywords(args, keywords, "$if", kwlist, &str, &integer, &real))
        return nullptr;
      
      if (str.Content())
        GePrint("Parameter str: " + str);
      
      GePrint("Parameter integer: " + String::IntToString(integer));
      GePrint("Parameter real: " + String::FloatToString(real));
     
      return pylib.ReturnPyNone();
    }
    

    If a function accepts parameters pass PYFN_FLAGS_KEYWORDS instead of PYFN_FLAGS_NOARGS for the Init() call.

    Finally, to parse a GeData/baselist object use 'G' format:

    static _PyObject *extendpyapi_PassBaseList(_PyObject *self, _PyObject *args, _PyObject *keywords)
    {
      PythonLibrary pylib;
     
      GeData data;
     
      const Char *kwlist[] = {"baselist", nullptr};
      if (!pylib.ParseTupleAndKeywords(args, keywords, "G", kwlist, &data))
        return nullptr;
     
      if (data.GetType() == DA_ALIASLINK)
      {
        BaseLink* link = data.GetBaseLink();
        if (link == nullptr)
          return pylib.ReturnPyNone();
     
        BaseList2D* baseList = link->GetLink(nullptr);
        if (baseList == nullptr)
          return pylib.ReturnPyNone();
     
        GePrint("Passed baselist: " + baseList->GetName());
      }
     
      return pylib.ReturnPyNone();
    }
    


  • On 15/07/2016 at 10:15, xxxxxxxx wrote:

    Howdy,

    Wow, thanks Yannick!

    I've been working on making aspects of my plugins scriptable through cofffee and wanted to included python scriptability, too. This gives me a place to start. 😉

    Adios,
    Cactus Dan



  • On 28/07/2016 at 16:15, xxxxxxxx wrote:

    Howdy,

    OK, I got the passing a BaseList as a prameter with the "G" format, but suppose I want to pass both a BaseList and an interger or real in the same function? How would that be formatted?

    Adios,
    Cactus Dan



  • On 29/07/2016 at 02:37, xxxxxxxx wrote:

    Originally posted by xxxxxxxx

    OK, I got the passing a BaseList as a prameter with the "G" format, but suppose I want to pass both a BaseList and an interger or real in the same function? How would that be formatted?

    Like the argument parsing in the function example extendpyapi_PassParameters() I posted above, concatenate the characters for each parameter.
    For instance:

    GeData data;
    Int32 integer = 0;
    Float real = 0.0f;
      
    const Char *kwlist[] = {"baselist", "integer", "real", nullptr};
    if (!pylib.ParseTupleAndKeywords(args, keywords, "Gif", kwlist, &data, &integer, &real))
        return nullptr;
    


  • On 29/07/2016 at 04:53, xxxxxxxx wrote:

    Howdy,

    DOH! It's the strings that always throw me off when they're used for anything other than printing.
    But I get it now:
    "$" = string format
    "i" = integer format
    "f" = float format
    "G" = GeData format

    Possibly if the example had the float first like "$fi" then I might have been able to figure that out. But, with it the other way around, no matter how many times I looked at it, I still saw the word "if", and got confused. 😊

    Thanks for the clarification.

    Adios,
    Cactus Dan



  • On 29/07/2016 at 05:53, xxxxxxxx wrote:

    You're welcome :)

    "C" can also be used to parse BaseContainer* too (note only pointer to BaseContainer, initialize with nullptr before calling ParseTupleAndKeywords()).



  • On 29/07/2016 at 06:07, xxxxxxxx wrote:

    Howdy,

    Thanks. Is there a complete list of formatting letters that you could post?

    Also, is there a way to add global symbols in python like in coffee, and if so, can you post an example?

    Adios,
    Cactus Dan



  • On 26/04/2017 at 00:05, xxxxxxxx wrote:

    Hello,

    I know this is a bit older topic but the last question from Cactus Dan was not answered and I also need it. Especially I need to transfer BaseObject to and from python script.

    So again, we just need a table of all formatting letters and types that Cinema python binding API uses.

    Just for completeness, python build-in formatting letters seems to work and are available here: https://docs.python.org/2.7/c-api/arg.html?highlight=parsetupleandkeywords#c.PyArg_ParseTupleAndKeywords



  • On 26/04/2017 at 02:38, xxxxxxxx wrote:

    Hi Miro,

    Originally posted by xxxxxxxx

    I know this is a bit older topic but the last question from Cactus Dan was not answered and I also need it. Especially I need to transfer BaseObject to and from python script.

    To parse a BaseList based object, use 'G' format character as show in my above code snippet's function  extendpyapi_PassBaseList(). The object can be casted to the most interesting type after retrieving it.

    Originally posted by xxxxxxxx

    So again, we just need a table of all formatting letters and types that Cinema python binding API uses.

    PythonLibrary::ParseTupleAndKeywords() accepts the following format characters for Cinema 4D C++ API classes:
    - $: String
    - %: Filename
    - M: Matrix
    - V: Vector
    - Q: Quaternion
    - C: BaseContainer*
    - G: GeData (versatile: can parse a BaseList, a custom data, a time, etc.)
    - T: BaseTime
    - X: BaseThread

    Originally posted by xxxxxxxx

    Just for completeness, python build-in formatting letters seems to work and are available here: https://docs.python.org/2.7/c-api/arg.html?highlight=parsetupleandkeywords#c.PyArg_ParseTupleAndKeywords

    PythonLibrary::ParseTupleAndKeywords()also accepts the following standard format characters: b, B, j, h, i, I, v, l, L, r, f, d, c



  • On 27/04/2017 at 23:34, xxxxxxxx wrote:

    That's perfect! Thank you.



  • On 24/09/2017 at 13:07, xxxxxxxx wrote:

    Hello all,

    It seems to be that i's only possible to make a python extension library as a plugin. Which plugin then is best suited? A command plugin, I would guess, or is there another plugin that loads completely automatic at startup?

    I need something that is always available, so that I don't have to think about loading the library. Much like the c++ example here: page_creating_libraries.html

    Regards,

    Hermen



  • On 25/09/2017 at 01:29, xxxxxxxx wrote:

    Hello,

    a "_ plugin_" is just a custom module loaded by Cinema. So a "plugin" does not need to include anything e.g. a command data extension. You only have to implement PluginStart()/PluginMessage()/PluginEnd(), see Plugin Functions Manual. In such a PluginMessage () function you have to register your Python extension as shown above.

    best wishes,
    Sebastian



  • On 25/09/2017 at 02:53, xxxxxxxx wrote:

    Aha!
    Now I understand, thanks for clearing that up!

    regards,

    Hermen


Log in to reply