How to create a 3DSMAX plugin by stariya

VIEWS: 368 PAGES: 10

									How to create a 3DSMAX
plugin?
by Carsten Brüggmann (2003 IO-Interactive)




Introduction
This document will teach you, how to write a plugin for 3ds max 5. Please follow the orders and do the
steps on your own after reading the different chapters. recommend to read the official help files to the
SDK as well, especially the introductional part, named "Getting started". There you will find everything
you can read here but in raw format.
There are different kinds of plugins you can write. Which plugin you'll choose, depends on what you want
to archieve with it. If you like to implement a new object, like a Cube or a special mesh object, you'll
probably choose a Procedural Object. In this tutorial, we will create a Utility plugin, that can produce
cubes by pressing on a button. Quite simple...




Overview
    1.   Preparations
    2.   Setting up the environment
    3.   Creating the project
    4.   Writing the basic framework
    5.   Including the plugin class
    6.   Compiling and Testing




1. Preparations
Make shure, that you got the following software installed:


        3ds Max 5 ( including the SDK !! )
        Microsoft Platform SDK
         (this is very important, as you have to use some include files of the
         platform SDK)
        Visual C++ 6.0

That's everything you need to create your own plugin for 3ds max. You now have to set up all
components, especially VisualC++, so that you are able to compile a plugin project for 3ds max. This is
done in the next step.
2. Setting up the environment
I assume the following settings as the base for my explanations:


         3ds MAX is installed at "C:\Program Files\3dsmax5\"
         The MAX SDK is installed at "C:\Program Files\3dsmax5\maxsdk"
          (You dont have to do it like this, just put it to where you like it to be...)
         Platform SDK is installed at "C:\Program Files\Microsoft SDK"
         Visual Studio resides at "C:\Program Files\Microsoft Visual Studio"


Setting the right directories
The next step is to set up the right include- and lib folders in VisualC++ so that you can use for instance #include "max.h"
and do'nt have to write the full path into your header- and cpp files. Besie that, we need the directories of the Platform
SDK to be on top of the directory list in VisualC++ so that the compiler always gets the right files. ( not the include files
from VisualC++ ! ) This is due to an incompatibility of the 3ds max types and the ones shipped with VisualC++ that time.
Microsoft changed it with the Platform SDK.

Add the folder "C:\Program Files\Microsoft SDK\include" to your Include folders and put it on top of the list.
Add "C:\Program Files\Microsoft SDK\lib" to you library folders list and put it on top of the list.
You may now add the 3ds max SDK folders to your include/lib folder list as you have done it with the Platform SDK.

These folders should be "C:\Program Files\3dsmax5\include" and "C:\Program Files\3dsmax5\lib". It's easy :)




3. Create the project
To be able to compile a plugin we have to create a special project in VisualC++. 3ds MAX uses Dll's as plugin executables
and reads them in, if they reside in the \stdplugs directory. This method is just for testing the very first plugin. There is an
.ini file, that controls, which plugins have to be loaded by 3ds MAX but this time, we concentrate on creating the plugin
and not on how to publish it.

So we create a new Win32 Dynamic Link Library project.
It's a simple Dll project and we name it "MyProject". Choose the place for the files in the dialog, perhaps "E:\myplugins\".
All the nessecary files are created to define the project, so we can add our .cpp and header files now:

Add a .cpp file, named myplugin.cpp to the project. The file contains an #include "max.h" for a start.

We have to add a .def file to the project to tell the compiler and linker, which functions we want to export in our Dll
excutable. The content of this file should be the following:

LIBRARY MyPlugInEXPORTS
LibDescription @1
LibNumberClasses @2
LibClassDesc @3
LibVersion @4

SECTIONS
.data READ WRITE

As you can see, we export four functions. These are accessed by 3ds MAX to get to know, which classes are included in
the plugin and how 3ds MAX should open them. Later we will include them into our .cpp file for implementation.

Now we add a resource script file to the project and name it "myplugin.rc". Here we add a new dialog resource and name
it IDD_MYPLUGIN_PANEL.
The dialog has to be 108 units wide to fit into the panel of 3ds max. As you can see in the picture, the dialog do not have a
border anymore.
Beside that, you have to set it up as a child. All this is done in the Styles Tab. You may noticed the parameters of the
button resource, that is called IDC_RANDOM_CUBE. This will be our button, with which we will create random cubes
later in 3ds max by pressing it. The edit box resource is named IDC_EDIT_MSG. The string table contains only one item
at this point: IDS_RB_MyCube -> "Test Pluginimplementation"

