How to check whether a node is a copy, instance or reference?


#11

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


#12

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!


#13

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))

#14

don’t tell MAX what to do. It doesn’t listen to you anyway :grimacing:


#15

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?


#16

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


#17

returns (same for MXS) both instances and references


#18

what is the HASH?


#19

@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))

#20

@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?


#21

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.

https://help.autodesk.com/view/3DSMAX/2018/ENU/?guid=__developer_3ds_max_sdk_features_modeling_geometry_pipeline_system_pipeline_overview_html


#22

Thanks for the link, I’ll take a look… in the meantime… I’ve found this answer from @denist that looks legit, I’ll check it out


#23

@denisT @dgsantana I think the key here is in this little snippet:

for i in range(node.GetNumRefs()):
    print(i, node.GetReference(i))

The slot number 1 will be different between instances/references. But one important question remains to me, what’s the proper way in python to know whether 2 ReferenceTarget are the same? I was trying to compare them by using the id operator id(ref_target) but that’s not good enough. Tsk, in c++ this would be quite straightforward as there isn’t any wrappers/proxies and you just need to compare raw pointers :frowning:

Any idea?


#24

ok… here is the final solution that works for me:

two nodes are instances or references:

n1.GetBaseObject() == n2.GetBaseObject()

two nodes are exact instances:

n1.GetObjectRef() == n2.GetObjectRef()

two nodes are exact references (not instances):

n1.GetBaseObject() == n2.GetBaseObject() and n1.GetObjectRef() != n2.GetObjectRef()

#25

@denisT Mmmm, I’ve tested your final solution and the out results look coherent, ie: created a scene with 1 original box and then made a copy/instance/reference out of it:

import MaxPlus

def traverse():
    nodes = []
    root_node = MaxPlus.Core.GetRootNode()

    def _traverse(node):
        if node != root_node:
            nodes.append(node)
        for c in node.Children:
            _traverse(c)
    _traverse(root_node)
    return nodes


n1, n2, n3, n4 = traverse()

import itertools
for x in itertools.combinations(traverse(), 2):
    print('-' * 80)
    n1, n2 = x
    print("Comparing {} with {}".format(n1, n2))
    # two nodes are instances or references
    print(n1.GetBaseObject() == n2.GetBaseObject())
    # two nodes are exact instances:
    print(n1.GetObjectRef() == n2.GetObjectRef())
    # two nodes are exact references (not instances):
    print(n1.GetBaseObject() == n2.GetBaseObject() and n1.GetObjectRef() != n2.GetObjectRef())

Here’s the output of the comparisons:

--------------------------------------------------------------------------------
Comparing INode: Box001, <000000003B8A0C90> with INode: Box002, <000000003B8A1AB0>
False
False
False
--------------------------------------------------------------------------------
Comparing INode: Box001, <000000003B8A0C90> with INode: Box003, <000000003B8A21C0>
True
True
False
--------------------------------------------------------------------------------
Comparing INode: Box001, <000000003B8A0C90> with INode: Box004, <000000003B8A28D0>
True
False
True
--------------------------------------------------------------------------------
Comparing INode: Box002, <000000003B8A1AB0> with INode: Box003, <000000003B8A21C0>
False
False
False
--------------------------------------------------------------------------------
Comparing INode: Box002, <000000003B8A1AB0> with INode: Box004, <000000003B8A28D0>
False
False
False
--------------------------------------------------------------------------------
Comparing INode: Box003, <000000003B8A21C0> with INode: Box004, <000000003B8A28D0>
True
False
True

I’ll continue testing out a little bit more… before your suggestion I was hashing the whole topological information of all triangular meshes and realized that way was quite expensive operation, ie: dumping less than 100 teapots took like 30s! that’s just crazy slow… so i’d prefer identify similar objects by making unexpensive comparisons like the ones suggested.

By the way, when you’re doing those == comparisons, would you say is fair to think the raw pointers are the ones being compared? :confused:


#26

yes. it means they are exactly the same object

as i see, your goal is to find all exact instances (not reference) to store only geo/topo/deform information once for all instances. so in you case all instances of the same object have to have the same Object Reference:

n1.GetObjectRef() == n2.GetObjectRef()

if this condition is false you have to store/export these nodes separetly


#27

you can also check it as:

n1.GetObjectRef().GetAnimHandle()
n2.GetObjectRef().GetAnimHandle()

they have the same handle id, which means it’s one Animatable


#28

@denisT First of all, thank you very much for these last comments of yours, they’re really unvaluable information! :wink:

That said, I think all my confusion after your very first answer in this thread started because I didn’t know to explain why GetObjectRefwasn’t creating a python dictionary “properly”, for instance, would you know to explain me the outcome of this snippet?

import MaxPlus
from collections import defaultdict


def traverse():
    nodes = []
    root_node = MaxPlus.Core.GetRootNode()

    def _traverse(node):
        if node != root_node:
            nodes.append(node)
        for c in node.Children:
            _traverse(c)
    _traverse(root_node)
    return nodes

dct = defaultdict(list)

for n in traverse():
    dct[n.GetObjectRef()].append(n)

for k, v in dct.items():
    print(k, len(v))

import itertools
for x in itertools.combinations(traverse(), 2):
    print('-' * 80)
    n1, n2 = x
    print(n1.GetObjectRef() == n2.GetObjectRef())

Create a scene with a bunch of instances… theorically that dictionary should contain 1 key with a list of all instances., right? but it won’t and I still don’t know to explain why not when the n1.GetObjectRef()==n2.GetObjectRef() operator should be True for all of them :confused:


#29

@denisT Let me simplify my previous question, would you know to explain me why the hashes are different here?

for n in traverse():
    print(hash(n.GetObjectRef))
    dct[n.GetObjectRef()].append(n)

#30

First, I am sure that this is a “dynamic” hash (probably related to the pointer address).
If it is “dynamic”, then it would be sufficient just to specify the anim handle. Since it is unique to this scene and continues to be unique to the rest of the scene life we can use it as a key.
But the hash is calculated using ‘Python’ methods. That’s what I guess…

… I continue looking into your snippet and will tell my thoughts later