Max SDK 开发入门

Max SDK是用于c++项目,开发生成max插件的SDK。编译生成的文件实质上是dll文件,根据插件类型的不同,其后缀有所差别,但形式都是.dl*

官方文档:https://help.autodesk.com/view/3DSMAX/2020/ENU/?guid=__developer_about_the_3ds_max_sdk_html

环境配置

安装max sdk

正常安装Max后,再次打开Max安装文件,选择安装工具和实用程序,之后选择安装SDK

image.png

安装visual studio及必要组件

不同版本的max sdk需要的visual studio(下简称vs)版本、windows sdk 开发工具等组件可能会不同,需要根据max sdk版本进行配置安装。

这个网页列举了不同sdk版本的需求环境。

https://help.autodesk.com/view/3DSMAX/2020/ENU/?guid=__developer_about_the_3ds_max_sdk_sdk_requirements_html 以max 2020 sdk为例,安装以下软件和组件即可(vs和platform toolset版本可以高一些,不过还是按照要求配置,遇到的问题会少一些,sdk必须是图中的版本) image.png

配置3ds Max Plugin Wizard项目模板到visual studio

3ds Max Plugin Wizard允许通过vs为3ds Max创建插件项目。

找到你的max skd安装目录,打开maxsdk\howto\3dsmaxPluginWizard\readme.txt文件,按照其中的步骤,进行安装,这里不再赘述。

成功后,可以在vs新建项目的模板中,搜索到max插件模板。

image.png

如果创建新项目失败,以下几点可以注意检查一下:

  • 如果你用的vs是2019的,可以在vc目录下自己创建个vcprojects文件夹,并把Wizard=VsWizard.VsWizardEngine.15.0最后的.15.0改为.16.0(这点官网没说)。
  • 再检查一下3dsmaxPluginWizard文件夹的读写状态,可能因为windows的一些问题,之前设置可读写没有成功。可以使用命令提示符的方式修改文件夹的可读写状态。

创建新项目

如果上一步环境配置没有问题,则会弹出一个创建引导窗口,随便选择一个然后下一步。

image.png

设置类名称、插件类型和描述,可以直接下一步。

image.png

输入max sdk目录、输出目录和3ds max目录。

输出目录可以设置为3ds Max 20**/Plugins下,即max的插件目录,这样生成插件后,可以直接安装到3dsmax。当然也可以是任意目录,然后手动把生成的插件复制到插件目录也行

3ds max目录可以让你本地调试时直接打开3ds max软件。

如果安装了QT(一个跨平台UI工具库),可以勾选Use QT UI

image.png

创建完成后可以生成一下,查看是否有错误:

  • 无法打开文件”bmm.lib”

    image.png

    这个目录下没有该文件,把配置Debug改为release

    image.png

  • 无法打开文件“***.h”可能是max sdk环境没有配置好,需要手动添加下面路径到包含目录

    D:\autodesk\3ds Max 2020 SDK\maxsdk\include

目录结构

image.png

这其中的绝大多数文件是自动生成的,不需要更改,基本只需要修改MyFirstMaxProject.cpp来完成插件功能,不过还是稍稍介绍下各个文件:

DllEntry.cpp

包含了Dll各解释函数的实现,还有最重要的DllMain入口函数,只需要知道是这么个样子且Dll必不可少即可,它是自动生成的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//当dll被加载时,windows会调用此函数;渲染时,会多次调用此函数,因此需要注意效率。
BOOL WINAPI DllMain(HINSTANCE hinstDLL,ULONG fdwReason,LPVOID /*lpvReserved*/)

//返回string,用来描述DLL
__declspec( dllexport ) const TCHAR* LibDescription();

//返回Dll中类的数量,必须及时手动修改返回值
__declspec( dllexport ) int LibNumberClasses();

//返回DLL中序号i的类
__declspec( dllexport ) ClassDesc* LibClassDesc(int i);

//这个函数返回一个预定义的常数,表明它是在哪个系统的版本
__declspec( dllexport ) ULONG LibVersion();

// 这个函数被调用一次,就在你的插件被3ds Max加载之后。
// 在此方法中进行一次性的插件初始化。
// 如果你认为你的插件成功加载,则返回TRUE,否则返回FALSE。如果 
// 该函数返回FALSE,系统将不加载该插件,然后它将调用FreeLibrary
// 并向你发送一条信息。
__declspec( dllexport ) int LibInitialize(void);

// 这个函数被调用一次,就在插件被卸载之前。
//在这个方法中进行一次性的插件解除初始化。"
// 系统并不关注返回值。
__declspec( dllexport ) int LibShutdown(void);
MyFirstMaxProject.def

