3ds max sdk .NET


I’m trying out 3dsmaxsdk .NET, is there anyone who would be interested?
Due to lack of documentation it looks more like cryptoanalysis but I already have some results.

for beginning:
my notes
eventLister - testing tool for callback and events

Do you have any own knowledge of .net sdk? Want to share?

Links, github repos, pieces of code are welcome.


Hello This is ammazing!
We are trieng to write 3d max listener. We need to listen all we can.
Could you help with writeing such Listener?

Do you know, How to get the the object properties from C# in Visual studio?


What exactly do you expect? For catching 3dsmax callbacks and events you can use:
a/ pure c# solution - clone this https://github.com/pavel-mxsf/EventLister , i thing it’s a good start point
b/ create public class in c#, compile to .dll, load with maxscript (dotnet.loadassembly “xxx.dll”), create instance of your dotnet class in maxscript (obj = dotnetobject “namespace.classname”) and use maxscript callbacks (see General Event Callback Mechanism in help) and Node Event System to call methods on your obj.

to get informations about objects in case A - it’s possible to get IINode objects from callbacks parameters (and see ObjRef property on inode - there are class specific things)

for B - maxscript way you can pass numbers, strings, booleans etc to dotnet so some wrapper should be used. or pass object .handle as parameter and find that object in dotnet with Autodesk.Max.IINode GetINodeByHandle(uint handle)

FYI “attach to process” in VS and debugger works - it helps a lot (breakpoints, watches). (you need to attach to 3dsmax.exe)


I like the idea very much.

  • Comparing class_ID
  • Retrieving values from an mxs call (fpvalue)

// Comparing class_ID
// Assuming "node" is a IINode value

if (node.ObjectRef.SuperClassID == SClass_ID.Camera || node.ObjectRef.SuperClassID == SClass_ID.Light)

// Retrieving values from an mxs call, here we're retrieving nodes
// See Visual Studio Object explorer for other value types (NTab, ITab, STab, BTab, etc)

string mxs = "objects as array"; 
IGlobal Global = Autodesk.Max.GlobalInterface.Instance;
IFPValue sceneNodes = Global.FPValue.Create();
Global.ExecuteMAXScriptScript(mxs, false, sceneNodes);

for (int i = 0; i<sceneNodes.NTab.Count; i++)
    IINode node = sceneNodes.NTab[(IntPtr)i];


It’s a shame this thread is so dead…

I don’t know if anyone can help me. I need to retreive the values given by an IntPtr value.More exactly:
for an IPolyLine, there are two properties that return an IntPtr value:

  • IntPtr Lengths (Cached lengths for each point.)
  • IntPtr Percents (Cached percentages for each point.)

How can I read/use/know what these Lengths or Percents are using this IntPtr value?

Thanks a lot.


You will probably need to Marshal those pointers into a .NET wrapper. But from my experience, not all the C++ SDK Classes have been fully ported to Autodesk.Max .NET SDK. Where are you getting this pointers from?



