Working with Nodes

In tik.maya, nodes are the fundamental building blocks. This guide explains how to create, access, and work with different node types.

What is a Node in tik.maya?

A node in tik.maya is a Python wrapper around a Maya scene node. Every node is tracked by its UUID and wrapped in a class that corresponds to its Maya type.

import tik.maya as tm

# Resolve a node - returns the appropriate wrapper class
cube = tm.resolve("pCube1")  # Returns Transform instance
print(type(cube))  # <class 'tik.maya.types.transform.Transform'>

Understanding Node Types

tik.maya automatically wraps Maya nodes in type-specific classes. The type of a node in tik.maya context refers to the Python wrapper class, which corresponds to the Maya node type.

The registry system resolves the correct wrapper class based on Maya’s node type:

import tik.maya as tm

# Different Maya node types get different wrapper classes
transform = tm.resolve("pCube1")       # Transform wrapper
mesh = tm.resolve("pCubeShape1")       # Mesh wrapper
joint = tm.resolve("joint1")           # Joint wrapper
locator = tm.resolve("locatorShape1")  # Locator wrapper

# Each type has specialized methods
print(transform.translate)  # Transform-specific property
print(mesh.vertices())      # Mesh-specific method
print(joint.radius)         # Joint-specific property

Node Type Hierarchy

tik.maya’s type system mirrors Maya’s node hierarchy:

Node (base wrapper)
├── DagNode (DAG hierarchy nodes)
│   ├── Transform
│   │   └── Joint
│   └── ShapeNode
│       ├── Mesh
│       ├── Curve
│       ├── Nurbs
│       ├── Locator
│       ├── Camera
│       └── Light
└── DependencyNode (DG-only nodes)
    ├── AnimCurve
    ├── BlendShape
    └── (other dependency nodes)

Node is the base class for all wrappers. It provides core functionality like UUID tracking, attribute access, and existence checking.

DagNode extends Node for nodes in the DAG (Directed Acyclic Graph) hierarchy. It adds parent/child relationships and world/local transformations.

Transform and ShapeNode are specialized DAG nodes. Transforms can have children and shapes, while ShapeNodes are the geometry/rendering components.

Creating Nodes

Creating New Nodes

tik.maya offers three ways to create nodes, each with different use cases:

Method 1: Type-Specific create() (Most Explicit)

Use the create() class method on any type class for the most explicit approach:

import tik.maya as tm

# Create a transform
grp = tm.Transform.create(name="myGroup")

# Create a joint
jnt = tm.Joint.create(name="spine_01")

# Create shape nodes (returns the shape wrapper)
loc = tm.Locator.create(name="myLocator")
print(type(loc))  # <class 'tik.maya.types.locator.Locator'>

# Access the parent transform via .transform property
loc.transform.translate = (5, 10, 0)

For geometry shapes, pass the Maya creation command as the first argument:

# Create a polygon sphere
sphere = tm.Mesh.create("polySphere", name="mySphere", radius=2)
sphere.transform.translate = (0, 5, 0)

# Create a NURBS plane
plane = tm.Nurbs.create("nurbsPlane", name="myPlane")

# Create a curve
curve = tm.Curve.create("circle", name="myCircle")

Method 2: createNode() or create_node() (cmds-like)

Use tm.createNode() or tm.create_node() for a familiar cmds-style interface:

import tik.maya as tm

# Create using createNode (camelCase alias)
transform = tm.createNode("transform", name="myTransform")

# Or using create_node (snake_case)
camera = tm.create_node("camera", name="myCamera")

# Works with any Maya node type
mult_node = tm.createNode("multiplyDivide", name="myMultiply")

This method automatically resolves the created node to the correct wrapper type.

Method 3: Node.create() (Generic)

Use tm.Node.create() for a generic creation that still resolves to the correct type:

import tik.maya as tm

# Create a camera using Node.create()
camera = tm.Node.create("camera", name="myCamera")
print(type(camera))  # <class 'tik.maya.types.camera.Camera'>

# Create a transform
transform = tm.Node.create("transform", name="myTransform")
print(type(transform))  # <class 'tik.maya.types.transform.Transform'>

Even though you’re calling Node.create(), tik.maya resolves the node type and returns the appropriate wrapper class (Camera, Transform, etc.).

Wrapping Existing Nodes

Use tm.resolve() to wrap nodes that already exist in the scene:

import tik.maya as tm

# By name
cube = tm.resolve("pCube1")

# From a list of names
nodes = tm.resolve(["pCube1", "pSphere1", "joint1"])