To be able to access the text in the string table, you have to include the following code in your myplugin.cpp file:

TCHAR *GetString(int id)
{
static TCHAR buf[256];
if (hInstance)
return LoadString(hInstance, id, buf, sizeof(buf)) ? buf : NULL;
return NULL;
}


Specifying Output and Include File Settings

The following instructions are just copied from the MAX SDK because they are quite similar to what we have to do for our
project. Simply follow each of the listed orders and yopu will have the right configurations for your project.

You now need to specify the output file name. This is the name of the DLL created. You specify the output file
name for your plug-in using the procedure below:
1 From the Project pull down menu choose Settings... Make sure all the configuration are being changed by selecting All
Configurations from the Settings For: dropdown list.
2 From the Project Settings dialog box choose the Link Tab.
3 Under Output file name: Enter the path to 3ds max plug-ins and your file name. For example
"\3DSMAX\STDPLUGS\MYPLUGIN.DLO"

Result: When the project is compiled and linked successfully, the DLL is written to this location.
The example above (MYPLUGIN.DLO) is the extenstion typically used by procecural object plug-ins. For a list of the
standard filename extensions for all plug-ins see Plug-In Directory Search Mechanism. Note that 3ds max will only load
DLLs that use these standard extensions.
You now need to specify the path of the include files for the SDK:
1 From the Project Settings dialog box choose the C/C++ Tab.
2 From the Category: drop down list box choose Preprocessor.
3 Under Additional include directories: enter the path \MAXSDK\INCLUDE.

You also need to add COMCTL32.LIB which is not included by default by Visual C++ in the list of libraries to link.
1 Still within the Project Settings dialog box choose the Link Tab (you may need to scroll to see it).
2 In the list of Object/library modules add COMCTL32.LIB to the list of existing choices.
3 Select OK to exit the dialog.

Updating the Project Configurations
A new plug-in project contains two default configurations. These are named Win32 Release and Win32 Debug. The
sample code that comes with the 3ds max SDK has an additional configuration: Win32 Hybrid. This section discusses the
need for these different configuration.
There are three conditions for plug-ins working with MAX. These are:

1) Release 3ds max and Release Mode (non-debug) plug-ins. When you create a plug-in for general distribtion you'll
compile in Release mode.
2) Release 3ds max and Debug Mode plug-ins. While developing a plug-in and using source code level debugging you'll
compile in Hybrid mode.
3) Debug 3ds max (only registered developers have access to this special version of MAX) and Debug Mode plug-ins.
While using the special Debug SDK, and Debug 3ds max you'd compile in Debug Mode.

In Microsoft Developer Studio the Debug and Release C runtime libraries use different heap management. This means
that allocating an object in debug mode and de-allocating it in release mode (and vice versa) can cause a crash. Most
developers will work with a version of 3ds max compiled in Release mode. Thus it uses the run-time libraries called
Multithreaded DLL. Plug-Ins that work with this version of 3ds max need to match this run-time library. If they don't
crashes will occur.
To prevent this from happening you need to create a new configuration and change some of the settings of the existing
configurations.

To create the Win32 Hybrid configuration follow these steps:
1 From the Build menu choose Configurations... and then choose Add....
2 Enter Hybrid in the Configuration edit box then choose Win32 Debug from the Copy settings from: list box and press OK
and then Close to exit the Configurations dialog. This creates a new Hybrid configuration using the same initial settings as
the Win32 Debug configuration.
3 From the Project menu choose Settings....
4 From the Settings For: section choose the Win32 Hybrid configuration.
5 From the C/C++ tab, under the Categories: drop down list choose Code Generation.
6 From the Use run-time library: drop down list choose Multithreaded DLL.
7 Choose the General tab of the dialog.
8 In the edit boxes under both Intermediate files: and Output files: enter Hybrid.
9 Select OK to exit the dialog.

Now update the Win32 Release configuration:
1 From the Project menu choose Settings....
2 From the Settings For: section choose the Win32 Release configuration.
3 From the C/C++ tab, under the Categories: drop down list choose Code Generation.
4 From the Use run-time library: drop down list choose Multithreaded DLL.
5 Select OK to exit the dialog.

