Instance Cloning when the Object is referencing the INode


which option do you go for

    • object references new node (original node loses functionality)
    • object references original node (new node has no functionality)
    • object references all nodes (possible nonsensical functionality and other issues and probably not possible)
    • disable instance and reference cloning (can’t seem to find an easy way to do this)
    • other ?


I went for other in the end, creating a NOTIFY_NODE_CLONED Notification handler, not ideal, but a makes any reference or instances clone into a copy clone. Though it doesn’t get “caught” with inst = instance node or ref = reference node in mxs but it does work with maxops.cloneNodes.


I’m sorry I can’t get into this issue deep… very busy right now. But the problem is very interesting, and I have met it before in my tasks. So, as soon as I have time, I will play with it.


heres the handler so far…

static void cloned_notify_proc(void* param, NotifyInfo* info)
	INode* cloned_node = static_cast<INode*>(info->callParam);
	Object* cloned_node_obj = cloned_node->GetObjectRef();
		cloned_node_obj = cloned_node_obj->FindBaseObject(); // skip over any modifers (not that any work on this)
		if(cloned_node_obj->SuperClassID() == GEN_DERIVOB_CLASS_ID) // is it a reference ?
			IDerivedObject* dobj = static_cast<IDerivedObject*>(cloned_node_obj);
			cloned_node_obj = dobj->GetObjRef();  // it is so get what it's derived from
		if(cloned_node_obj->ClassID() != XFORM_MONITOR_CLASS_ID) return; // this clone is not our's to interfere with so bum out

// get the object the the new node is references

	XFormMonitor* cloned_xfm_obj = static_cast<XFormMonitor*>(cloned_node_obj);	
	if(!cloned_xfm_obj || cloned_xfm_obj->GetThisNode() == cloned_node) return; // already a copy clone op

// get the object we are cloning from
	XFormMonitor* src_xfm_obj = static_cast<XFormMonitor*>(param);
	if(!src_xfm_obj || src_xfm_obj->GetThisNode() == cloned_node) return; // catch whether the src node and new node the same ?

// so we now have 2 different nodes and one xfm object so make the cloned_node a nice "unique" xfm object of it's own 
// it's "this node" (cloned_node) will be caught and assigned to the object when the node references it in SetObjectRef.

	XFormMonitor* unique_xfm_obj = static_cast<XFormMonitor*>(src_xfm_obj->Clone());

	if(src_xfm_obj != cloned_xfm_obj) 
		cloned_xfm_obj->DeleteThis(); // delete the derived object the reference was referencing

a couple of notes…

GetThisNode() if a function that returns the node from a INodeTransformMonitor reference that our object maintains.

It’s weird/unusal notify event because it’s not as general as most so every node that is cloned will fire off this event but with our this pointer attached (regardless of class) and if we have 20 of our nodes in the scene that’s 20 different events ready to be fired off when cloning something completely unrelated which is pretty bonkers. It’s was quite amusing and a tad bizarre turning boxes into helpers when instance or reference cloning :slight_smile:

perhaps the more “global” NOTIFY_POST_NODES_CLONED would be a better bet than trying to handle at an individual level though it would still need to be registered with the class and not in each constructor. hmmmm.


turns out the NOTIFY_POST_NODES_CLONED was a much better solution, put the registration in LibInitialize

__declspec(dllexport) int LibInitialize() 
	RegisterNotification(notify_post_nodes_cloned_proc, NULL, NOTIFY_POST_NODES_CLONED);
	return TRUE; 

with the function now looking like this…

struct npnc_callparam
	INodeTab* origNodes; 
	INodeTab* clonedNodes; 
	CloneType cloneType;

