Converting pointers from MaxPlus to pymxs

Converting pointers from MaxPlus to pymxs

alex
Enthusiast Enthusiast
1,680 Views
11 Replies
Message 1 of 12

Converting pointers from MaxPlus to pymxs

alex
Enthusiast
Enthusiast

Hi,

 

in the previous version of Verge3D (which were MaxPlus-based) we extensively used raw pointers to nodes, textures etc obtained using Wrapper.GetUnwrappedPtr() method. This allowed us passing these pointers to python modules written in C++. In C++ we did simple type casting to restore link to the original object:

 

INode *node = (INode *)nodePtr;

 

This is nor longer the case with maxscript-based pymxs. It comes with refs.getAddr(node) method which according to the documentation should give us pointer to maxscript Value (or MAXWrapper) instance but this code:

 

INode* node = ((Value*)nodePtr)->to_node();
// or 
INode* node = ((MAXWrapper*)nodePtr)->to_node();

 

does not work crashing the plugin.

 

It looks like refs.getAddr() is pointing to something else... Is there some other method to get raw pointer to INode (or Texmap etc) using maxscript/pymxs ?

 

Thanks!

 

0 Likes
Accepted solutions (1)
1,681 Views
11 Replies
Replies (11)
Message 2 of 12

larryminton
Autodesk
Autodesk

What exactly is nodePtr? How are you getting and persisting it? Could it have been garbage collected?

 

The implementation of refs.getAddr is in the sdk sample files - maxsdk\samples\maxscript\mxsagni\refs.cpp:

 

Value*
refs_getaddr_cf(Value** arg_list, int count)
{
	// refs.getAddr<max_obj>
	check_arg_count(getAddr, 1, count);
	ReferenceTarget* refTarg;
	if (arg_list[0] == &undefined)
		refTarg = NULL;
	else if( arg_list[0]->is_kind_of(class_tag(MAXNode)) )
		refTarg = ((MAXNode*)arg_list[0])->node;
	else 
		refTarg = arg_list[0]->to_reftarg();
	MAXScript_TLS* _tls = (MAXScript_TLS*)TlsGetValue(thread_locals_index);
	return_value_tls(IntegerPtr::intern((INT_PTR)refTarg));
}

 

Note that if nodePtr is not a MAXWrapper, the to_reftarg() call will throw a mxs RuntimeError that you will need to catch and handle. Best to check this before the call.

 

if (arg_list[0]->is_kind_of(class_tag(MAXWrapper)))

 

And if a node, and the node has been deleted from the scene, to_node() will throw a mxs RuntimeError.  After making sure derives from MAXWrapper, can cast to MAXWrapper and use the 'deletion_check_test(val)' macro defined in maxsdk\include\maxscript\maxwrapper\mxsobjects.h to see if it was deleted.

 

Larry


Larry Minton
Principal Engineer, M&E-Design Animation
0 Likes
Message 3 of 12

alex
Enthusiast
Enthusiast

Hi Larry,

 

thanks you for your help!

 

nodePtr is a pointer which is passed to custom python method implemented using C++:

static PyObject *extractSkinBonePointers(PyObject *self, PyObject *args) {
  long long nodePtr;
  PyArg_ParseTuple(args, "L", &nodePtr)
  INode* node = ((Value*)nodePtr)->to_node();
  ...
}

In python this function is executed this way:

nodePtr = int(rt.refs.getAddr(node))
skinBonePtrs = maxcpp.extractSkinBonePointers(nodePtr)

I believe, since this python call is made inside some local scope, the node variable will not be garbage collected. As such, there is no need to check it.

 

BTW what is about this code inside getAddr() implementation?

arg_list[0]->is_kind_of(class_tag(MAXNode)

As I understand it won't be executed and instead the execution goes straight to to_reftarg() ? If so, to_reftarg() method executes get_max_object() which in turn should get us the reference to the wrapped object?

0 Likes
Message 4 of 12

larryminton
Autodesk
Autodesk

we are having a discussion internally about your question. For self preservation I have been avoiding python, and one of the other guys working with the python will respond.
For MAXNode, to_reftarg() (and to_object()) return a pointer to the base object. So if want to get the pointer to the node itself, need to use to_node()


Larry Minton
Principal Engineer, M&E-Design Animation
0 Likes
Message 5 of 12

458569
Participant
Participant

I'm currently investigating this. More feedback later today.

Hugo

0 Likes
Message 6 of 12

458569
Participant
Participant

What you get is a ReferenceTarget*

 

	long long refTarg;
	if (!PyArg_ParseTuple(args, "L", &refTarg))
		return NULL;
	ReferenceTarget* pRef = reinterpret_cast<ReferenceTarget*>(refTarg);

 

an INode is a ReferenceTarget. If you are sure your ReferenceTarget is an INode, you can static_cast<INode*>(pRef)

 

Other than that, you don't need to do 

nodePtr = int(rt.refs.getAddr(node))

you can simply do nodePtr = rt.refs.getAddr(node)

 

This solution seems to work on my side (I quickly tried it).

I hope this helps. 

0 Likes
Message 7 of 12

458569
Participant
Participant

To check whether a ReferenceTarget* is a Node (and if the cast can be safely done), something like this would work:

	long long refTarg;
	if (!PyArg_ParseTuple(args, "L", &refTarg))
		return NULL;
	ReferenceTarget* pRef = reinterpret_cast<ReferenceTarget*>(refTarg);
	if (pRef->GetInterface(INODE_INTERFACE))
	{
		INode* pNode = static_cast<INode *>(pRef);
	}
0 Likes
Message 8 of 12

alex
Enthusiast
Enthusiast
Accepted solution

Thank you very much for the explanation! Indeed refs.getAddr() returns the pointer to ReferenceTarget, however the root of the issue is completely different.

 

It looks like there is a bug in 3d Max versions prior to 2019 (tested all minor updates from 2018 to 2018.4). The pymxs.runtime.refs.getAddr() method returns incorrect pointer (maybe due to some implicit conversion to float value which has limited precision bits). Thankfully, the original maxscript version works good which allowed us to implement this hack:

    rt.execute('fn getProperPtr obj = (return trimRight (refs.getAddr(obj) as string) "P")')
    return int(rt.getProperPtr(mEntity))

so basically I'm getting string value inside maxscript and then coerce to integer in Python. I know it looks bad but it works! 

 

In 3ds Max 2019 and later the ref.getAddr() works and returns exactly the same pointer as MaxPlus' GetUnwrappedPtr() method which we used before.

0 Likes
Message 9 of 12

alex
Enthusiast
Enthusiast

BTW, investigating this issue further I found a possible explanation why it's happening.

 

In 3ds Max 2018, the method:

 

 

rt.classOf(rt.refs.getAddr(rt.getNodeByName('Sphere'))

 

 

return Double, while in 2019-2021 it's Integer. Thought it might help you guys.

 

Anyway, thanks for your support!

0 Likes
Message 10 of 12

458569
Participant
Participant

Thanks again Alex,

I just checked and the fix was done in Update1 of 2019, just to confirm what you said.

(before that fix ptrs were wrapped as floats).

Hugo

 

0 Likes
Message 11 of 12

alex
Enthusiast
Enthusiast

Great! We're about to finish rewriting Verge3D to pymxs. I'd say everything looks good except this minor issue we managed to resolve with the workaround.

Thanks Hugo and the team for support!

0 Likes
Message 12 of 12

458569
Participant
Participant

Awesome! 🙂

Hugo

0 Likes