• 1/1

Adding New Functions Written in C++ to MAXScript

Posted by Christopher Diggins, 16 September 2010 8:00 pm

If you have ever seen the MAXScript SDK documentation, you will realize that it is terribly tricky and convoluted. However for most of things you may want to do with it, you can instead use the Function Publishing API. For example a common task that people want to do with MAXScript is to add new global functions. In this article I describe how you can add a new global function to MAXScript. 

Consider the example of adding a  factorial  function. To do this you can do  the following steps:

  1. Create a new GUP plug-in that does nothing. 
  2. Declare a new class that derives from FPInterfaceDesc.
  3. Declare a constant Interface_ID (use unique numbers generated using gencid.exe)
  4. Declare a constant function id for each exposed function.
  5. In the constructor's member initialization list, pass the appropriate arguments to the FPInterfaceDesc constructor. See the documentation, and the example below. 
  6. In the constructor body call FPInterfaceDesc::AppendFunction() for each  function you want to expose.
  7. Create a function map, with an entry for each exposed function. 
  8. Implement the exposed functions as static member functions, or as non-member functions. 
  9. Instantiate a static instance of FPInterfaceDesc in your plug-in.

And that's it. 

The following code demonstrates the steps: 

class FactorialStaticInterface : public FPInterfaceDesc
{
public:

    static const Interface_ID id;
    
    enum FunctionIDs
    {
        factorial,
    };

    FactorialStaticInterface()
        : FPInterfaceDesc(id, _M("IFactorial"), 0, NULL, FP_CORE, end)
    {
        AppendFunction(
            factorial,      /* function ID */
            _M("fact"),     /* internal name */
            0,              /* function name string resource name */ 
            TYPE_INT,       /* Return type */
            0,              /* Flags */
            1,              /* Number of arguments */
                _M("x"),    /* argument internal name */
                0,          /* argument localizable name string resource id */
                TYPE_INT,   /* arg type */
            end); 
    }

    static int Factorial(int x) { 
        return x <= 1 ? 1 : x * Factorial(x - 1); 
    } 

    BEGIN_FUNCTION_MAP
        FN_1(factorial, TYPE_INT, Factorial, TYPE_INT)
    END_FUNCTION_MAP
};

const Interface_ID FactorialStaticInterface::id = Interface_ID(0x4bd4297f, 0x4e8403d7);
static FactorialStaticInterface factorial;

 

5 Comments

Christopher Diggins

Posted 20 September 2010 1:27 pm

This is very helpful Martin. Thank you. I apologize for the shortcomings. I was pressed for time on Friday.

1.

FPInterfaceDesc(
id, /* An Interface_ID that uniquely identifies the interface being exposed */
_M("IFactorial"), /* The name of the interface as exposed to 3ds Max */
0, /* A resource ID for an entry in the string table that desceribes the interface */
NULL, /* A pointer to the ClassDesc (leave it as NULL for static/core interfaces */
FP_CORE, /* Identifies the kind of interface (core interface in this case */
end /* Identifies the end of a variable argument list (necessary) */
)

2. To use it in MAXScript

IFactorial.fact 5
>> 120

Now make a global variable:

global fact = IFactorial.fact
>> fact()
fact 5
>> 120

3. We want to move away from the MAXScript SDK is that it very hard to use, very poorly documented, and for the most part redundant. IMHO it was a mistake for it to have been exposed as an "API" in the first place. It is simply an exposure pf the internals of our scripting engine. By focusing on a single relatively easy to understand API (e.g. the Function Publishing API) we have a better hope of doing a good job in documentation and support.

That said, there are handful of key parts of the MAXScript SDK that we will have to containue to document and support nonetheless (as you point out in number 4) to make the FP API more usable.

4. Great ideas! We'll need to cover "mappable" collections and MAXScript functions as arguments.

5. Also a good idea. I'll keep this in mind.

Yannick Puech

Posted 21 September 2010 8:33 am

The Function Publishing API is so much easier to use than the MaxScript SDK. The arguments passed to functions and returned are converted automatically by the system. You can also declare properties and their getter/setter handler. Note that the classes exposed with the FP API are also accessible by C++ plugins.
Also it's better to put functions in a class than adding global functions. And declaring a struct with the Maxscript SDK is such a pain...

With the Function Publishing API you don't need to deal with MAXScript memory allocation. It's all handled by the system because the functions can be called from MaxScript and C++. You just have to declare carefully the arguments type and return type of the functions.

spacefrog

Posted 8 September 2011 1:51 am

Is it possible to expose C++ functions to Maxscript this way, which are expecting a VARIABLE amount of arguments ? So that those both calls would work in maxscript eg: "myinterface.myfunc 10" and "myinterface.myfunc 10 20"
Or is Maxscript ALWAYS expecting a fixed ( as specified in the FP descriptor ) amount and type(s) of argument(s) ?

I looked up the FN_VA macro and it should be exactly what i want, but i couldn't figure out what to enter in the FP descriptor , where you have to list the argument types and counts of the published functions ( eg. TYPE_INT etc... )

an answer would be very helpfull... thanks

spacefrog

Posted 8 September 2011 2:01 am

@Martin Breidt:
Just to answer that part 4 about passing arrays to and fro maxscript via the FP mechanisms
AFAIK You can specify arrays in the function descriptor ( the AppendFunction call in the above example ) using the various TYPE_*_TAB defines, eg: TYPE_INT_TAB would allow you to pass an integer array. You can even pass by reference ( meaning modifying an existing maxscript array ) by using the ByReference defines : eg TYPE_INT_TAB_BR
( _BR = by reference ) which would pass the existing array without copying it ...

I did not really test this, but at least maxscript is telling me that that specific argument is an "in and out parameter" if i use the by reference define. This should be a great and fast way of passing large arrays to C++ functions for direct manipulation

spacefrog

Posted 8 September 2011 3:30 am

Okay:
found the way to have a variable number of arguments being passed to FP exposed functions from 'maxscript
In the Functiondescriptor's parameter specification, you have to add a "f_keyArgDefault", followed by a default value ( of same type as the argument).
This creates a "key" argument in maxscript, which are optional and have to be passed using the "key:arg" syntax

eg. for the above example to make the argument "x" optional and default to 1 ( would not make much sense, but anyways...), one would write:

...
_M("x"), /* argument internal name */
0, /* argument localizable name string resource id */
TYPE_INT, /* arg type */
f_keyArgDefault, /* turns the arguement to a "key" argument, which are optional in maxscript
1, /* if key arguement is not given when called from MXS, use the default of 1
end);

Add Your Comment

You must be logged in to post a comment.

Please only report comments that are spam or abusive.