# Works with any valid Maya node name
persp_cam = tm.resolve("persp")  # Camera transform
persp_shape = tm.resolve("perspShape")  # Camera shape

Node Type Resolution Order

When resolving nodes (either with tm.resolve() or creating with tm.createNode()/ tm.Node.create()), tik.maya determines the wrapper class based on Maya’s node inheritance hierarchy.

Maya nodes inherit from multiple base types. For example, a camera node actually inherits these Maya classes in order:

['containerBase', 'entity', 'dagNode', 'shape', 'camera']

tik.maya resolves to the most specific (closest) available wrapper type in the inheritance chain:

  1. If a Camera wrapper exists, the node is wrapped as Camera

  2. If not, but a ShapeNode wrapper exists, it’s wrapped as ShapeNode

  3. If not, but a DagNode wrapper exists, it’s wrapped as DagNode

  4. If none match, it falls back to the base Node wrapper

import tik.maya as tm

# Camera resolves to most specific type available
camera = tm.resolve("perspShape")
print(type(camera))  # <class 'tik.maya.types.camera.Camera'>

# If Camera type didn't exist, it would resolve to ShapeNode
# If ShapeNode didn't exist, it would resolve to DagNode
# As a last resort, it would resolve to Node

This ensures you always get the most specific wrapper available for any node type.

Node Lifecycle

Existence Checking

Nodes can be deleted, and references can become invalid. Always check existence when needed:

cube = tm.Transform.create(name="tempCube")

# Check if node still exists in Maya
if cube.exists():
    print(f"{cube.name} exists")

# Delete the node
cube.delete()

# Now it doesn't exist
print(cube.exists())  # False

Tip

tik.maya tracks nodes primarily using maya.api.OpenMaya.MObject handles for performance, with UUID as a backup for restoration when the MObject link breaks (such as after undo/redo operations). This means your Python reference stays valid even if the node is recreated.

Renaming Nodes

Node wrappers survive renames because they track by MObject (with UUID backup), not name:

cube = tm.Transform.create(name="originalName")
print(cube.name)  # "originalName"

# Rename the node
cube.rename("newName")
print(cube.name)  # "newName"

# The wrapper still works - UUID hasn't changed
cube.translate_x = 10.0  # Works perfectly

Duplicating Nodes

Duplicate nodes and get new wrappers:

original = tm.Transform.create(name="original")
original.translate = (1, 2, 3)

# Duplicate returns a new wrapper
duplicate = original.duplicate()
print(duplicate.name)  # "original1"
print(duplicate.translate)  # (1.0, 2.0, 3.0)

Working with Different Node Types

Transform Nodes

Transforms are the most common node type. They support hierarchies and transformations:

import tik.maya as tm

# Create transforms
parent = tm.Transform.create(name="parent")
child = tm.Transform.create(name="child")

# Set hierarchy
child.parent = parent

# Transform operations
parent.translate = (0, 5, 0)
parent.rotate = (0, 45, 0)
parent.scale = (2, 2, 2)

# Matrices
local_matrix = parent.matrix
world_matrix = parent.world_matrix

# Freeze transformations
parent.freeze(translate=True, rotate=True, scale=True)

Joint Nodes

Warning

Work in Progress: Joint-specific API is still being developed.

Joints extend Transform with rigging-specific features:

# Create a joint chain
root = tm.Joint.create(name="spine_root")
mid = tm.Joint.create(name="spine_mid", parent=root)
tip = tm.Joint.create(name="spine_tip", parent=mid)

# Joint-specific properties
root.radius = 2.0
root.draw_style = 2  # Bone drawing style

# Position joints
mid.translate = (0, 5, 0)
tip.translate = (0, 5, 0)

Shape Nodes

Shape nodes contain geometry or visual data. They always have a parent transform:

# Create shapes
mesh = tm.Mesh.create("polyCube", name="myCube")
curve = tm.Curve.create("circle", name="myCircle")
locator = tm.Locator.create(name="myLocator")

# Access parent transforms
mesh.transform.translate = (0, 0, 0)
curve.transform.rotate = (0, 90, 0)

# Shape-specific operations
vertices = mesh.vertices(space="world")
cvs = curve.cvs(space="object")

Mesh-Specific Operations

mesh = tm.resolve("pCubeShape1")

# Get vertex positions
verts = mesh.vertices(space="world")
print(f"Vertex count: {len(verts)}")

# Find vertices in radius
nearby = mesh.vertices_in_radius(center=(0, 0, 0), radius=5.0)

