Notifications
Clear all

[Closed] SDK: Unsavable or self-deleting objects if conditions are not met?

The plugin I am working on creates several scene nodes which are useful while the plugin interface is up, but are useless without it and just clutter up the scene. I have these nodes delete themselves when the UI is closed, but if the scene is saved while they still exist, or in certain undo conditions, they may end up in the scene without the UI to control them.

I’m wondering if there is some way to a) prevent a particular node or nodes from being saved along with the scene, and/or b) cause the nodes to delete themselves if they exist in the scene without the plugin interface.

18 Replies
1 Reply
(@denist)
Joined: 11 months ago

Posts: 0

why do you not like to have ‘auxiliary’ nodes in the file? – they don’t make sense, they make scene looks ‘dirty’, anything else…

maybe it’s just enough to make them invisible for users?

You might be able to do a ‘pre-save’ callback, and detect right before max wants to save.
then you can remove the nodes, and create them again afterwards if needed.

I do not have specific code for you, but it might point you in the right direction.

There is some callback related examples here:

http://docs.autodesk.com/3DSMAX/16/ENU/3ds-Max-SDK-Programmer-Guide/files/GUID-55DBA3E2-A9B1-43F4-9BC3-F9B80CB42E64.htm

I’ve been working on other things in the meantime, but I’m now at the point where I’m coming back to this. The link given above describes three separate systems for monitoring node events, and I’m not really even sure which one I should be looking at.

I still think that deleting an object immediately prior to save, then restoring it after the save is a bit of a roundabout way of making something unsavable. But if that’s the way to go about it I’m game. If anyone does happen to have any code examples they could point me to it would help immensely, but any advice at all is of course appreciated.

So I think I’ve made a bit of progress, but I’ve hit a wall. I have the “delete object prior to save” part working, but am stuck on the “bring it back after the save” part. The solution I am working on currently looks something like this:

class UnObject : public UtilityObj {
	// ...

	//Constructor/Destructor
	UnObject();
	~UnObject();

	static void PreSave(void *param, NotifyInfo *info);
	static void PostSave(void *param, NotifyInfo *info);
};
static UnObject theUnObject;

void UnObject::PreSave(void *param, NotifyInfo *info) {
	theHold.Begin();
	if (theHold.Holding()) theHold.Put(new restoreObj);
	if (theUnObject.TargetNode) { theUnObject.TargetNode->Delete(0, TRUE); }
	if (theHold.Holding()) theHold.Put(new restoreObj(TRUE));
	theHold.Accept("PreSave");
}

void UnObject::PostSave(void *param, NotifyInfo *info) {
	if (theUnObject.TargetNode) { theHold.Restore(); }
}

UnObject::UnObject() {
	//...
	RegisterNotification(PreSave,  this, NOTIFY_FILE_PRE_SAVE);
	RegisterNotification(PostSave, this, NOTIFY_FILE_POST_SAVE);
}

UnObject::~UnObject() {
	UnRegisterNotification(PreSave,  this, NOTIFY_FILE_PRE_SAVE);
	UnRegisterNotification(PostSave, this, NOTIFY_FILE_POST_SAVE);
}

Now, I’m noticing a couple things. First, I can’t seem to make the pre-save callback an undoable action. Second, I can’t perform a restore from the post-save callback. So I am guessing that these callbacks operate outside the normal undo/redo system.

Okay, my most recent approach is:
Pre-save: store all relevant information about the node in question, then delete the node
Post-save: use the stored information to rebuild the node

At first it appears to work just fine, but it’s very easy to break. With this setup, if I adjust the node in any way, save, and then perform an undo, Max crashes. It’s obvious enough to me why this is happening: Max is trying to apply the undo action to the original node which no longer exists. However, I am unsure how to proceed from here.

Is there some way to make Max register the replacement node as having the same identity as the original (i.e. so that actions performed by the undo will be applied to the replacement instead of the old, non-existent one)? Or should I be approaching this differently?

The relevant code is below. “TargetNode” is simply a helper object with a paramblock2 to control its size, visualization, and directional axis. For practical purposes it could just as easily be replaced by a point helper or anything else.

