Object Creation Methods

3DS Max Plug-In SDK

Object Creation Methods

See Also: Class BaseObject, Class Interface, Class CommandMode, Class MouseCallBack, Class ClassDesc, Class Matrix3, Class IDerivedObject.

Overview

This section discusses several different approaches a developer may use to manage the object creation process for their plug-in.

In many cases the creation process of an object is handled by the system. For example, in the procedural sphere, the developer simply provides a callback object to handle the user / mouse interaction. This is done using the method BaseObject::GetCreateMouseCallBack(). This method returns a callback object that handles the user input during the creation process. The system takes care of calling this callback as the user works with the mouse. It also takes care of creating the instance of the plug-in class, creates the node in the scene, and sets the node's object reference to point to the plug-in object. Thus the system handles most of the work.

In certain cases a developer may need to manage the creation process at a lower level in order to provide special functionality. Two methods of the plug-in's Class Descriptor, BeginCreate() and EndCreate(), allow the plug-in to manage the process. When the system is about to create a plug-in object BeginCreate() is called. If the plug-in does not wish to have a custom creation process it can return 0 from this method and the system will invoke the default process. The default implementation of this method returns 0, so if the plug-in does not override it the system handles the creation. If the plug-in does require a custom creation phase, it does so in its implementation of this method. When it is finished it returns nonzero to indicate it does not want the default process invoked.

The target camera is an example of a plug-in that must handle its own creation process. This is because it creates two nodes in the scene: the camera and the target. It also must link the camera to the target using a lookat controller that ensures the camera is always 'looking at' the target. The system-managed creation procedure using GetCreateMouseCallBack() is not adequate to meet the needs of this plug-in. Therefore the target camera implements ClassDesc::BeginCreate() and ClassDesc::EndCreate(). You may review the source code for this plug-in in \MAXSDK\SAMPLES\OBJECTS\CAMERA.CPP.

Note that GetCreateMouseCallback() is pure virtual. Therefore plug-ins derived from BaseObject that use ClassDesc::BeginCreate() must still provide a NULL implementation of this method. This may be done by implementing it as:

CreateMouseCallBack* GetCreateMouseCallBack() {return NULL;}

Another plug-in that manages its own creation process by implementing BeginCreate() and EndCreate() is the Ring Array system.

As a final note about these methods, a plug-in developer can force the system to call ClassDesc::EndCreate() by calling the method Interface::StopCreating(). This terminates the creation process.

Objects, Nodes and Derived Objects

A developer may wish to create instances of the standard 3ds max primitives. For example, an import / export plug-in might want to create standard 3ds max spheres, cones, tori, etc. APIs are provided so a plug-in can create objects and nodes in the scene. Additionally plug-ins can create derived objects to add modifiers to objects. This section introduces the methods required and provides some sample code demonstrating their use.

The standard 3ds max primitives, or in fact any plug-in procedural object that 3ds max has registered, may be created given the ClassID and SuperClassID of the object. This is done using the following method:

virtual void *CreateInstance(SClass_ID superID, Class_ID classID)=0;

This creates an instance of a registered class. This will call Create() on the class descriptor. This method returns a pointer to the specified object. For reference see List of Class IDs and List of Super Class IDs. Note: This method is from class Interface, but there is also a global version of this API that may be used at any time (defined in \MAXSDK\INCLUDE\PLUGAPI.H).

To set the parameters for these objects methods of BaseObject are available:

virtual IParamArray *GetParamBlock();

If an object or modifier wishes, it can make its parameter block available for other plug-ins to access. This method returns a pointer to the item's parameter block.

virtual int GetParamBlockIndex(int id);

If a plug-in makes its parameter block available (using GetParamBlock()) then it will need to provide #defines for indices into the parameter block. These #defines should not be directly used with the parameter block but instead converted by this function that the plug-in implements. In this way if a parameter moves around in a future version of the plug-in the #define can be remapped.

For reference see List of Parameter Block IDs. This lists all the parameter block indices for the standard plug-ins that ship with MAX.