描述DLL文件的各种属性,对于DLL的功能使用是必要的。

MyFirstMaxProject.rc

包含插件所需的窗口资源,插件名称、类型和描述数据都存在这里,定义了插件默认的用户界面面板(如果没使用QT)

3dsmaxsdk_preinclude.h

这个文件定义了生成编译时待办事项信息所需的宏。如果你是手动创建你的项目,你将不需要这个文件。

resoure.h

与.rc文件类似,该文件是自动生成的,存储资源文件中使用的常量值。如果你是手动创建你的插件,你将不需要这个文件。

plugin_form.ui, QtPluginRollup.cpp, QtPluginRollup.h

如果你勾选了Use QT,则会有这些文件,他们定义了插件的用户界面。

MyFirstMaxProject.h

包含了引用的文件,其中utilapi.h包括了UtilityObj类的定义,其他的头文件在各种类型的插件项目中大差不差。

hInstance是操作系统内存空间中DLL模块的一个句柄(Handle)

GetString()用于从一个字符串表中返回一个字符串。字符串表为储存在.rc文件中的名称、类型、描述数据。

MyFirstMaxProject.cpp

继承自UtilityObj类,其中的成员函数都是纯虚拟的,需要实现所有的函数。

该文件中包括MyFirstMaxProject插件类、MyFirstMaxProjectClassDesc描述类和一个全局函数ClassDesc2* GetMyFirstMaxProjectDesc(),该函数被Dll中的LibClassDesc函数调用,返回MyFirstMaxProjectClassDesc类的静态对象地址。

在3ds max中,同时只能存在一个Utility类型的插件实例(不同于几何对象插件),所以创建插件实例使用的是单例模式。

描述类负责联系3ds Max。插件向导将其命名为<yourpluginname>ClassDesc,但如果你是手动创建你的插件,你可以使用任何名字来命名它。它必须继承自ClassDesc2。它描述了我们正在实现的插件类的类型,它的名字,它的类ID等。它还实现了一个函数,以便在请求时提供这个插件类的实例。插件向导并没有实现这个类中的任何功能的代码–实现这些功能是插件开发者的责任。

Max SDK开发的第一小步

开始和结束函数

首先看void MyFirstMaxProject::BeginEditParams(Interface* ip,IUtil* iu) 这个函数,在插件激活时会被调用,尝试在里面加入

ip->PushPrompt(_M("Hello World form BeignEditParams()"));

这可以让你在打开插件时,在下面输出一条信息:

image.png

补充一下,Utility插件都是在这里的工具,在生成的文件放置到max/plugins目录后,可以这样设置插件到面板上image.png

同样,在void MyFirstMaxProject::EndEditParams(Interface* ip,IUtil*) 中加入:
ip->PushPrompt(_M("Bye form EndEditParams()"));

在结束插件时,输出“Bye form EndEditParams()”,说是结束,只要窗口不再显示该插件(切换到别的Utility插件,或者切换别的编辑模式)都会调用End函数,这点很类似Unity的Enable()和Disable()函数。

实际上在结束函数中应该写ip->PopPrompt(); ,因为切换到别的窗口后,之前的输出信息不会消失,这条会消除“Hello World form BeginEditParams()”信息。

场景图、场景节点和场景对象

场景图(Scene Graph)存储了场景对象的信息和他们之间的关系(父子关系),场景图由场景节点(INode)组成

每一个场景节点对应一个场景对象的信息,有指向场景对象的指针,子节点的数量,父节点和子节点的地址,该对象的位置旋转缩放,以及应用的材质。场景节点指向的场景对象不拥有这些信息。

场景图是树形结构,树的根是一个虚拟节点,不与任何节点连接;没有父物体的节点,则是这个树中的第一级节点。

场景对象实例化后,如果没有相应的场景节点,则不会在视口中显示。

下面是一部分INode的成员函数

1
2
3
4
5
6
7
INode::IsRootNode() // determines if a node is the root node (does not have a parent node)
INode::GetParentNode() // returns the parent node of a child.
INode::NumberOfChildren() // gets the number of children of the node.
INode::GetChildNode() // returns an INode pointer to the nth child.
INode::GetName() // returns the name of the scene object of this node.
INode::GetObjectRef() // returns a pointer to the scene object of the node.
INode::GetNodeTM() // returns the PRS of the node's scene object at the given time.

如何学习

maxsdk/howto文件夹中包含了一些简单的插件项目。

maxsdk/samples文件夹中包含了max中标准的插件的vs项目。

可以通过这些项目学习一些开发方法。