// =================
// Plugin base class
// =================
class UnObject : public UtilityObj {
	public:
		// Interface
		// ---------
		Interface *ip;
		IUtil	 *iu;
		HWND	  hPanel;

		static INT_PTR CALLBACK DlgProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
		// ---------

		//Constructor/Destructor
		UnObject					 ();
		~UnObject					();

		virtual void BeginEditParams (Interface *ip, IUtil *iu);
		virtual void EndEditParams   (Interface *ip, IUtil *iu);
		virtual void Init			(HWND hWnd);
		virtual void DeleteThis	  () { }
	
		INode		   *TargetNode;
		TargetInterface *TargetITF;

		float		   size;
		int			 viz;
		int			 axis;
		Matrix3		 tm;
		bool			TargetExists;

		static void PreSave  (void *param, NotifyInfo *info);
		static void PostSave (void *param, NotifyInfo *info);
		static void Unfreeze (void *param, NotifyInfo *info);
};
static UnObject theUnObject;

// =============================
// Sent before the file is saved
// =============================
void UnObject::PreSave(void *param, NotifyInfo *info) {
	if (theUnObject.TargetNode && !is_deleted(theUnObject.TargetNode)) {
		if (theUnObject.TargetNode->Selected())	GetCOREInterface()->ClearNodeSelection();

		TimeValue t = GetCOREInterface()->GetTime();
		theUnObject.size = theUnObject.TargetITF->GetSize();
		theUnObject.viz  = theUnObject.TargetITF->GetViz();
		theUnObject.axis = theUnObject.TargetITF->GetAxis();
		theUnObject.tm   = theUnObject.TargetNode->GetObjTMBeforeWSM(t);

		theUnObject.TargetNode->Delete(0, TRUE);
		theUnObject.TargetExists = TRUE;
	}
	else theUnObject.TargetExists = FALSE;
}

// ============================
// Sent after the file is saved
// ============================
void UnObject::PostSave(void *param, NotifyInfo *info) {
	if (theUnObject.TargetExists) {
		TimeValue t		  = GetCOREInterface()->GetTime();
		Interface *ip		= theUnObject.ip; // utility interface
		Object	*TargetObj = (Object *)ip->CreateInstance (HELPER_CLASS_ID, TARGETHELPER_CLASS_ID);

		theUnObject.TargetNode = ip->CreateObjectNode (TargetObj); // create target node
		theUnObject.TargetITF  = GetTargetInterface   (TargetObj); // get target interface
		theUnObject.TargetITF->SetSize	(theUnObject.size);	  // set target size
		theUnObject.TargetITF->SetViz	 (theUnObject.viz);	   // set target viz
		theUnObject.TargetITF->SetAxis	(theUnObject.axis);	  // set target axis
		theUnObject.TargetNode->SetNodeTM (t, theUnObject.tm);	 // set target node transform
		theUnObject.TargetNode->SetName   ("Target");			  // set target name

		ip->RedrawViews(t);
	}
	theUnObject.TargetExists = FALSE;
}

static UnObjectClassDesc UnObjectDesc;
ClassDesc2 *GetUnObjectDesc() { return &UnObjectDesc; }

// Constructor
UnObject::UnObject() {
	iu		 = NULL;
	ip		 = NULL;	
	hPanel	 = NULL;
	TargetNode = NULL;
	TargetITF  = NULL;

	TargetExists = FALSE;

	RegisterNotification(PreSave,  this, NOTIFY_FILE_PRE_SAVE);
	RegisterNotification(PostSave, this, NOTIFY_FILE_POST_SAVE);
	RegisterNotification(Unfreeze, this, NOTIFY_NODE_UNFREEZE);
}

// Destructor
UnObject::~UnObject() {
	UnRegisterNotification(PreSave,  this, NOTIFY_FILE_PRE_SAVE);
	UnRegisterNotification(PostSave, this, NOTIFY_FILE_POST_SAVE);
	UnRegisterNotification(Unfreeze, this, NOTIFY_NODE_UNFREEZE);
}