Note: To use these parameter block IDs, make sure you have the following statement in your source code:

#include "istdplug.h"

To create a node in the scene given an object pointer use this method from class Interface:

virtual INode *CreateObjectNode( Object *obj )=0;

This creates a new node in the scene with the given object. Methods of INode allow the node to be named, and assigned a transform controller.

Developers may also create derived objects and add modifiers to objects. Methods of class IDerivedObject are available for this purpose. For example:

virtual void AddModifier(Modifier *mod, ModContext *mc=NULL,

 int before=0)=0;

Adds a modifier to this derived object.

The following code, from \MAXSDK\SAMPLES\UTILITIES\UTILTEST.CPP, demonstrates the use of these methods.

#include "istdplug.h"

void UtilTest::MakeObject()

 {

 // Create a new object through the CreateInstance() API

 Object *obj = (Object*)ip->CreateInstance(

  GEOMOBJECT_CLASS_ID,

  Class_ID(CYLINDER_CLASS_ID,0));

 assert(obj);

 // Get a hold of the parameter block

 IParamArray *iCylParams = obj->GetParamBlock();

 assert(iCylParams);

 // Set the value of radius, height and segs.

 int rad = obj->GetParamBlockIndex(CYLINDER_RADIUS);

 assert(rad>=0);

 iCylParams->SetValue(rad,TimeValue(0),30.0f);

 int height = obj->GetParamBlockIndex(CYLINDER_HEIGHT);

 assert(height>=0);

 iCylParams->SetValue(height,TimeValue(0),100.0f);

 int segs = obj->GetParamBlockIndex(CYLINDER_SEGMENTS);

 assert(segs>=0);

 iCylParams->SetValue(segs,TimeValue(0),10);

 // Create a derived object that references the cylinder

 IDerivedObject *dobj = CreateDerivedObject(obj);

 // Create a bend modifier

 Modifier *bend = (Modifier*)ip->CreateInstance(

  OSM_CLASS_ID,

  Class_ID(BENDOSM_CLASS_ID,0));

 // Set the bend angle

 IParamArray *iBendParams = bend->GetParamBlock();

 assert(iBendParams);

 int angle = bend->GetParamBlockIndex(BEND_ANGLE);

 iBendParams->SetValue(angle,TimeValue(0),90.0f);

 // Add the bend modifier to the derived object.

 ModContext* mc = new ModContext(new Matrix3(1), NULL, NULL);

 dobj->AddModifier(bend, mc);

 // Create a node in the scene that references the derived object

 INode *node = ip->CreateObjectNode(dobj);

 // Name the node and make the name unique.

 TSTR name(_T("MyNode"));

 ip->MakeNameUnique(name);

 node->SetName(name);

 // Redraw the viewports

 ip->RedrawViews(ip->GetTime());

 }

Here is another piece of sample code that creates a shape object using the CreateInstance API. This example creates a circle, but one could modify it to create lines or other shapes.

// Vector length for unit circle

#define CIRCLE_VECTOR_LENGTH 0.5517861843f

 

void Utility::Test() {

 Interface *ip = theUtility.ip;

 Object *obj =

  (Object*)ip->CreateInstance(SHAPE_CLASS_ID, splineShapeClassID);

 

 SplineShape *ss =(SplineShape *)obj;

 BezierShape &ashape = ss->GetShape();

 ashape.NewShape();

 Spline3D *spline = ashape.NewSpline();   

 

 float radius = 300.0f;

 float vector = CIRCLE_VECTOR_LENGTH * radius;

 

 for(int ix=0; ix<4; ++ix) {

  float angle = 6.2831853f * (float)ix / 4.0f;

  float sinfac = (float)sin(angle), cosfac = (float)cos(angle);

  Point3 p(cosfac * radius, sinfac * radius, 0.0f);

  Point3 rotvec = Point3(sinfac * vector, -cosfac * vector, 0.0f);

  spline->AddKnot(SplineKnot(KTYPE_BEZIER,LTYPE_CURVE,

   p,p + rotvec,p - rotvec));

  }

 spline->SetClosed();

 spline->ComputeBezPoints();   

 ashape.UpdateSels();

 ashape.InvalidateGeomCache();

 

 INode *node = ip->CreateObjectNode(obj);

 TSTR name(_T("MySplineNode"));

 ip->MakeNameUnique(name);

 node->SetName(name);

 

 // Redraw the views

 ip->RedrawViews(ip->GetTime());

}

