I wouldn’t use MaxPlus for python scripting, at least not only MaxPlus. You should use the pymxs dynamic module, which is a bridge between maxscript to python.
MaxPlus is very incomplete, it was meant to replace Autodesk.Max, that was the .NET api for 3dsmax. But that never happened, it took a boost with MCG, but is still miles away from what you can do with maxscript (or the C++ SDK).
Cheers,
Daniel
Thanks for the advice, although the only thing I can complain about embedded python in 3dsmax is basically:
- Modern python (ie:>=3.6 at least) not supported
- Autogenerated documentation of maxplus where you’ll be guessing mostly of the time even after you’ve read the counterpart c++ wrapped stuff
- Max should detect somehow whether your python has made something nasty and allow to break the process instead crashing, starting max over and over till you catch the bug is really time consuming
That said… so far I’m pretty happy with maxplus+python and everything I’ve wanted to implement so far has been implemented, although it’s true it took quite a lot of time effort… about pymxs, yeah, I’ve used slightly and it’s really handy for certain tasks.
Anyway, please let’s try to no go offtopic here, does anyone know how to tweak my above pasted code to detect copies/instances/references?
Thanks in advance!
Something like this, if I got what you wanted.
from collections import defaultdict
from pymxs import runtime as rt
def node_key(n):
return str(n).split()[-1]
def traverse():
nodes = []
root_node = rt.rootNode
def _traverse(node):
if node != root_node:
nodes.append(node)
for c in node.children:
_traverse(c)
_traverse(root_node)
return nodes
instances_and_references = defaultdict(list)
for node in traverse():
clones = []
rt.instancemgr.getinstances(node, pymxs.mxsreference(clones))
instances_and_references[node_key(node.name)] = clones
print('-' * 80)
for k, v in instances_and_references.items():
print(k, len(v))
First of all, thank you very much for your attempt, which btw is much nicer and lighter than mine.
Unfortunately the outcome of that script is similar to the one I’ve pasted above, it doesn’t distinguish between instances & references.
Why is this important, imagine I’m dumping the topological information of all nodes from the scene, now, let’s say there are 3 boxes instantiated and 2 boxes references (and with some modifiers applied on top of it). Now, let’s say I dump the information of 1 of the instances and then I cache the other ones… as a result, the information of the referenced boxes would be lost… That’s why I’m interested to distinguish between copy/instance/referenced objects.
Does it make sense?
i’m looking now in MaxPlus documentation and see INode method GetObject
i guess that two instances have to have the same Object returned by this method
this Object as I guess is equivalent of c++ SDK:
Object* osobj = node->GetObjectRef()->Eval(t).obj;
which must be same for instances.
if they are different for two objects, the nodes can still be references but it’s not your case
@denisT Imagine this case:
If I use GetObject
as suggested:
instances_and_references = defaultdict(list)
for node in traverse():
instances_and_references[node.GetObject()].append(node)
The final dictionary will be something like:
(<MaxPlus.Object; proxy of <Swig Object of type 'Autodesk::Max::Object *' at 0x00000000509CF480> >, 1)
(<MaxPlus.Object; proxy of <Swig Object of type 'Autodesk::Max::Object *' at 0x00000000509CF4B0> >, 1)
(<MaxPlus.Object; proxy of <Swig Object of type 'Autodesk::Max::Object *' at 0x00000000509CF3C0> >, 1)
Why? Cos those MaxPlus.Object returned by GetObject are not “equal”.
When I said “… wasn’t giving me the same hash between two instances” I meant the objects weren’t equal (__eq__), to use object as keys in dictionary they must be hashable and to retrieve their values (__getitem__) they must be equal, for more info check this out, and here’s a little snippet to clarify these concepts:
from collections import defaultdict
class foo():
def __init__(self, n):
self.number = n
def __hash__(self):
return self.number % 2
def __eq__(self, other):
return self.__hash__() == other.__hash__()
instances = defaultdict(list)
instances[foo(2)].append(2)
instances[foo(4)].append(4)
instances[foo(6)].append(6)
print(dict(instances))
@denisT , @dgsantana Guys, ok, let’s forget for a moment about python-maxplus as using maxscript from python is straightforward, now, consider this mcve:
tp_original = Teapot()
$Teapot:Teapot001 @ [0.000000,0.000000,0.000000]
tpi1 = instance tp_original
$Teapot:Teapot002 @ [0.000000,0.000000,0.000000]
tpi2 = instance tp_original
$Teapot:Teapot003 @ [0.000000,0.000000,0.000000]
tpi3 = reference tp_original
$Teapot:Teapot004 @ [0.000000,0.000000,0.000000]
InstanceMgr.GetInstances tp_original &instances
4
instances
#($Teapot:Teapot004 @ [0.000000,0.000000,0.000000], $Teapot:Teapot003 @ [0.000000,0.000000,0.000000], $Teapot:Teapot002 @ [0.000000,0.000000,0.000000], $Teapot:Teapot001 @ [0.000000,0.000000,0.000000])
In this case, my point is perfectly clear, as you can see InstanceMgr.GetInstances
will return both instances & references but that’s still not good enough… how would you check which elements from that array instances
are references?
Ok, now I see what you want. But it’s a bit difficult to check (at least I’m not recalled of any way in maxscript/maxplus, only in the SDK) to see if it’s a ref or an instance. Since a reference is an instance, but with a “new” modifier stack (Derived Object with a ref to another Derived Object). You can check this here, maybe it will shed some light or give you some other idea.