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


#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


#31

I’ll read a couple of times the beforementioned article https://hynek.me/articles/hashes-and-equality/… but there is definitely one sentence which caught my attention there “The hash of an object must never change during its lifetime.”.

Not sure the automatic maxplus swig wrappers can guarantee this. Usually I use swig myself to wrap a lot of c++ code and it’s something to consider myself from now on… providing proper hashable objects. If they use the internal pointers to calculate the hash and they’re garbage collected maybe that’s why the hash isn’t the same… Anyway, I’ll wait to hear your thoughts about this crutial and interesting subject


#32

Your statement here holds, check this out:

dct = defaultdict(list)

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

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

#33

i see its absolutely safe, and much more ‘3ds max sdk’


#34

the thing is:
– name can be changed
– params can be changed
– the handle id - NEVER


#35

but of course to get the Animatable back you have to use GetAnimByHandle()


#36

@denisT So… from now on, would you say any type of handle in MaxPlus are immutable? Asking this cos in other parts of my exporter I was parsing the string produced by the repr of some objects… :slight_smile:

Anyway, tomorrow I’ll test out intensively GetAnimByHandle to see how it’'ll behave and whether will be faster than my current “solution”.

Thank you very much denis, once again… :wink:


#37

hi guys, I was following the thread but it looks like I cannot use the pymxs.mxsreference function:

'module' object has no attribute 'mxsreference'

any idea why? (Max 2018)

EDIT: i guess that my 3dsmax copy is not recent enough?