Quickstart

This guide gets you working with tik.maya in five minutes.

Installation

tik.maya is part of the TikWorks repository. Add the src directory to your Maya Python path:

import sys
sys.path.append("/path/to/tikworks/src")

import tik.maya as tm

Dynamic cmds Wrapping

tik.maya dynamically wraps the entire maya.cmds module, allowing you to use it as a drop-in replacement for maya.cmds with minimal changes to existing scripts:

# Traditional approach
import maya.cmds as cmds

# Simply change the import!
import tik.maya as tm

# Now all your cmds calls work through tik.maya
tm.polyCube(name="myCube")
tm.xform("myCube", translation=(1, 2, 3))
tm.setAttr("myCube.translateX", 5.0)

Under the hood, tik.maya uses PEP 562 (module-level __getattr__) to intercept attribute access. When you call tm.polyCube(), it dynamically proxies to maya.cmds.polyCube() while intelligently wrapping inputs and outputs.

Key benefits:

  • Seamless migration: Existing scripts can switch namespaces with minimal changes

  • Automatic type resolution: Commands that return nodes automatically return typed tik.maya wrappers

  • Selective overrides: Critical functions like createNode use optimized OpenMaya API implementations for better performance

  • Input cleaning: tik.maya objects are automatically converted to strings for Maya commands

  • Output wrapping: Maya node names are automatically wrapped as tik.maya objects when appropriate

# Example: This returns a tik.maya.Transform object, not a string
cube = tm.polyCube(name="myCube")[0]
print(type(cube))  # <class 'tik.maya.types.transform.Transform'>

# You can immediately use object-oriented methods
cube.translate = (1, 2, 3)
cube["visibility"].value = False

Tip

Want to see more? Check out the snippets/comparisons/ folder in the repository for many side-by-side examples comparing maya.cmds and tik.maya approaches. Example 08_cylinder_rig demonstrates how an entire rigging script can work by simply changing import maya.cmds as cmds to import tik.maya as tm!

Wrapping Existing Nodes

Use tik.maya.resolve() to wrap any existing Maya node:

import tik.maya as tm

# Wrap a node by name
cube = tm.resolve("pCube1")

# The returned object is typed — Transform, Mesh, Joint, etc.
print(type(cube))  # <class 'tik.maya.types.transform.Transform'>

tik.maya automatically returns the correct wrapper class based on the Maya node type.

Working with Attributes

Access attributes using bracket notation:

# Get a Plug object for the attribute
plug = cube["translateX"]

# Read and write values
plug.value = 5.0
print(plug.value)  # 5.0

# Or use the property shortcut on transforms
cube.translate_x = 10.0

Attribute plugs have useful properties:

# Lock/unlock
cube["translateX"].locked = True
cube["translateX"].lock()    # equivalent

# Visibility in channel box
cube["translateX"].visible = False

# Keyable state
cube["translateX"].keyable = False

Plugs also support mathematical operators to create dependency graph nodes:

# Arithmetic operations create Maya nodes automatically
driver = tm.Transform.create(name="driver")
follower = tm.Transform.create(name="follower")

driver["tx"].value = 10.0

# Create addDL and multDL nodes, then connect to follower
(driver["tx"] * 2.0 + 5) >> follower["ty"]
print(follower["ty"].value)  # 25.0

Tip

For comprehensive coverage of plug operations including all mathematical operators, connections, and advanced patterns, see Working with Plugs and Attributes.

Connecting Attributes

Use the >> operator for connections:

locator = tm.resolve("locator1")
cube = tm.resolve("pCube1")

# Connect translate
locator["translate"] >> cube["translate"]

# Chain connections
a["output"] >> b["input"] >> c["input"]

Or use the explicit method:

locator["translateX"].connect(cube["translateX"])

Creating Nodes

Use the create() class method on type classes:

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

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

# Create a locator (returns the shape node)
loc = tm.Locator.create(name="myLocator")
loc.transform.translate = (1, 2, 3)  # Access parent transform

