3dsmax插件开发新手上路

 感谢老狼同学的这篇文章。

 点击可看原帖地址

[原创]3dsmax插件开发新手上路

作者:老狼

email: [email protected]
QQ:4197680024

我尽量把话题说的通俗易懂一点,因为要做插件的可能不只是程序员:)
所谓插件,其实就是动态链接库,windows 系统上就是dll,打开Autodesk\3ds Max 9\stdplugs文件夹,会看到下面一大堆扩展名叫dlu,dlo,等等的文件。其实这都是dll改的名字,本质还是dll。当宿主程序运行起来的时候,会加载这个文件夹下的所有dlxx扩展名的文件。因此你可以用任何创建dll的方法来生成它,比如用Win32 DLL Project,或者MFC DLL Project;我本人更喜欢用MFC DLL Project,可以方便的用MFC的功能;在3dsmax插件中使用MFC DLL Project ,有一些需要注意的问题,将在后文叙述.

如果你想快速写插件,通常使用 maxsdk提供的 vc wizard 最容易创建一个特定的插件框架程序.这里简单说一下设置 3dsmax vc wizard 的方法:

安装了3dsmax product以后,还要安装maxsdk,sdk和大量的sample在dvd完整安装版本上,PluginWizard 的路径在Autodesk\3ds Max 2009 SDK\maxsdk\howto\3dsmaxPluginWizard下面,里面有readme.txt文件,大意是这样的:用文本编辑器打开3dsmaxPluginWizard.vsz,我装的是3dsmax2009,打开这个vsz文件的内容如下:

VSWIZARD 7.0
Wizard=VsWizard.VsWizardEngine.8.0

Param="WIZARD_NAME = 3dsmaxPluginWizard"
Param="ABSOLUTE_PATH = C:\dev\p4\gouda\3dswin\src\maxsdk\howto\3dsmaxPluginWizard"
Param="FALLBACK_LCID = 1033"

把ABSOLUTE_PATH 的路径改为 D:\Autodesk\3ds Max 2009 SDK\maxsdk\howto\3dsmaxPluginWizard,就是你复制文件的原路径

这个时候用VC8 新建 Project ,选择VC++,会看到一个 3ds max plugin Wizard 列表项,然后按照提示一步一步操作就能创建一个基本的插件框架.

这里我要多说一点的是用自定义dll project方法创建3dsmax plugins的方法,我本人不太喜欢用现成的东西,凡事总喜欢自己亲手做来的东西,这样理解的也比较深刻透彻

3dsmax插件,有几个标准的可导出函数,3dsmax.exe装载插件时,用GetProcAddress API找出预定的几个标准接口,然后调用一个LibClassDesc的接口,创建出用户自定义的对象实例,类似于"抽象厂"方法

3dsmax 插件中必须定义的标准导出函数:
#define DLLEXPORT_API  __declspec(dllexport)

//插件描述
DLLEXPORT_API  const TCHAR* LibDescription()
{
    LoadString( IDS_LIBDESCRIPTION );
}
//这个dll中有几个插件
DLLEXPORT_API  int LibNumberClasses()
{
    return 1;
}
//取得插件描述块,3dsmax.exe用它来创建你的插件类的实例
DLLEXPORT_API  ClassDesc* LibClassDesc(int i)
{
    switch(i) 
    {
          case 0: return GetMyClassDesc();
          default: return 0;
    }
}
//LibVersion用来匹配插件和宿主3dsmax.exe之间的版本匹配问题.
DLLEXPORT_API  ULONG LibVersion()
{
    return VERSION_3DSMAX;
}

关于如何在VC中创建DLL Project,不需要多说了,要说的一点就是导出函数必须用 _declspec(dllexport) 修饰,另外还需要在def文件中列出导出函数的名字如下:
假定一个导出插件的文件名是SampleExporter
//SampleExporter.def
LIBRARY SampleExporter
EXPORTS
    LibDescription            @1
    LibNumberClasses  @2
    LibClassDesc            @3
    LibVersion                   @4
SECTIONS
    .data READ WRITE

下面着重说GetMyClassDesc() 这个函数...

#define SampleExporter_CLASS_ID   Class_ID(0xc2a1ee34, 0x832cc295)  //这个Class_ID用Autodesk\3ds Max 2009 SDK\maxsdk\help\getcid.exe 自己生成,只要不存在冲突就可以.

//SampleExporter 是从SceneExport 继承的导出插件类,SceneExport是sdk预定义的用于实现导出插件的基类,用户写导出插件,只需要继承它,然后实现相应的纯虚接口即可.
//导入插件基于SceneExport,辅助插件基于UtilityObj,还有创建面板上的,详见3ds Max SDK Programmer'sGuide中的Type of Plug-Ins 介绍.
//函数DoExport是3dsmax.exe和导出插件程序交互的主要接口,选择File/Export菜单,然后选择SampleExporter后,将会进入DoExport函数...
class SampleExporter : public SceneExport {
public:        
    static HWND hParams;
        
