Get Top Level Nodes in 3DS MAX

I ran into a situation the other day where I needed all of the top level nodes of a scene, not ALL of the nodes. I also wanted to avoid using the MaxPlus API as it has been deprecated in Python 3. Here’s what I found.

1609223026406-08ZY4PF7QCF5501Q7ANR.png

For this scene, the top level nodes would be “Boxes”, “Pyramid001”, and “Sphere001”. The children of “Boxes” would not be included.

from pymxs import runtime as mxs

top_nodes = list(mxs.rootScene[mxs.name('world')].object.children)

Return ALL Nodes in 3DS MAX

There are times when you need to get all the nodes in a scene. In 3DS MAX, the most common way I’ve seen to get all the nodes is the following:

from MaxPlus import Core

nodes = Core.GetRootNode().Children

This line of code only gets you the top-level nodes. If there are any nested nodes, such as those found in groups, then only the top-most node (i.e. the “parent”) will be returned.

Scene Explorer

Scene Explorer

Scene

Scene

Only “Boxes”, “Pyramid001”, and “Sphere001” will be returned with the above code. The individual “Box00#” objects will NOT be returned. This is the native behavior of 3DS MAX. The parent of the group (which is considered a “Helper“ class) may have some actions done to it, but those actions may not cascade down into the children nodes.

For instance, if you’d like to hide all nodes in the scene, you’d THINK hiding the parent node of a group would hide the children as well.

from MaxPlus import Core

nodes = Core.GetRootNode().Children

for n in nodes:
    n.Hide = True
Updated scene

Updated scene

Updated scene explorer

Updated scene explorer

This doesn’t feel like the expected behavior, but is, in fact, what happens. So we also need to hide the children nodes, no matter how deep into nested groups they are. So when I say we need to get all the nodes…

… I mEAN ALL the nodes.

The way I’ve come up with is to list all children nodes recursively.

from MaxPlus import Core
  
def get_all_nodes(nodes=None):
    """Returns all descendants of a list of nodes.
    If None is provided, it will return all nodes in the scene.

    Args:
        nodes (list[MaxPlus.INode]|None): Nodes from which to find descendants.

    Returns:
        list[MaxPlus.INode]: List of all nodes.
    """
    all_nodes_list = []
    if nodes is None:
        nodes = Core.GetRootNode().Children
    for node in nodes:
        if node.GetNumChildren():
            all_nodes_list.extend(get_all_nodes(node.Children))
        all_nodes_list.append(node)

    return all_nodes_list

This allows you to get ALL nodes. It also allows you to get all nodes within in a group or several groups supplied in a list.

Retrieve Main Window in PyQt/PySide for DCC Apps

If you’re developing within a DCC and you create a new GUI element in PyQt/PySide, such as a QFrame, QMainWindow, or QDialog, you’re going to want the main window of the DCC app as the parent of the new GUI element. This ensures that when we close the main app, the new widget closes as well. Here are the ways I’ve gotten the main window for the most recent DCCs I’ve used:

3DS MAX 2020

reference

from PySide2 import QtWidgets


def get_qmax_main_window():
    """Get the 3DS MAX main window.
    
    Returns:
        PySide2.QtWidgets.QMainWindow: 'QMainWindow' 3DS MAX main window.
    """
    for w in QtWidgets.QApplication.topLevelWidgets():
        if w.inherits('QMainWindow') and 
                w.metaObject().className() == 'QmaxApplicationWindow':
            return w
    raise RuntimeError('Count not find QmaxApplicationWindow instance.')

Houdini 18

reference

import hou

def getHoudiniMainWindow():
    """Get the Houdini main window.
    
    Returns:
        PySide2.QtWidgets.QWidget: 'QWidget' Houdini main window.
    """
    return hou.qt.mainWindow()

Katana 3

from UI4.App import MainWindow

def getKatanaMainWindow():
    """Get the Katana main window.

    Returns:
        UI4.App.MainWindow.KatanaWindow: 'KatanaWindow' Katana main window.
    """
    return MainWindow.GetMainWindow()

Mari 4

from PySide2.QtWidgets import QApplication

import mari

def getMariMainWindow():
    """Get the Mari main window.
    
    Returns:
        PySide2.QtWidgets.QWidget: 'MriMainWindow' Mari main window.
    """

    # Set Mari main window to be in focus.
    mari.app.activateMainWindow()

    # Returns the window that has focus.
    return QApplication.activeWindow()

Maya 2020

from PySide2 import QtWidgets
import shiboken2

import maya.OpenMayaUI as apiUI

def getMayaMainWindow():
    """Get the Maya main window.
    
    Returns: 
        PySide2.QtWidgets.QWidget:  'TmainWindow' Maya main window.
    """

    ptr = apiUI.MQtUtil.mainWindow()
    if ptr is not None:
        return shiboken2.wrapInstance(long(ptr), QtWidgets.QWidget)

Nuke 12

from PySide2 import QtWidgets

def getNukeMainWindow():
    """Get the Nuke main window.

    Returns:
        PySide2.QtWidgets.QMainWindow: 'DockMainWindow' Nuke 
            main window.
    """
    for w in QtWidgets.QApplication.topLevelWidgets():
        if w.inherits('QMainWindow') and w.metaObject().className() == \
                'Foundry::UI::DockMainWindow':
            return w
    raise RuntimeError('Could not find DockMainWindow instance')

A simple “QApplication.activeWindow()” would also probably work for Nuke, but if there are multiple instances of QMainWindow, it might get confused. As far as I know, there is only one “Foundry::UI::DockMainWindow“, so it’s best to check for that.