Now update the Win32 Debug configuration:
1 From the Project menu choose Settings....
2 From the Settings For: section choose the Win32 Debug configuration.
3 From the C/C++ tab, under the Categories: drop down list choose Code Generation.
4 From the Use run-time library: drop down list choose Debug Multithreaded DLL.
5 Select OK to exit the dialog.

Result: You now have three configurations available, each used for a different purpose. When you create a plug-in for
distribution to the public use your Win32 Release configuration. During development of the plug-in when you want to use
source level debugging use the Win32 Hybrid configuration. If you are a registered developer using the special Debug
SDK and Debug 3ds max then use the Win32 Debug configuration.


Specifying the libraries or the project

You need special libraries to access 3ds MAX. There are many different available for a lot of purposes. Keep in mind, that
every plugin needs the core.lib! You will find them in your "..\maxsdk\lib" folder. You have to add these library files to your
project by getting into the "Project" menu and choosing "Add To Project". Here you change the file type to "Library files
(.lib)" and choose the libs you like to add. You can press CTRL to choose more files than one in the dialog.
Hint: You can delete ODBC32.LIB and ODBCCP32.LIB from the list of linked lib files, which are included by default but
not needed.


Finish

As you now have a complete plugin project. You can select the Hybrid as your active configuration. All dependencies
should be updated by VisualC++ the time you'll compile the project for the first time. If not, then there'll be a menu under
"Build" where you can update them on your own.

Another thing is quite important. If you want to debug your plugin in 3ds MAX, you have to set up the Debug options.
Therefore you open the project settings dialog and choose the "Win32 Hybrid" version. Then you choose the tab "Debug"
and select the 3dsmax.exe in the textfield named with "Executable for debug session". There's the possibility of opening a
file dialog to find the file. Just set up the working directory with the one containing the 3dsmax.exe.


The last step in configuring the workspace is to save it!   Save your workspace now !!




4. Writing the basic framework

I hereby suggest to have a look at the according help in the 3ds MAX SDK. Go to "Advanced Topics" and choose "DLL,
Library Functions and Class Descriptors". If you follow the steps there, you will end up with a .cpp and a header file
containing the needed Dll functions. I will just explain, what we have to do exactly for our project...

You already created the .def file and I pointed out that you have to create implementaions of these functions as well.
Otherwise the linker would'nt create the project and 3ds MAX could'nt read the plugin class.

1.
Below the inludes of the header files "max.h" and "resource.h", you have to publish the global hInstance variable.
After that, the GetString() function follows.

2.
Then you implement the function LibNumberClasses, which returns the amount of classes in your plugin Dll.

__declspec(dllexport) int LibNumberClasses() { return 1; }

3.
Next thing you do is to implement the DllMain function, the main entry function for the Dll. Here the functions
InitCustomControls() and InitCommonControls() are executed. The first one initializes all the 3ds max user interface
elements you've choosen to be a part of your plugin.

int controlsInit = FALSE;

BOOL WINAPI DllMain(HINSTANCE hinstDLL,ULONG fdwReason,LPVOID lpvReserved)
{
hInstance = hinstDLL;

if (! controlsInit) {
controlsInit = TRUE;
InitCustomControls(hInstance);
InitCommonControls();
}
return(TRUE);
}

4.
The next function is fairly simple. It describes your plugin with a string. MAX asks for that at different places in the
program.

__declspec( dllexport ) const TCHAR *LibDescription() {
return _T("My special plugin!. It can produce cubes...");
}

5.
This function just returns the version of 3ds max, the API you're using for your plugin and the version of the SDK. But the
best thing is that you do'nt have to think about that, because everything is already been packed into one define:
VERSION_3DSMAX. :))

__declspec( dllexport ) ULONG LibVersion() { return VERSION_3DSMAX; }

6.
The next function is the linkage to your plugin classes, you will add later. It will return a pointer to a description class, thrue
which 3ds MAX can get a pointer to the actual class object and to some parameters to this class.

__declspec(dllexport) ClassDesc *LibClassDesc(int i) {
switch(i) {
case 0: return GetMyCubeDesc();
default: return 0;
}
}

7.
That's it !! You have written your main .cpp file - the framework for your plugin classes, that will do the "real" plugin-stuff.
We will now write another .cpp file, that contains the plugins class. The according header file will be included in the
myplugin.cpp file.




