3dsmax2026插件开发入门:使用.net8开发

环境:

  • window11
  • 3dsmax2026
  • .net8
  • vs2026

写这个的原因:

看到autodesk官网上明确声明:3dmax2026的一项重大变更是.net的支持从4.8转向了.net8,但网络并没有看到相关的入门博客,问chatgpt耗费了我1个多小时,最后还让我用c++开发。。。

最后,我只能扒官方的文档一步步尝试,还好最后成功了。

参考:

第一个例子:编写一个跟随3dsmax启动的插件

代码:

MyFirst3dMaxPlugin.slnx

xml 复制代码
<Solution>
  <Configurations>
    <Platform Name="Any CPU" />
    <Platform Name="x64" />
  </Configurations>
  <Project Path="MyFirst3dMaxPlugin/MyFirst3dMaxPlugin.csproj">
    <Platform Solution="*|x64" Project="x64" />
  </Project>
</Solution>

MyFirst3dMaxPlugin.csproj

xml 复制代码
<Project Sdk="Microsoft.NET.Sdk">

	<PropertyGroup>
		<TargetFramework>net8.0-windows</TargetFramework>
		<ImplicitUsings>enable</ImplicitUsings>
		<Nullable>enable</Nullable>
		<Platforms>x64</Platforms>
	</PropertyGroup>

	<ItemGroup>
		<!-- 修改为自己3dmax的安装目录 -->
		<Reference Include="Autodesk.Max">
			<HintPath>D:\AutoDesk\3ds Max 2026\Autodesk.Max.dll</HintPath>
		</Reference>
	</ItemGroup>

</Project>

AssemblyFunctions.cs

csharp 复制代码
using System.Reflection;

namespace MyFirst3dMaxPlugin
{
    public class AssemblyFunctions
    {
        //下面是四个3dmax支持的声明周期函数
        //注意: 我是向d盘写入文件, 如果向c盘写入可能会有权限的问题

        public static void AssemblyMain()
        {
            File.AppendAllText("d:\\demo.txt", "AssemblyMain-" + DateTime.Now.ToString() + "\r\n");
            // 如果想在3dmax启动的时候调试的话 加上下面一行, 这样3dmax启动的时候会有弹框提示
            //System.Diagnostics.Debugger.Launch();
        }

        public static void AssemblyShutdown()
        {
            File.AppendAllText("d:\\demo.txt", "AssemblyShutdown-" + DateTime.Now.ToString() + "\r\n");
        }

        public static void AssemblyLoad()
        {
            File.AppendAllText("d:\\demo.txt", "AssemblyLoad-" + DateTime.Now.ToString() + "\r\n");
        }
        public static void AssemblyInitializationCleanup()
        {
            File.AppendAllText("d:\\demo.txt", "AssemblyInitializationCleanup-" + DateTime.Now.ToString() + "\r\n");
        }
    }
}

编译运行

编译后直接放到3dmax的bin/assemblies目录:

可以将 ·MyFirst3dMaxPlugin.deps.json· 一并拷贝过去

这样打开3dmax后就可以看到写入的日志了:

当我们关掉3dmax后再看日志:

第二个例子:写一个内嵌的web服务器

在第一个例子基础上,在3dsmax2026启动时就开启一个web服务器。

代码:

改造 MyFirst3dMaxPlugin.csproj

xml 复制代码
<Project Sdk="Microsoft.NET.Sdk">

	<PropertyGroup>
		<TargetFramework>net8.0-windows</TargetFramework>
		<ImplicitUsings>enable</ImplicitUsings>
		<Nullable>enable</Nullable>
		<Platforms>x64</Platforms>
	</PropertyGroup>

	<ItemGroup>
		<!-- 修改为自己3dmax的安装目录 -->
		<Reference Include="Autodesk.Max">
			<HintPath>D:\AutoDesk\3ds Max 2026\Autodesk.Max.dll</HintPath>
		</Reference>
		<!-- web框架 -->
		<FrameworkReference Include="Microsoft.AspNetCore.App" />
	</ItemGroup>

</Project>

新增 Program.cs

csharp 复制代码
using Microsoft.AspNetCore.Builder;

namespace MyFirst3dMaxPlugin
{
    public class Program
    {
        public static void Main()
        {
            var builder = WebApplication.CreateBuilder();
            var app = builder.Build();
            app.MapGet("/", () =>
            {
                return "hello-" + DateTime.Now;
            });
            app.Run("http://localhost:5000");
        }
    }
}

改造 AssemblyFunctions.cs

csharp 复制代码
using System.Reflection;

namespace MyFirst3dMaxPlugin
{
    public class AssemblyFunctions
    {
        //下面是四个3dmax支持的声明周期函数
        //注意: 我是向d盘写入文件, 如果向c盘写入可能会有权限的问题

