Implementation
Details
In order
to understand how the implementation works, we should look at how MaxScript
interacts with .NET systems. .NET assemblies are .dll files which contain code
in the form of CIL -- or the Common Intermediate Language. An assembly is
loaded by MaxScript using the LoadAssembly function in MaxScript's DotNet
struct. Once an assembly is loaded, all of the public data types, classes,
events, and objects are visible to MaxScript through wrappers. MaxScript can
interact with these wrappers, and in turn, the wrappers can interact with the
Common Language Runtime (CLR).
Before
jumping directly into how .NET applications can be integrated with 3DS Max,
there is software you will need. The first and most obvious program is 3DS Max
9.0 or later. The version number is important in this case because the ability
for MaxScript to interact with .NET programs was not introduced until version
9.0.
Once you
have 3DS Max 9.0 installed, you will need a way to develop .NET applications.
.NET applications can be built by various IDEs (Integrated Development Environments),
but the one we recommend is Microsoft Visual C# Express Edition 2005. The
express edition series of Visual Studio is free to download and develop on, and
we have used similar versions of Visual Studio for our systems integration. The
reason the 2005 version is specifically needed is that it installs the .NET
Framework version 2.0, which is what MaxScript expects to interact with.
Now that
all the needed software is installed and running, it's time to look at the
means of communication between .NET and MaxScript. While MaxScript can call
.NET functions directly, .NET has no awareness of MaxScript or 3DS Max's API.
Therefore, any time .NET code needs to trigger 3DS Max functionality, it must
do so through the use of events which are handled by MaxScript functions as
callbacks. Our example will illustrate how a .NET event can be tied to a
MaxScript function.
The
actual implementation of the .NET integration of our tool chain with 3DS Max is
a complex set of MaxScript and C# code. Because of the thousands of lines of
MaxScript and C# code involved, we want to provide a simplified example to
illustrate how to leverage this new feature in 3DS Max. We will lay out a pair of MaxScript and C#
programs which tie 3DS Max functionality to a .NET control. This example, though small, encompasses all
of the functionality required to complete our large scale integration.
In the following example code, we
will illustrate how to use function calls and callbacks / events to control 3DS
Max and pass information from MaxScript into a .NET control.
MaxScript Code
[This function gets called when the .NET control raises the CreateSphere event.]
function OnCreateSphere obj args =
(
Sphere segs:62 wireColor:white
)
[This function gets called when the .NET control raises the CreateLight event. The 'args' argument has been filled out by the .NET control, and is passed to MaxScript for processing. In this case, it contains a color.]
function OnCreateLight obj args =
(
new_light = OmniLight()
new_light.color = Color args.LightColor.R args.LightColor.G args.LightColor.B
)
[Loads the .NET assembly containing the C# code. This is really where the integration happens.]
DotNet.LoadAssembly "c:/projects/dotnetassemblies/MaxScriptIntegrationDemo.dll"
[This is the rollout which actually houses the .NET control itself, which is accessible because we loaded the assembly on the line above.]
rollout r_MaxScriptIntegration ".NET Integration" width:300 height:400
(
[This is how you instantiate the .NET control using MaxScript. It is a fully qualified name, with “Namespace.Name”]
local dotnet_object = "MaxScriptIntegrationDemo.MaxScriptIntegrationDemoControl"
local custom_control = DotNetObject dotnet_object
[Now create a .NET panel on the rollout to contain the .NET control instantiated above. A panel is a control provided by the .NET framework.]
DotNetControl dnc_dotNetPanel "System.Windows.Forms.Panel" width:290 height:360
[These buttons are used to call .NET functions when pressed]
button btn_sayHello "Say Hello" across:2 align:#center
button btn_sayGoodbye "Say Goodbye And Leave" align:#center
[Do some work to hook up events from our .NET control to the functions declared at the top of this code.]
on r_MaxScriptIntegration open do
(
[This is how a .NET event is tied to a MaxScript function. DotNet.AddEventHandler is a MaxScript function, and takes three arguments. The first argument specifies the .NET control on which to listen for events. The second argument specifies the actual event to listen for by name on the .NET control. The third argument specifies the MaxScript function which will handle this event.]
DotNet.AddEventHandler custom_control "CreateSphere" OnCreateSphere
DotNet.AddEventHandler custom_control "CreateLight" OnCreateLight
[This line adds our custom .NET control to the panel control. Every control has a .Controls property, which is a list of child controls, and we can add or remove other controls from this list.]
dnc_dotNetPanel.Controls.Add custom_control
)
on btn_sayHello pressed do
(
[When the “Say Hello” button is pressed, call the function SayHelloUserControl() on our custom .NET control.]
custom_control.SayHelloUserControl()
)
on btn_sayGoodbye pressed do
(
[When the “Say Goodbye” button is pressed, call the function Say() on our custom .NET control, which takes a string from MaxScript and displays a modal message box using the string passed to the Say() function.]
custom_control.Say "Goodbye! Press OK to close the Max rollout."
[When the user clicks “OK” on the message box, this line will be executed, closing the rollout.]
destroyDialog r_MaxScriptIntegration
)
)
createDialog r_MaxScriptIntegration;
C# Code
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Text;
using System.Windows.Forms;
/// The name of the namespace we want our
/// demo to exist in.
namespace MaxScriptIntegrationDemo
{
/// <summary>
/// A simple control used to illustrate integration between .Net and MaxScript.
/// The control contains a "Create Circle" button and a "Create Omni Light"
/// button. The buttons have event associated with them which can be
/// registered for by MaxScript. Be sure that all objects that
/// need to communicate with MaxScript inherit directly or indirectly from
/// System.ComponentModel.Component or events won't work correctly.
/// </summary>
public partial class MaxScriptIntegrationDemoControl : UserControl
{
public MaxScriptIntegrationDemoControl()
{
InitializeComponent();
}
/// <summary>
/// The CreateCircle event can be registered for by MaxScript so that when
/// the event is raised MaxScript can take the appropriate action.
/// </summary>
public static event EventHandler CreateCircle;
/// <summary>
/// This event will be called when the "Create Circle" button is clicked on the
/// .Net user control. If any other object (including MaxScript) is registered
/// for the event they will be notified.
/// </summary>
/// <param name="sender">The button that was pressed.</param>
/// <param name="e">Generic EventArgs</param>
private void _makeCircleButton_Click(object sender, EventArgs e)
{
if (CreateCircle != null)
{
CreateCircle(sender, e);
}
}
/// <summary>
/// The CreateLight event can be registered for by MaxScript so that when
/// the event is raised MaxScript can take the appropriate action.
/// </summary>
public static event EventHandler CreateLight;
/// <summary>
/// This event will be called when the "Create Omni Light" button is clicked
/// on the .Net user control. If any other object (including MaxScript)
/// is registered for the event they will be notified.
/// </summary>
/// <param name="sender">The button that was pressed.</param>
/// <param name="e">Generic EventArgs</param>
private void _makeOmniLightButton_Click(object sender, EventArgs e)
{
if (CreateLight != null)
{
/// We want the user to pick a color before raising the event so we
/// can pass that information along with our custom LightEventArgs.
if (_colorDlg.ShowDialog() == DialogResult.OK)
{
CreateLight(sender, new LightEventArgs(_colorDlg.Color));
}
}
}
/// <summary>
/// Simple method to show how functions on .Net objects can be called from
/// MaxScript.
/// </summary>
public void SayHelloUserControl()
{
MessageBox.Show("Hello");
}
/// <summary>
/// Simple method to show how functions on .Net object can be called from
/// MaxScript.
/// </summary>
/// <param name="text">What should be printed in the .Net MessageBox
/// </param>
public void Say(string text)
{
MessageBox.Show(text);
}
}
/// <summary>
/// Sample custom EventArgs to illustrate how data can be passed to
/// MaxScript through events.
/// The class encapsulates a color value for passing with the CreateLight event.
/// </summary>
public class LightEventArgs : EventArgs
{
/// <summary>
/// .Net Color object.
/// </summary>
private Color _color;
/// <summary>
/// Constructor
/// </summary>
/// <param name="color">The color to be passed to MaxScript when
/// creating a light.</param>
public LightEventArgs(Color color)
{
_color = color;
}
/// <summary>
/// Property: Used to access the color value.
/// </summary>
public Color LightColor
{
get
{
return _color;
}
}
}
}
|
Much obliged.
This is actually not quite right :-)
http://msdn.microsoft.com/en-us/library/y31yhkeb(VS.80).aspx
The .NET integration-features that have been introduced into MAX is a step in the right direction, but there are some shortcomings that may benefit from the act of being brought to the surface, in case someone out there were dancing a mazurka of joy, thinking they could throw out the MAX sdk come blue morning.
The ability to interface with MAX datatypes from the .NET assembly is practically nonexistant. As a result, shuffling large portions of data to the .NET-side therefore requires iterating the data. This e.g. hardens the possibilities of quickly performing a very common plugin task: munching geometry.
Hopefully, integration will become a bit tighter in the future, although I would rather prefer they put their weight on making MAXscript run faster.
I had such a bad time with making the UI for the tool. I could never make it as good as I would have liked.
IIRC Maxscript cannot access everything that's visible in the UI. For example I had issues with using the "loft" tool in Maxscript. Not sure if anything's changed now. I used Maxscript essentially for raising events and for the UI. The majority of the work was done in the 3DS Max Plugins.
Could you tell us something about the formats in which you had exported the data from 3DS Max?
"For some developers, integration with 3DS Max may mean rewriting thousands of lines of code within .NET, and each developer will need to assess how reasonable that would be to achieve."
I've been in positions where I needed to get a lot of old C code into C#. Lucky for me, it was in a DLL, and C# makes running DLL functions relatively easy with platform invoke. Microsoft has a tutorial at http://msdn.microsoft.com/en-us/library/aa288468.aspx.
You'll have to marshal data between the DLL and your C# code, but there ends up being about 10 basic types of marshals (string, array, pointers, etc etc), and once you've got a handle on them, building a class that will run all your DLL functions for you becomes pretty easy.
Anyways, just my two cents. With platform invoke, you'll still have to write a good bit of C# code, but you don't have to rewrite (and re-debug) the code that does most of the work.