    int        ExtCount();                // Number of extensions supported
    const TCHAR *    Ext(int n);                // Extension #n (i.e. "3DS")
    const TCHAR *    LongDesc();            // Long ASCII description (i.e. "Autodesk 3D Studio File")
    const TCHAR *    ShortDesc();            // Short ASCII description (i.e. "3D Studio")
    const TCHAR *    AuthorName();            // ASCII Author name
    const TCHAR *    CopyrightMessage();            // ASCII Copyright message
    const TCHAR *    OtherMessage1();            // Other message #1
    const TCHAR *    OtherMessage2();            // Other message #2
    unsigned int    Version();                // Version number * 100 (i.e. v3.01 = 301)
    void        ShowAbout(HWND hWnd);        // Show DLL's "About..." box

    BOOL SupportsOptions(int ext, DWORD options);
    int        DoExport(const TCHAR *name,ExpInterface *ei,Interface *i, BOOL suppressPrompts=FALSE, DWORD options=0);

    //Constructor/Destructor
    SampleExporter ();
    ~SampleExporter ();        

};
//3dsmax.exe如何与用户自定义的插件建立起联系,就是通过下面这个ClassDesc类来获得创建插件对象实例所需要的必要的信息
class  SampleExporterClassDesc: public ClassDesc2 
{
public:
    virtual int IsPublic()                     { return TRUE; }
    virtual void* Create(BOOL /*loading = FALSE*/)         { return new SampleExporter (); }//这里其实就是"抽象厂"方法,让用户提供一个实例
    virtual const TCHAR *    ClassName()             { return GetString(IDS_CLASS_NAME); }
    virtual SClass_ID SuperClassID()                 { return SCENE_EXPORT_CLASS_ID; }
    virtual Class_ID ClassID()                 { return SampleExporter_CLASS_ID; }
    virtual const TCHAR* Category()                 { return GetString(IDS_CATEGORY); }
    virtual const TCHAR* InternalName()             { return _T("SampleExporter"); }    // returns fixed parsable name (scripter-visible name)
    virtual HINSTANCE HInstance()                 { return hInstance; }        // returns owning module handle
};

static  SampleExporterClassDesc  expoertDesc;
ClassDesc2* GetMyClassDesc() { return &expoertDesc; }

//下面是SampleExporter类的实现代码
SampleExporter::SampleExporter()
{
}
SampleExporter::~SampleExporter() 
{
}
int SampleExporter::ExtCount()
{
    #pragma message(TODO("Returns the number of file name extensions supported by the plug-in."))
    return 1;
}
const TCHAR *SampleExporter::Ext(int n)
{        
    #pragma message(TODO("Return the 'i-th' file name extension (i.e. \"3DS\")."))
    return _T("");
}

const TCHAR *SampleExporter::LongDesc()
{
    #pragma message(TODO("Return long ASCII description (i.e. \"Targa 2.0 Image File\")"))
    return _T("");
}
    
const TCHAR *SampleExporter::ShortDesc() 
{            
    #pragma message(TODO("Return short ASCII description (i.e. \"Targa\")"))
    return _T("");
}
const TCHAR *SampleExporter::AuthorName()
{            
    #pragma message(TODO("Return ASCII Author name"))
    return _T("");
}
const TCHAR *SampleExporter::CopyrightMessage() 
{    
    #pragma message(TODO("Return ASCII Copyright message"))
    return _T("");
}
const TCHAR *SampleExporter::OtherMessage1() 
{        
    //TODO: Return Other message #1 if any
    return _T("");
}
const TCHAR *SampleExporter::OtherMessage2() 
{        
    //TODO: Return other message #2 in any
    return _T("");
}
unsigned int SampleExporter::Version()
{                
    #pragma message(TODO("Return Version number * 100 (i.e. v3.01 = 301)"))
    return 100;
}
void SampleExporter::ShowAbout(HWND hWnd)
{            
    // Optional
}
BOOL SampleExporter::SupportsOptions(int ext, DWORD options)
{
    #pragma message(TODO("Decide which options to support.  Simply return true for each option supported by each Extension the exporter supports."))
    return TRUE;
}
//这里是真正的导出函数入口,用户选择导出后,将执行到这里
int  SampleExporter::DoExport(const TCHAR *name,ExpInterface *ei,Interface *i, BOOL suppressPrompts, DWORD options)
{
    #pragma message(TODO("Implement the actual file Export here and"))

    if(!suppressPrompts)
        DialogBoxParam(hInstance, 
                MAKEINTRESOURCE(IDD_PANEL), 
                GetActiveWindow(), 
                maxProject1OptionsDlgProc, (LPARAM)this);

    #pragma message(TODO("return TRUE If the file is exported properly"))
    return FALSE;
}

至此一个基本的插件程序框架就搭建起来了,剩下的就是要遍历场景节点,实现你自己的数据导出功能了
(完)

你可能感兴趣的:(插件开发)