I have passed a shape by its handle to C#. There, I extract the first spline of the shape and convert it to IPolyLine.
This IPolyLine has these two properties or attributes (‘Percents’ and ‘Lengths’) that return an IntPtr pointer that I suppose points to a floatTab (ParamType2 = 2048 I think).
I need to read these float values, but I don’t know how to get them from these pointers (my knowledge of SDK and C# is just a few days practice).

Thanks a lot for your help, @dgsantana. Obrigado.


Ok, from the C++ SDK, this are trully float pointers, so to make them useful in C#, you can convert the IntPtr to a float array.

var lens = new float[pl.Verts];
Marshal.Copy(pl.Lenghts, lens, 0, pl.Verts);

EDIT: Forgot to say that that “pl” should be your IPolyLine.

I haven’t test this, but should be something on this lines.


Works like a charm! Thanks a lot, @dgsantana.
What’s the whole sense of the ‘Marshall’ class/instruction? All this is too hard for me.

By the way… I have found by chance another way, of course worse than yours:

            int numsegments = pl.Segments;
            IntPtr percents = pl.Percents;

            IFPValue percentsList = Autodesk.Max.GlobalInterface.Instance.FPValue.Create();
            percentsList.InitTab((ParamType2) 2048, 0);    // 2048 == floatTab
            percentsList.FTab.Append(numsegments, percents, 0);

            for (int i = 0; i < percentsList.FTab.Count; i++)
                float myPercent = percentsList.FTab[(IntPtr)i];


Great, glad it word out. The Marshal class is used to “communicate” between the unmanaged world (pointer world) and the .NET managed one. In C#, most people don’t know but it’s possible to write code similar to C, (the reason for the unsafe block and flag), using pointers, and bypassing the .NET type system, of course this should only be done for performance reasons or integration with some C/C++ code.


I’m one of these people!


Need help from experts…

After several days working with sdk.NET for manipulating meshes, I have found that I can’t index IBitArrays!!! It seems it’s a new bug appeared in 2013-2014 version.
If I can’t get what vertex or faces are selected, it’s the end. I can’t go on.

I’ve found a trick in this forum from Vincentt user: https://www.ephere.com/autodesk/max/forums/general/thread_2527.html
where he creates an extension ‘GetBit’ for IBitArrays.

But sadlely, I get a compiling error in line: “void* nativePtr = bitArray.NativePointer.ToPointer();” : there’s no ‘NativePointer’ definition for IBitArray

Anyone knows how to solve this or do I have to lose all these days of coding?
I can’t find any workaround to extract information from an IBitArray.


string s = ManagedServices.MaxscriptSDK.ExecuteStringMaxscriptQuery("(polyop.getvertselection selection[1] as array)as string");

Not sure if that fits your needs.
Maybe there’s a way to convert IBitarray to array or smth like it, i couldnt find.


Thanks Sergey.
Sadlely, the mesh is something manipulated inside the C# code. And it doesn’t belong to any node or editable_mesh or poly, so it hasn’t a ‘handleByAnim’ value neither to use it with a MaxScript call.
All I think about will lose all performance. It’s really a shame.


I am not sure, but it could be:


I think Handle was then renamed to NativePointer in newer Max versions.
Anyway, it is a mess to work with Bitarray in C#.


Hey Jorge! Thanks a lot! It works fine.
The problem is that, as far as I know, working with IBitArrays is a must. You can’t work with arrays as in Maxscript for selections.
This extension is a mystery for me… but it works!


Once again, problems with IBitArray in SDK.NET

There’s no the OR operation between IBitArrays (or at least, I can’t find it). There’s just NOT, AND and XOR, and some reverse, rotation and shift operations.
How can I do an OR operation with these other ones??!! Is it possible?

Thanks in advance.


The way I’ve found, but I’m sure there’s a better one is:



If anyone has a better solution, then my IBitArray extension is:

    public static class IBitArrayExtensions
        public static IBitArray BitwiseOR(this IBitArray A, IBitArray B)
            int sizeA = A.Size;
            int sizeB = B.Size;

            if (sizeA > sizeB)
                B.SetSize(sizeA, 1);
                A.SetSize(sizeB, 1);

            return B.BitwiseXor(A.BitwiseXor(A.BitwiseAnd(B)));

Use it like: A.BitwiseOR(B) to add B IBitArray to A IBitArray.

P.S.: to much code to get an OR operation!!!


Perhaps it can help someone:
Function to retrieve values from a generic local/global MaxScript bidimensional arrray.
For example: myVar = #(#(“hello”, 1, true),#(“goodbye”, 2, false), #(“regards”, 5, true))

using System;
using Autodesk.Max;

namespace Proin3D
    class SDKNETUtilities

        /// <summary>
        /// Getting local MXC variables: generic bidimensional array
        /// [Ex.: myVar = #(#("hello", 1, true),#("goodbye", 2, false), #("regards", 5, true)) ]
        /// </summary>
        /// <typeparam name="T"></typeparam>    type of the value to retrieve (string, int, float, bool...)
        /// <param name="localVarName"></param> the name of the local variable in MXS
        /// <param name="elementIndex"></param> first index of the bidimensional array (zero-based) [element]
        /// <param name="valueIndex"></param>   second index of the bidimensional array (zero-based) [value]
        /// <returns></returns>                 returned value will be type found (string, int, float, bool...)
        ///                                     converted to type 'T' if possible. ERROR IF CONVERSION IS NOT POSSIBLE
        ///                                     OR defaullt 'T' VALUE if index out of range.
        /// By Andrés Fernández - Proin3D - April 2016     
        static public T GetMxsBiDimensionalLocalVariable<T>(string localVarName, int elementIndex, int valueIndex)
            IFPValue IFPvar1 = GlobalInterface.Instance.FPValue.Create();
            GlobalInterface.Instance.ExecuteMAXScriptScript(localVarName, false, IFPvar1);

            if (IFPvar1.Type != ParamType2.FpvalueTabBv) return default(T);

            ITab<IFPValue> IFPvarMain = IFPvar1.FpvTab;
            if (elementIndex > IFPvarMain.Count-1 || elementIndex < 0) return default(T);

            IFPValue IFPvar2 = IFPvarMain[(IntPtr)elementIndex];
            if (IFPvar2.Type != ParamType2.FpvalueTabBv) return default(T);
            ITab<IFPValue> IFPvarElement = IFPvar2.FpvTab;
            if (valueIndex > IFPvarElement.Count - 1 || valueIndex < 0) return default(T);

            IFPValue IFPvar3 = IFPvarElement[(IntPtr)valueIndex];
            ParamType2 ptype = IFPvar3.Type;

            switch (ptype)
                case ParamType2.Bool:
                case ParamType2.Bool2:
                    return (T)System.Convert.ChangeType(IFPvar3.B, Type.GetTypeCode(typeof(T)));

                case ParamType2.PcntFrac:
                case ParamType2.Float:
                    return (T)System.Convert.ChangeType(IFPvar3.F, Type.GetTypeCode(typeof(T)));

                case ParamType2.Double:
                    return (T)System.Convert.ChangeType(IFPvar3.Dbl, Type.GetTypeCode(typeof(T)));

                case ParamType2.Int:
                    return (T)System.Convert.ChangeType(IFPvar3.I, Type.GetTypeCode(typeof(T)));

                case ParamType2.Int64:
                case ParamType2.Intptr:
                    return (T)System.Convert.ChangeType(IFPvar3.Intptr, Type.GetTypeCode(typeof(T)));

                case ParamType2.String:
                    return (T)System.Convert.ChangeType(IFPvar3.S, Type.GetTypeCode(typeof(T)));

                    return default(T);

Call it inside your C# code:
bool theValue = GetMxsBiDimensionalLocalVariable<bool>(“myVar”, 1, 2);
int theValue = GetMxsBiDimensionalLocalVariable<bool>(“myVar”, 0, 1);
As allways, “use it at your own risk”.

P.S.: It would be fine if someone implements a throwException when conversion is not possible (I’m still haven’t searched for this in C#)