5. Including the plugin class

The header file:
I will describe these file just by going from block to block in the source files. The first one will be the header file - exporting
some global functions.
The headerfile will be named "mycube.h".

#ifndef __CUBE__H
#define __CUBE__H

#include /* */"Max.h"
#include "resource.h"

TCHAR *GetString(int id);

extern ClassDesc* GetMyCubeDesc();

extern HINSTANCE hInstance;

#endif

This is easy as it just publishes the pointer to the ClassDesc class of the CUBE class. I will describe, what this class is
doing. Here you should realize, that you have to include the "max.h" and the header file, created by the resource compiler.
Beside that you have to publish the GetString() function and the hInstance variable.
The .cpp file (The implementation)

First, you include all the needed header files:

#include "mycube.h"
#include /* */"utilapi.h"
#include "simpobj.h" // To get the GenBox object created
#include "resource.h" // created by the IDE...

The simpobj.h contains all classes, with which you can create all the simple objects in 3ds MAX like cubes, spheres and
so on. We will use the ability to create cubes.

#define MyCube_CLASS_ID Class_ID(0x4f385752, 0x707321b2)

This is very important!!! Every plugin has to have a unique ID in 3ds MAX. You may use the small program gencid.exe
which should be in "\maxsdk\help". This opens a small window and creates define for a unique ID which you can copy and
paste into your code. Never ever forget this! It's important and if you use an ID not created by gencid.exe, it may be that
3ds MAX gets confused and not loads in your plugin.

class MyCube : public UtilityObj {
public:
IUtil *iu;
Interface *ip;
HWND hPanel;

MyCube(); // Constructor
void BeginEditParams(Interface *ip,IUtil *iu);
void EndEditParams(Interface *ip,IUtil *iu);
void DeleteThis() {}

};
static MyCube theMyCube;

This is the main class for the plugin. Here you can choose, from which class yours should be derived. That sets up the
nature of your plugin and its purpose. I've chosen a Utility class here. To be able to do this, I have included the "utilapi.h"
where the UtilityObj class resides.
Well, the public variables are:
- iu: this is a pointer to the your utility object in 3ds MAX. We will use it later.
- ip: this pointer is used to represent the interface to 3ds MAX, with which you can access all the functions from 3ds MAX.
- the hPanel is our structure of the plugins user interface - just a normal Win32 window.

After the constructor follows BeginEditParams() which is the entry function, that is called everytime, the plugin is loaded
into 3ds MAX. Here we will add our rollup - our user-interface for the plugin. As you might guess, the EndEditParams()
function is called, when the utility object is destroyed because MAX don't need it anymore. Here we will free all our
resources.

The actual Initialization and creation of the Utiliy object follows after the class description. Here the MyCube object is
created statically.

The next thing, we have to implement, is the class descriptor, that tells 3ds MAX everything about the class, we're about
to create.

class MyCubeClassDesc:public ClassDesc {
public:
int IsPublic() {return 1;}
void * Create(BOOL loading = FALSE) {return &theMyCube;}
const TCHAR * ClassName() {return GetString(IDS_RB_MyCube);}
SClass_ID SuperClassID() {return UTILITY_CLASS_ID;}
Class_ID ClassID() {return MyCube_CLASS_ID;}
const TCHAR* Category() {return _T("IO-Interactive");}
};
static MyCubeClassDesc MyCubeDesc;
ClassDesc* GetMyCubeDesc() {return &MyCubeDesc;}

IsPublic() tells MAX, that this plugin can be used globally.
Create() just returns the Cube object.
ClassName() returns the string from the string resource - naming the class.
SuperClassID() describes, of which kind our plugin class is and it's a uility...
ClassID() returns the ClassID, created with gencid.exe to MAX. Remind yourself - it has to be unique !!
Category() returns the category, this plugin has should reside. This is for sorting all the different plugins.

After the class description, there follows the static creation of the actual description-object and the declaration of the
function, returning the pointer to that object, used by LibClassDesc() in myplugin.cpp.

static BOOL CALLBACK MyCubeDlgProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{

MyCube *u = (MyCube *)GetWindowLongPtr(hWnd, GWLP_USERDATA);
if (!u && msg != WM_INITDIALOG ) return FALSE;

switch (msg) {
case WM_INITDIALOG:

u = (MyCube *)lParam;
SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG)u);
break;