# Unlock normals
mesh.unlock_normals(soften=True)

Curve-Specific Operations

curve = tm.resolve("curveShape1")

# Get CV positions
cvs = curve.cvs(space="world")

# Query curve properties
degree = curve.degree
form = curve.form

Camera Nodes

# Create a camera (returns the shape)
cam = tm.Camera.create(name="myCamera")

# Camera-specific properties
cam.focal_length = 50.0
cam.horizontal_film_aperture = 1.417

# Position the camera via its transform
cam.transform.translate = (10, 10, 10)
cam.transform.rotate = (0, 45, 0)

Dependency Graph Nodes

Some nodes don’t participate in the DAG hierarchy. They’re pure dependency graph nodes:

import maya.cmds as cmds

# Create a dependency node
node_name = cmds.createNode("multiplyDivide")
mult_node = tm.resolve(node_name)

# Access as a generic Node wrapper
mult_node["input1X"].value = 5.0
mult_node["input2X"].value = 2.0
print(mult_node["outputX"].value)  # 10.0

Node Selection and Queries

Listing Scene Nodes

tik.maya provides tm.ls() (or tm.list_scene_nodes()), a wrapper for cmds.ls() that returns resolved tik.maya objects:

import tik.maya as tm

# List all transforms in the scene (returns tik.maya objects)
transforms = tm.ls(type="transform")
for transform in transforms:
    print(f"{transform.name} - {type(transform)}")

# List all joints
joints = tm.ls(type="joint")

# You can use all cmds.ls arguments
selected = tm.ls(selection=True)
visible = tm.ls(visible=True)

# Using the snake_case alias
all_meshes = tm.list_scene_nodes(type="mesh")

Selecting Nodes

Use tm.select() (or tm.select_nodes()) to select nodes:

import tik.maya as tm

# Create some nodes
cube = tm.Transform.create(name="myCube")
sphere = tm.Transform.create(name="mySphere")

# Select nodes
tm.select([cube, sphere])

# Using the snake_case alias
tm.select_nodes([cube])

# Supports cmds.select kwargs
tm.select([cube], add=True)  # Add to selection

Hierarchy Navigation

Working with Parents and Children

transform = tm.resolve("pCube1")

# Get parent
parent = transform.parent
if parent:
    print(f"Parent: {parent.name}")

# Set parent
new_parent = tm.resolve("group1")
transform.parent = new_parent

# Get children
for child in transform.children:
    print(f"Child: {child.name}")

# Get all descendants recursively
all_descendants = transform.collect_hierarchy(include_self=False)

Filtering Hierarchy

root = tm.resolve("rig_root")

# Get only joints in hierarchy
joints = root.collect_hierarchy(node_types=["joint"], include_self=True)

# Get only transforms (excluding joints)
transforms = root.collect_hierarchy(node_types=["transform"], include_self=False)

Working with Shapes

transform = tm.resolve("pCube1")

# Get all shape nodes under this transform
for shape in transform.shapes:
    print(f"Shape: {shape.name} - {type(shape)}")

# Typically just one shape, but can be multiple
if transform.shapes:
    mesh = transform.shapes[0]
    print(mesh.vertices())

Node Comparison and Identity

cube1 = tm.resolve("pCube1")
cube2 = tm.resolve("pCube1")

# Same node, different wrapper instances
print(cube1 == cube2)  # True (compares UUIDs)

# Direct UUID comparison
print(cube1.uuid == cube2.uuid)  # True

# Check if it's the same type
print(isinstance(cube1, tm.Transform))  # True

Common Patterns

Batch Operations on Nodes

# Get all selected transforms
nodes = tm.ls(selection=True)

# Lock all scale attributes
for node in nodes:
    if isinstance(node, tm.Transform):
        for axis in ["scaleX", "scaleY", "scaleZ"]:
            node[axis].locked = True
            node[axis].visible = False

Summary

  • Nodes are Python wrappers around Maya scene nodes, tracked by MObject (with UUID backup)

  • Types in tik.maya correspond to Maya node types (Transform, Joint, Mesh, etc.)

  • The registry automatically returns the correct wrapper class based on inheritance order

  • Three creation methods: Type.create(), tm.createNode(), and tm.Node.create()

  • Use tm.resolve() to wrap existing nodes

  • Node wrappers survive renames and namespace changes

  • Different node types have specialized properties and methods

  • Navigate hierarchies with parent, children, and collect_hierarchy()

  • Use tm.ls() and tm.select() for scene queries and selection

For detailed information on working with node attributes, see Working with Plugs and Attributes.