// ==================================================
// Function called when the utility plugin is started
// ==================================================
void UnObject::BeginEditParams(Interface *ip, IUtil *iu) {
	this->iu = iu;
	this->ip = ip;

	if (!hPanel)
		{ hPanel = CreateDialogParam(hInstance, MAKEINTRESOURCE(IDD_PANEL), GetActiveWindow(), DlgProc, (LPARAM) this); }
	else
		{ SetActiveWindow(hPanel); }
}
	
// Function called when the plugin is deactivating
void UnObject::EndEditParams(Interface *ip, IUtil *iu) { this->iu = iu; }

// ===============================
// Called on WM_INITDIALOG message
// ===============================
void UnObject::Init(HWND hWnd) {
	theUnObject.hPanel = hWnd;
	CenterWindow(hWnd, GetParent(hWnd));
}

// ================
// Dialog Procedure
// ================
INT_PTR CALLBACK UnObject::DlgProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
	TimeValue t   = GetCOREInterface()->GetTime();
	Interface *ip = theUnObject.ip; // utility interface

	switch (msg) {
		case WM_INITDIALOG:	theUnObject.Init(hWnd);	break;

		case WM_COMMAND: {
			switch(LOWORD(wParam)) {
				case IDC_BTN_CREATETARGET: {
					if (theUnObject.TargetNode) theUnObject.TargetNode->Delete(0, FALSE);

					theUnObject.tm = Matrix3(1);
					Object *TargetObj = (Object *)ip->CreateInstance (HELPER_CLASS_ID, TARGETHELPER_CLASS_ID);

					theUnObject.TargetNode = ip->CreateObjectNode (TargetObj); // create target node
					theUnObject.TargetITF  = GetTargetInterface   (TargetObj); // get target interface
					theUnObject.TargetITF->SetSize(100.0f);					// set target size
					theUnObject.TargetNode->SetNodeTM (t, theUnObject.tm);	 // set target node transform
					theUnObject.TargetNode->SetName   ("Target");			  // set target name   

					theUnObject.TargetExists = TRUE;

					ip->RedrawViews(t);
					break;
				}

				case IDCANCEL: {
					if (theUnObject.TargetNode) theUnObject.TargetNode->Delete(0, FALSE);
					theUnObject.TargetNode = NULL; 

					EndDialog(hWnd, FALSE);

					if (theUnObject.iu) {
						theUnObject.iu->CloseUtility();
						theUnObject.iu = NULL;
					}

					theUnObject.ip		= NULL;
					theUnObject.hPanel	= NULL;
					theUnObject.TargetITF = NULL;

					ip->RedrawViews(t);
					theHold.Release();
					break;
				}
			} // end switch(LOWORD(wParam))
		} // end WM_COMMAND
		case WM_LBUTTONDOWN:
		case WM_LBUTTONUP:

		default: return 0;
	}

	return 1;
}

So I was wondering if there is some way to undelete a deleted node in Maxscript? It seems like it should be possible, and would definitely help in what I am trying to do…

personally i like to keep max scene clean. looking in max scene i always try to figure out what every node in scene is for

but working with maya and maya scenes i kinda became used not too much worry about ‘cleanness’ of the scene.
so now i usually keep my ‘auxiliary’ nodes in the scene, but in my plugin i have a solution in case if one of them be deleted

Hi Denis, thanks for replying. My main concern here is that unless they are specifically removed by the plugin itself, the utility could end up constantly creating additional nodes every time it is used. Aside from just feeling like a bad practice, the complexity of the “auxiliary” nodes is depended on the geometry on which they are based, and could end up ballooning the file size of a scene.

i still think that you shouldn’t worry too much about your made ‘dirt’. the good-bad practice matter changes. the size of max file isn’t really caused by number of nodes in it. it’s more about a content of these nodes now

but if we talk about a good practice.

if you have to use ‘auxiliary’ nodes with your tool (plugin), make them your own(custom) class. it prevents the system makes anything weird with them.

without your plugin they will be annoyingly “unknown”. which is good IMHO

Page 1 / 2