        public static void AssemblyMain()
        {
            File.AppendAllText("d:\\demo.txt", "AssemblyMain-" + DateTime.Now.ToString() + "\r\n");
            // 如果想在3dmax启动的时候调试的话 加上下面一行, 这样3dmax启动的时候会有弹框提示
            //System.Diagnostics.Debugger.Launch();

            // 第一种方法: 程序集解析事件
            AppDomain.CurrentDomain.AssemblyResolve += (sender, args) =>
            {
                string assemblyName = new AssemblyName(args.Name).Name;
                string depsPath = Path.Combine(
                    Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location),
                    "MyFirst3dMaxPlugin-Dependency",
                    assemblyName + ".dll"
                );

                if (File.Exists(depsPath))
                    return Assembly.LoadFrom(depsPath);

                return null;
            };

            //// 第二种方法: 手动加载所有dll
            //string assemblyDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
            //string depsDir = Path.Combine(assemblyDir, "MyFirst3dMaxPlugin-Dependency");

            //// 加载所有依赖DLL
            //foreach (string dll in Directory.GetFiles(depsDir, "*.dll"))
            //{
            //    try
            //    {
            //        Assembly.LoadFrom(dll);
            //    }
            //    catch (Exception ex)
            //    {
            //        // 处理加载失败
            //        File.AppendAllText("d:\\demo.txt", "AssemblyMain-处理加载失败-" + dll + "-" + DateTime.Now.ToString() + "\r\n");
            //    }
            //}

            Task.Run(() => Program.Main());
        }

        public static void AssemblyShutdown()
        {
            File.AppendAllText("d:\\demo.txt", "AssemblyShutdown-" + DateTime.Now.ToString() + "\r\n");
        }

        public static void AssemblyLoad()
        {
            File.AppendAllText("d:\\demo.txt", "AssemblyLoad-" + DateTime.Now.ToString() + "\r\n");
        }
        public static void AssemblyInitializationCleanup()
        {
            File.AppendAllText("d:\\demo.txt", "AssemblyInitializationCleanup-" + DateTime.Now.ToString() + "\r\n");
        }
    }
}

编译运行

发布配置:

将 Pulish 下的内容整体拷贝到 bin/assemblies\MyFirst3dMaxPlugin-Dependency

还有将 MyFirst3dMaxPlugin.dll 拷贝到 bin/assemblies 下

现在让我们再打开3dsmax2026,然后看下浏览器:

现在,一个内嵌的web服务器已经诞生了。

第三个例子:写一个自定义菜单项

目标:点击这个菜单后向场景中添加一个茶壶

代码:

新建一个工程ActionPluginDemo,直接上代码:
ActionPluginDemo.csproj

csharp 复制代码
<Project Sdk="Microsoft.NET.Sdk">

	<PropertyGroup>
		<TargetFramework>net8.0-windows</TargetFramework>
		<ImplicitUsings>enable</ImplicitUsings>
		<Nullable>enable</Nullable>
		<Platforms>x64</Platforms>
		<!-- 修改为自己3dmax的安装目录 -->
		<ADSK_3DSMAX_x64_2026>D:\AutoDesk\3ds Max 2026</ADSK_3DSMAX_x64_2026>
	</PropertyGroup>

	<ItemGroup>
		<Reference Include="Autodesk.Max">
			<HintPath>$(ADSK_3DSMAX_x64_2026)\Autodesk.Max.dll</HintPath>
			<Private>False</Private>
		</Reference>
		<Reference Include="CSharpUtilities">
			<HintPath>$(ADSK_3DSMAX_x64_2026)\CSharpUtilities.dll</HintPath>
			<Private>False</Private>
		</Reference>
		<Reference Include="ManagedServices">
			<HintPath>$(ADSK_3DSMAX_x64_2026)\ManagedServices.dll</HintPath>
			<Private>False</Private>
		</Reference>
		<Reference Include="MaxCustomControls">
			<HintPath>$(ADSK_3DSMAX_x64_2026)\MaxCustomControls.dll</HintPath>
			<Private>False</Private>
		</Reference>
		<Reference Include="UiViewModels">
			<HintPath>$(ADSK_3DSMAX_x64_2026)\UiViewModels.dll</HintPath>
			<Private>False</Private>
		</Reference>
		<Reference Include="WPFCustomControls">
			<HintPath>$(ADSK_3DSMAX_x64_2026)\WPFCustomControls.dll</HintPath>
			<Private>False</Private>
		</Reference>
	</ItemGroup>

	<!-- 自动将生成的 ActionPluginDemo.dll 拷贝到 bin\assemblies -->
	<Target Name="PostBuild" AfterTargets="PostBuildEvent">
		<Copy SourceFiles="$(TargetPath)"
			DestinationFolder="$(ADSK_3DSMAX_x64_2026)\bin\assemblies\"
			SkipUnchangedFiles="true" />
	</Target>

</Project>

MaxDotNetCUIDemo.cs

csharp 复制代码
using Autodesk.Max;
using UiViewModels.Actions;