Other Methods of Object Creation - NonMouseCreate()

A method of class Interface named NonMouseCreate() may be used to create a new object / node without going through the usual create mouse proc sequence. This call must be made when the system is ready to create -- that is during execution of the system-provided default object creation process. To understand how this works consider the example of the procedural sphere.

When the user selects Sphere from the 3ds max UI, an instance of the sphere is created and its parameters appear. There is not a node in the scene, but an instance of the object is created. The user can then edit the sphere parameters before it is put in the scene. After a sphere is created, the creation process is in a different phase -- the user is editing the parameters of the sphere that was just created -- not the one about to be created. Therefore the first sphere is a special case -- the user can edit the parameters both before and after it is created. Subsequent spheres can only have their parameters edited after they are created.

The procedural sphere's user interface allows the user to type in a center position and radius value and then press a 'Create' button. This creates a new sphere in the scene with these parameters. When the button is pressed it calls the method NonMouseCreate().

When the NonMouseCreate() method is used, the default creation manager looks to see if a sphere has just been created in the scene. If there is one, it ends the creation phase on that sphere and creates a new one and puts it in the scene. If there is not a newly-created sphere yet, NonMouseCreate() will simply put the object into the scene.

Thus calling NonMouseCreate() treats the object creation just as if the user clicked the mouse in the viewport to create the object -- but they cannot interactively adjust the parameters. The method takes a single Matrix3 argument which is the transformation relative to the construction plane. In the code below, the translation part of this matrix is set to the values the user typed in for the X, Y, and Z location of the sphere. The user is thus typing in values that are relative to the current construction plane.

The following code is from the procedural sphere. This is the code that is executed if the user presses the 'Create' button in the 'Keyboard Entry' rollup page. This allows the user to type in the values for the sphere's position and radius using the keyboard and then create the object.

BOOL SphereTypeInDlgProc::DlgProc(

 TimeValue t,IParamMap *map,HWND hWnd,UINT msg,

 WPARAM wParam,LPARAM lParam) {

 switch (msg) {

  case WM_COMMAND:

   switch (LOWORD(wParam)) {

    case IDC_TI_CREATE: {

     // The user has pressed the create button...

     // If the radius is zero there is nothing to create

     // so just return.

     if (so->crtRadius==0.0) return TRUE;

     // We only want to set the value if the object is

     // not in the scene.

     if (so->TestAFlag(A_OBJ_CREATING)) {

      so->pblock->SetValue(PB_RADIUS,0,so->crtRadius);

      }

     // Create an identity matrix

     Matrix3 tm(1);

     // Update the translation portion of the matrix to the

     // creation position type-in parameter

     tm.SetTrans(so->crtPos);

     // Call the interface method to create the node...

     // This will use the values in the objects pblock.

     so->ip->NonMouseCreate(tm);

     so->suspendSnap = FALSE;

     // NOTE that calling NonMouseCreate() will cause this

     // object to be deleted. DO NOT DO ANYTHING BUT RETURN.

     return TRUE;

     }

    }

   break;

  }

 return FALSE;

 }

Summary

There are several ways a developer may create objects in the scene. Some rely on the developer providing a custom creation process command mode and mouse proc. Others use the default object creation manager. In either case, developers may create any plug-in object that the system has loaded from its DLL at 3ds max start up. Plug-ins have access to the parameter block of all the standard 3ds max primitives. Developers may also add object modifiers to any of the objects created.