How to expose an object function done in C++



  • On 08/07/2016 at 03:03, xxxxxxxx wrote:

    User Information:
    Cinema 4D Version:   R17 
    Platform:   Windows  ;   
    Language(s) :   C.O.F.F.E.E  ;  C++  ;   PYTHON  ;

    ---------
    I've made my own kind of object and it is working fine, now I wanna to expose a function from my object to be called from Python and Coffee.

    Is there any example?

    Thank you in advance.



  • On 08/07/2016 at 06:02, xxxxxxxx wrote:

    Howdy,

    Well, you can create Coffee functions with the SDK's Coffee class, but I don't know if you can do it for Python, as I don't see a Python class in the SDK documentation.

    EDIT:
    Take a look at this thread:
    https://plugincafe.maxon.net/topic/4558/4341_extending-coffee-with-c&KW=AddGlobalFunction

    Adios,
    Cactus Dan



  • On 08/07/2016 at 08:55, xxxxxxxx wrote:

    Sorry I didn't explain properly I mean a function relative to my object, never used COFFEE but I mean something like

    MyObject.saveDataTo ("c:\\whatever.txt")

    being MyObject a instance of an object of my own class.



  • On 08/07/2016 at 09:30, xxxxxxxx wrote:

    Howdy,

    Well, you could do something like this:

    class MyObject : public ObjectData
    {
    public:
    	Bool setting;
    	
    	Bool GetSetting(void);
    	
    	// other class functions...
    };
      
    Bool GetSetting(void)
    {
    	return setting;
    }
      
    // coffee function to access object setting
    void MyObjectGetSetting(Coffee *cof, Value*&sp, Int32 argc)
    {
    	Bool res = false;
    	
    	if(argc == 1 && sp[argc-1].IsType(COFFEE_VTYPE_OBJECT))
    	{
    		GeData gData;
    		GeCoffeeValue2GeData(cof,&sp[argc-1],&gData);
    		BaseLink* bl = gData.GetBaseLink();
    		
    		BaseObject *op = static_cast<BaseObject*>(bl->GetLink(GetActiveDocument()));
    		if(op)
    		{
    			MyObject *mOp = op->GetNodeData();
    			if(mOp) res = mOp->GetSetting();
    		}
    	}
    	
    	GeCoffeeGeData2Value(cof, res, &sp[argc]);
    	sp += argc;
    }
    

    ... and then in your coffee script call it like this:

    var setng = MyObjectGetSetting(op);
    

    Is that similar to what you want to do?

    Adios,
    Cactus Dan



  • On 08/07/2016 at 09:48, xxxxxxxx wrote:

    This part ...

    _// coffee function to access object setting_
    
     _void MyObjectGetSetting(Coffee \*cof, Value\* &sp, Int32 argc)_
    
     _{_
    
     _	 Bool res = false;_
    
     _	_
    
     _	 if(argc == 1 && sp[argc-1].IsType(COFFEE_VTYPE_OBJECT))_
    
     _	 {_
    
     _		 GeData gData;_
    
     _		 GeCoffeeValue2GeData(cof,&sp[argc-1],&gData);_
    
     _		 BaseLink\* bl = gData.GetBaseLink();_
    
     _		_
    
     _		 BaseObject \*op = static_cast<BaseObject\*>(bl->GetLink(GetActiveDocument()));_
    
     _		 if(op)_
    
     _		 {_
    
     _			 MyObject \*mOp = op->GetNodeData();_
    
     _			 if(mOp) res = mOp->GetSetting();_
    
     _		 }_
    
     _	 }_
    
     _	_
    
     _	 GeCoffeeGeData2Value(cof, res, &sp[argc]);_
    
     _	 sp += argc;_
    
     _}_
    

    ...is inside my object (C++) or this is pure COFFEE?

    Thank you so much



  • On 08/07/2016 at 10:03, xxxxxxxx wrote:

    Howdy,

    That would be a function outside of your object class. You can add it to the bottom of your myobject.cpp file, and declare it in the main header file.

    Then in your PluginStart() function in your main.cpp file, you add your function to the coffee master like this:

    Coffee *cofM = GetCoffeeMaster();
    if(cofM)
    {
    	cofM->AddGlobalFunction("MyObjectGetSetting",MyObjectGetSetting);
    }
    

    Adios,
    Cactus Dan



  • 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