case WM_DESTROY:

break;

case WM_COMMAND:
switch (LOWORD(wParam)) {



case IDOK:
theMyCube.iu->CloseUtility();
break;

case IDC_RANDOM_CUBE:
{



SetWindowText(GetDlgItem(hWnd, IDC_EDIT_MSG), _T("Create Random Cube"));
GenBoxObject *gb = (GenBoxObject *)CreateInstance(GEOMOBJECT_CLASS_ID, Class_ID(BOXOBJ_CLASS_ID, 0));

gb->SetParams(10.0f, 20.0f, 30.0f);
INode *node = u->ip->CreateObjectNode(gb);
TSTR name(_T("RandomBox"));
u->ip->MakeNameUnique(name);
node->SetName(name);
u->ip->SelectNode(node);
u->ip->ExecuteMAXCommand(MAXCOM_ZOOMEXT_SEL); // redraws the viewport



}
break;
}



break;

default:
return FALSE;
}
return TRUE;
}

This function is the callback for the events of our window that descibes our rollup in 3ds MAX. We have to react on all of
the important events it creates. ut in the beginning of the function we have to get the pointer to our Cube-object via the
hWnd structure. Having that, we can then create the needed obejcts, that represent the utility object to have access to 3ds
MAX.

We react on INITDIALOG by setting the pointer of our utiliy to the window handler of our window. By that, 3ds MAX now
knows, how to communicate to our utility object. If you've reated special objects, you should delete them in
WM_DESTROY.

WM_COMMAND handles all the user accesses to our rollup. IDOK handles the case of closing our plugin - so we call
CloseUtility in our object for deleting everything and freeing the space used.

IDC_RANDOM_CUBE comes from our resource file. We created it by setting up a button. When the button has been
pressed, we want to set the text in the textbox. That's simle, as you can see it in the first line of the event block.

After that we want to create a new cube object in 3ds MAX. To do so, we have to create a new BoxObject by calling the
interface function of 3ds MAX using the special arguments GEOMOBJECT_CLASS_ID, which tells MAX, that we want t
create a simple object, and BOXOBJ_CLASS_ID, which asks for an actual box to be created.
Then we set up the parameters for that box, the width, height and depth of the object, all of them set to 10.

When you create an object in 3ds MAX, you have to create a node for it, that represents the object in the object-tree of
3ds MAX. So we create a new one by calling CreateObjectNode() from the interface of 3ds MAX and provide our object
pointer as an argument to this function. e name it "RandomBox" and use MakeNameUnique() to make shure, that this
name is not used by another object. If so, 3ds MAX attaches numbers to our node to make it unique. After that, we name
the node with the newly created uniue name by calling SetName().

At last, we select our new node and call a MAX-Command, that zooms the active viewport to our newly created object.

And by doing this, we fullfilled our task of creating a cube by pressing the button.

MyCube::MyCube()
{
iu = NULL;
ip = NULL;
hPanel = NULL;
}

void MyCube::BeginEditParams(Interface *ip,IUtil *iu)
{
this->iu = iu;
this->ip = ip;
hPanel = ip-
>AddRollupPage(hInstance,MAKEINTRESOURCE(IDD_MYCube_PANEL),MyCubeDlgProc,GetString(IDS_RB_MyCube)
,(LPARAM)this);
}

void MyCube::EndEditParams(Interface *ip,IUtil *iu)
{
this->iu = NULL;
this->ip = NULL;
ip->DeleteRollupPage(hPanel);
hPanel = NULL;
}

These last three unctions are easy to understand. The first one is the standard constructor of the class, that initializes all
the pointers.
The function BeginEditParams() gets two arguments that come from 3ds MAX itself. The pointer to our utility object and
the interface-pointer to 3ds MAX. Here we add our rollup page, the user interface of our plugin by calling
AddRollupPage().
In EndEditParams() we delete our created rollup page and free the memory.
6. Compiling and Testing
Now, as all the settings are done, we can save everything and run a first test. Press F7 to build the whole project. As you
should have set up the directory of the created executable to the \stdplugs folder, the dll should be put into that one by the
VisualC++. Just press F5 to fire up 3ds MAX and test your plugin by dragging it onto your panel on the right side and
clicking on it for activation.




http://kniffo.maxscript.de/tut/3dsmax_plugin/

								
To top