void notify_post_nodes_cloned_proc(void *param, NotifyInfo* info)
	npnc_callparam* params  = static_cast<npnc_callparam*>(info->callParam);
	if(params->cloneType == NODE_COPY) return;

	INodeTab& origNodes = *params->origNodes;
	INodeTab& clonedNodes = *params->clonedNodes;
	assert(origNodes.Count() == clonedNodes.Count());

	for(int i = 0; i < origNodes.Count(); ++i)
		INode* orig_node = origNodes[i];
		Object* orig_obj = orig_node->GetObjectRef();
		orig_obj = orig_obj->FindBaseObject();
		if(orig_obj && orig_obj->ClassID() == XFORM_MONITOR_CLASS_ID)
			INode* cloned_node = clonedNodes[i];
			Object* cloned_obj = cloned_node->GetObjectRef();

			XFormMonitor* unique_xfm_obj = static_cast<XFormMonitor*>(orig_obj->Clone());

			if(params->cloneType == NODE_REFERENCE && cloned_obj && cloned_obj->SuperClassID() == GEN_DERIVOB_CLASS_ID)

much neater solution all round


looking into original post i see that the scenario is built-in. so, it’s how is it in max. Do you want to have a ‘special’ clone scenario? maybe it will be easier just add your own ‘clone’ method?


what is ‘instance’ and ‘reference’? (correct me if I’m wrong):

  • two nodes are instances if their RefObjects are the same
  • two nodes are references if their reference targets are the same
  • two nodes are references but not instances if their reference targets are the same but their refobjects are different (other way they are instances)
  • two objects are instances or references if their reference targets are the same

what any other relation can be possible?


there is another case - World Space Modifier Object. But let’s forget about this case for now…


isn’t a reference just a node referencing an IDerivedObject where it’s ObjRef points to the original object


so you have two nodes:

  • NODE_A - node with modifiers
  • NODE_B - some node

and you want to replace Derived Object of NODE_A with ObjRef of NODE_B. Right?


not particularly, every thing works for me as it stands

delete objects

b0 = box()
b1 = reference b0 pos:[40,0,0]
addmodifier b1 (edit_poly())
b2 = instance b1 pos:[80,0,0]

make a changes in the setup the way you want

class FindReferenceInPipe : public  GeomPipelineEnumProc
	Object* obj;

	BOOL ref_found;
	FindReferenceInPipe(Object* _obj) : obj(_obj), ref_found(FALSE) { }
	PipeEnumResult proc(ReferenceTarget *object, IDerivedObject *derObj, int index)
		if(derObj && derObj->GetObjRef() == obj)
			ref_found = TRUE;
			return PIPE_ENUM_STOP;

   def_visible_primitive(areNodesReferences, "areNodesReferences");

// areNodesReferences <node> <node>

Value* areNodesReferences_cf(Value** arg_list, int count)
	check_arg_count(isReference, 2, count);

	INode* first_node = arg_list[0]->to_node();
	INode* second_node = arg_list[1]->to_node();

	Object* first_obj = first_node->GetObjectRef();

	FindReferenceInPipe pipe_enumerator(first_obj->FindBaseObject());
	EnumGeomPipeline(&pipe_enumerator, second_node, true); 

	return pipe_enumerator.ref_found ? &true_value : &false_value;

and seems to work both ways (edited so it matchs the areNodesInstances


hmm… but your method says for instances that they are references as well.

here is how i check the reference relation:

Value* areReferences_cf(Value** arg_list, int count)
	check_arg_count(areReferences, 2, count);
	ReferenceTarget* targ0 = arg_list[0]->to_reftarg();
	Object* obj0 = arg_list[0]->to_node()->GetObjectRef();
	ReferenceTarget* targ1 = arg_list[1]->to_reftarg();
	Object* obj1 = arg_list[1]->to_node()->GetObjectRef();
	return bool_result((targ0 == targ1) && (obj0 != obj1));


hmm… but your method says for instances that they are references as well.

no it doesn’t

areNodesReferences  $box02 $box01 
areNodesReferences  $box01 $box02 


are_references.max (228 KB)

check this file (max 2016) … box003 and box004 are instances but in your method are references as well


arggh head **** :slight_smile: too much wine!


I have to take the place of you. I have Easter today :wink:


scrub that