# Create geometry (pass the Maya command as first argument)
sphere = tm.Mesh.create("polySphere", name="mySphere")
plane = tm.Nurbs.create("nurbsPlane", name="myPlane")

Note

Shape types like Locator, Mesh, and Curve return shape node wrappers. Access the parent transform via the .transform property.

DAG Hierarchy

Navigate the scene hierarchy:

transform = tm.resolve("pCube1")

# Get parent
parent = transform.parent

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

# Get children
for child in transform.children:
    print(child.name)

# Get shapes
for shape in transform.shapes:
    print(shape.name, type(shape))

Transform Operations

Common transform operations are built in:

# Read transforms
print(cube.translate)    # MVector
print(cube.rotate)       # MVector
print(cube.scale)        # MVector

# Write transforms
cube.translate = (1, 2, 3)
cube.rotate = (0, 45, 0)
cube.scale = (2, 2, 2)

# Snap to another transform
cube.snap_to(target, position=True, rotation=True)

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

# Get matrices
local_matrix = cube.matrix
world_matrix = cube.world_matrix

Node Lifecycle

# Check existence
if cube.exists():
    print("Node exists")

# Rename (reference stays valid!)
cube.rename("newCubeName")
print(cube.name)  # "newCubeName"

# Delete
cube.delete()

Adding Custom Attributes

# Add an attribute
cube.add_attr("customFloat", attributeType="float", defaultValue=0.0)

# Access it
cube["customFloat"].value = 1.5

# Delete it
cube.delete_attr("customFloat")

Working with Shapes

tik.maya provides shape-specific functionality:

# Mesh operations
mesh = tm.resolve("pCubeShape1")
vertices = mesh.vertices(space="world")
nearby = mesh.vertices_in_radius((0, 0, 0), radius=1.0)
mesh.unlock_normals(soften=True)

# Curve operations
curve = tm.resolve("curveShape1")
cvs = curve.cvs(space="world")

Advanced Example: Controllers and Panels

This example combines a controller role, the control shape library, hierarchy utilities, and a panel construct to build a small rig preview setup. The shape library returns a list of available shape names that you can pass into Controller.create. The panel accepts a camera name, camera shape, or wrapper ("persp" refers to Maya’s default perspective camera) along with a window resolution in pixels.

import tik.maya as tm
from tik.maya.roles.controller import Controller
from tik.maya.utils.control_shapes import ControlShapeLibrary
from tik.maya.constructs import Panel

# Build a simple joint chain
root_joint = tm.Joint.create(name="spine_root")
mid_joint = tm.Joint.create(name="spine_mid", parent=root_joint)
end_joint = tm.Joint.create(name="spine_end", parent=mid_joint)

# Browse available shapes in the library
shape_library = ControlShapeLibrary.get_instance()
available_shapes = shape_library.list_shapes()
print(available_shapes)  # ["Circle", "Square", "CubePin", ...]

# Create a controller with a library shape
if not available_shapes:
    raise RuntimeError("No control shapes found in the library.")
shape_name = available_shapes[0]
ctrl = Controller.create(name="spine_ctrl", shape=shape_name, size=2.0)
ctrl.color = (0.9, 0.2, 0.2)

# Align and connect to the joint chain
ctrl.transform.snap_to(root_joint, position=True, rotation=True)
ctrl.transform["translate"] >> root_joint["translate"]
ctrl.transform["rotate"] >> root_joint["rotate"]

# Lock scale attributes to prevent unintended joint scaling
# collect_hierarchy returns wrapped DAG nodes under the root.
for joint in root_joint.collect_hierarchy(node_types=["joint"], include_self=True):
    for axis in ["scaleX", "scaleY", "scaleZ"]:
        joint[axis].locked = True
        joint[axis].visible = False

# Spawn a dedicated preview panel
panel = Panel(camera="persp", resolution=(1280, 720), title="Rig Preview")
panel.display_textures = True
panel.grid = False

Next Steps