namespace ActionPluginDemo
{
    public class MaxDotNetCUIDemo : CuiActionCommandAdapter
    {
        // 动作显示的文本
        public override string ActionText
        {
            get { return InternalActionText; }
        }

        // 动作所属的分类
        public override string Category
        {
            get { return InternalCategory; }
        }

        // 执行动作时的逻辑
        public override void Execute(object parameter)
        {
            var global = GlobalInterface.Instance;
            var coreInterface = global.COREInterface13;

            try
            {
                // 创建茶壶几何体
                IClass_ID teapotClassId = global.Class_ID.Create(
                    (uint)BuiltInClassIDA.TEAPOT_CLASS_ID,
                    (uint)BuiltInClassIDB.TEAPOT_CLASS_ID);

                // 创建几何体实例
                object obj = coreInterface.CreateInstance(
                    SClass_ID.Geomobject,
                    teapotClassId as IClass_ID);

                if (obj == null)
                {
                    coreInterface.PushPrompt("无法创建茶壶对象");
                    return;
                }

                // 将对象转换为 IObject
                var teapotObject = obj as IObject;
                if (teapotObject == null)
                {
                    coreInterface.PushPrompt("无法将对象转换为 IObject");
                    return;
                }

                // 创建场景节点
                IINode node = global.COREInterface.CreateObjectNode(teapotObject);

                // 正确的方法:将节点添加到场景中
                coreInterface.RootNode.AttachChild(node, false);

                // 获取参数块
                IAnimatable anim = teapotObject as IAnimatable;
                if (anim != null)
                {
                    IIParamBlock2 pb = anim.GetParamBlock(0);
                    if (pb != null)
                    {
                        // 设置茶壶参数
                        // 参数索引和意义:
                        // 0: 半径 (Radius)
                        // 1: 分段数 (Segments)
                        // 2: 平滑 (Smooth)
                        // 3: 生成贴图坐标 (Generate Mapping Coords)
                        pb.SetValue(0, coreInterface.Time, 25.0f, 0);  // 半径25单位
                        pb.SetValue(1, coreInterface.Time, 4, 1);       // 分段数4
                        pb.SetValue(2, coreInterface.Time, 1, 2);    // 平滑
                        pb.SetValue(3, coreInterface.Time, 0, 3);   // 不生成贴图坐标
                    }
                }

                // 选择新创建的节点
                coreInterface.SelectNode(node, true);

                // 重绘视图
                coreInterface.RedrawViews(coreInterface.Time, RedrawFlags.Normal, null);

                coreInterface.PushPrompt("茶壶创建成功!");
            }
            catch (Exception ex)
            {
                coreInterface.PushPrompt($"Error: {ex.Message}");
            }
        }

        // 内部动作文本
        public override string InternalActionText
        {
            get { return "Jackletter-ActionText!"; }
        }

        // 内部分类名称
        public override string InternalCategory
        {
            get { return "Jackletter-Category"; }
        }
    }
}

编译运行

编译完成后,已自动将 ActionPluginDemo.dll 丢进 bin\assemblies目录,如下:

然后,启动3dmax

配置菜单

启动后,点击 "自定义"-> "菜单编辑器",在弹出的窗口中搜索 "jackletter"(类别切换成所有类别),会看到下面收到的我们定义的插件信息,将它拖拽到右面,然后点击保存,在弹框提示中选择"保存",我们可以在文件弹出的菜单配置文件保存框中写入自己喜欢的名字(我写的是test),主要步骤看下图:

观察菜单效果

配置完成后,我们回到3dsmax软件,点击主菜单"文件",会看到我们的插件,点击它,效果如下:

补充项:如何调试代码

  • 对于3dmax刚启动时的代码
    看代码注释,只要将 System.Diagnostics.Debugger.Launch(); 放开就行了。
  • 对于webapi或其他的代码,可以采用附加到进程的方法,如下:
    快捷键:ctrl+alt+p

    然后打个断点:

    然后浏览器访问以下就可以了:
相关推荐
1314lay_10079 小时前
.NET 7.0在.NET Core Web API中实现限流
.net·.netcore
步步为营DotNet9 小时前
深入探究.NET中Stream:灵活高效的数据流处理核心
服务器·数据库·.net
1314lay_100710 小时前
C# .Net 7.0 Core添加日志可视化
visualstudio·c#·.net·.netcore
时光追逐者12 小时前
C#/.NET/.NET Core技术前沿周刊 | 第 66 期(2026年1.12-1.18)
c#·.net·.netcore
一个帅气昵称啊13 小时前
.Net C# AI 如何实现联网搜索
人工智能·c#·.net
初级代码游戏1 天前
C#:程序发布的大小控制 裁剪 压缩
c#·.net·dotnet·压缩·大小·发布·裁剪
weixin_421994781 天前
重复的力量 - 循环
.net·.netcore
Liust1 天前
扩展方法+泛型+委托+Lambda 联合使用
.net
一叶星殇1 天前
C# .NET 如何解决跨域(CORS)
开发语言·前端·c#·.net