感谢老狼同学的这篇文章。
点击可看原帖地址
[原创]3dsmax插件开发新手上路
作者:老狼
email: cgwolver@163.comQQ: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;
}
至此一个基本的插件程序框架就搭建起来了,剩下的就是要遍历场景节点,实现你自己的数据导出功能了
(完)