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


#1

Right now I iterate over all nodes and I try to convert them to triangular meshes using something like:

try:
    obj_state = node.EvalWorldState()
    obj = obj_state.Getobj()
    tri_obj = obj.AsTriObject()
except Exception as e:
    log("dump_geometry: geometry not available")
    return None

tri_mesh = tri_obj.GetMesh()

Right now I didn’t care about file-format optimization so I was dumping the same topological information over and over without distinguishing whether the nodes were copied/instantiated or referenced. Well, that’s my question, is there any way to check which type of node (copy/instance/reference) I’m trying to dump?

Thanks in advance.


#2

you can’t know that two nodes are copies
if two nodes have the same object they are instances
if two nodes have the same reference target they are references


#3

@denisT First of all, thanks for your comment, that’s really appreciated, each time I’ve asked in the forum you’ve always been really helpful, out of curiosity, are you an employee from autodesk? :slight_smile:

Anyway, by any chance, do you know which MaxPlus methods I could use here?

In the meantime, diving in the docs


#4

to check is two nodes instances:

node1.GetObjectRef() == node2.GetObjectRef() 

to check is two nodes references:
make a ReferenceMaker from node and check first Reference:

ref1.GetReference(0) == ref2.GetReference(0)

#5

@denisT Thank you very much, I’ll give it a shot tomorrow :wink:


#6

i don’t use MaxPlus… so i mostly quess :slight_smile:


#7

No


#8

Hehe, well… I’ve been using maxplus for almost 1 week and I’m pretty happy about it… the only bad experience I’ve had so far happened when some instance was holding some Point3 instances and when the object going out of scope max crashing unexpectedly (thanks swig!). Because of that each time I added these Point3 into my objects I had to clone/convert them into some python lists. That said, in comparison to the old maxscript… working with python in 3dsmax is a world of happiness :slight_smile:

No

Well, after checking your answers I must to say you know quite a lot about the max internals and this isn’t a big niche, so I assumed you were… :wink:


#9

it’s only because the real world of MaxScript has never been opened to you, like for most others …

and I consider Python in the 3DS MAX (MaxPlus) a cheap fake compared to Python in the Maya (OpenMaya, API 2.0)


#10

@denisT I’ve tested your ideas and I’m still a little bit puzzled…

node1.GetObjectRef() == node2.GetObjectRef() wasn’t giving me the same hash between two instances (which is necessary so == operator will give true) and then, about this one ref1.GetReference(0) == ref2.GetReference(0), I still don’t know how to make a ReferenceMaker from a particular node.

Anyway, so far I’ve come up with this mcve:

import MaxPlus


def node_key(n):
    return str(n).split()[-1]


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


instances_and_references = defaultdict(list)

for node in traverse():
    os = node.EvalWorldState()
    obj_ref = node.GetObjectRef()
    key = node.FindNodeFromBaseObject(obj_ref)
    instances_and_references[node_key(key)].append(node)

print('-' * 80)
for k, v in instances_and_references.items():
    print(k, len(v))

This problem with the above code is that’s just able to distinguish between copies and instances&references…

To test it out:

  • Create a scene with a box and copy the box, run
  • Create a box instance, run
  • Create a box reference, run
  • Create a box reference+meshsmooth modifier, run

You should see the outcome from that scene something like:

('<000000003CC4F4F0>', 1)
('<000000003CC51130>', 4)

which obviously is not what I’m looking for, as I want to be sure my file-format will save only unique topological geometry only once and the above code won’t guarantee that.

Any advice to fix the above code?


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