Skip to content

Instantly share code, notes, and snippets.

@achayan
Last active October 27, 2025 20:23
Show Gist options
  • Select an option

  • Save achayan/b33f3bbbc6e6b2c4cce6e94f46b78e6e to your computer and use it in GitHub Desktop.

Select an option

Save achayan/b33f3bbbc6e6b2c4cce6e94f46b78e6e to your computer and use it in GitHub Desktop.
"""
nParticle Locator Attachment Script - Clean Version
Simple script to attach locators to nParticles using Maya Python API
"""
import maya.cmds as cmds
import maya.OpenMaya as om
class NParticleLocatorSystem:
"""
Simple system to attach locators to nParticles.
Uses Maya API for efficient particle data access.
"""
def __init__(self, particle_shape_name):
"""
Initialize with an nParticle shape node.
Args:
particle_shape_name (str): Name of the nParticle shape node
"""
self.particle_shape = particle_shape_name
self.locators = []
self.script_node = None
def create_locators(self):
"""
Create a locator for each particle at current frame.
Returns:
list: Created locator names
"""
if not cmds.objExists(self.particle_shape):
print(f"Error: Particle shape '{self.particle_shape}' not found")
return []
# Get particle count and positions using API
particle_count, positions = self._get_particle_positions()
if particle_count == 0:
print("No particles found")
return []
print(f"Creating {particle_count} locators...")
# Create locators
self.locators = []
for i in range(particle_count):
loc_name = f"{self.particle_shape}_loc_{i}"
# Check if locator already exists
if cmds.objExists(loc_name):
print(f"Locator {loc_name} already exists, skipping...")
self.locators.append(loc_name)
continue
# Create locator
loc = cmds.spaceLocator(name=loc_name)[0]
# Set position
pos = positions[i]
cmds.xform(loc, translation=pos, worldSpace=True)
self.locators.append(loc)
print(f"Created {len(self.locators)} locators")
return self.locators
def _get_particle_positions(self):
"""
Get particle positions using Maya API.
Returns:
tuple: (count, list of (x,y,z) tuples)
"""
try:
# Get MObject for particle shape
sel_list = om.MSelectionList()
sel_list.add(self.particle_shape)
m_obj = om.MObject()
sel_list.getDependNode(0, m_obj)
# Get dependency node function set
fn_dep = om.MFnDependencyNode(m_obj)
# Get worldPosition plug
pos_plug = fn_dep.findPlug("worldPosition", False)
pos_obj = pos_plug.asMObject()
# Get vector array data
fn_array = om.MFnVectorArrayData(pos_obj)
pos_array = fn_array.array()
# Convert to list of tuples
positions = []
for i in range(pos_array.length()):
vec = pos_array[i]
positions.append((vec.x, vec.y, vec.z))
return pos_array.length(), positions
except Exception as e:
print(f"Error getting particle positions: {e}")
return 0, []
def attach_with_script_node(self):
"""
Create a script node that updates locator positions every frame.
This persists with the scene file.
Returns:
str: Script node name
"""
if not self.locators:
print("No locators to attach. Run create_locators() first.")
return None
# Script node name
script_name = f"{self.particle_shape}_locatorScript"
# Delete existing script node if it exists
if cmds.objExists(script_name):
cmds.delete(script_name)
# Create the update code
update_code = f"""
import maya.cmds as cmds
import maya.OpenMaya as om
particle_shape = '{self.particle_shape}'
locator_prefix = '{self.particle_shape}_loc_'
def update_locators():
try:
# Find all locators dynamically
all_transforms = cmds.ls(type='transform')
locators = []
for trans in all_transforms:
if trans.startswith(locator_prefix):
try:
idx_str = trans.replace(locator_prefix, '')
idx = int(idx_str)
locators.append((idx, trans))
except:
pass
if not locators:
return
# Sort by index
locators.sort(key=lambda x: x[0])
locator_names = [loc[1] for loc in locators]
# Get particle positions using API
sel_list = om.MSelectionList()
sel_list.add(particle_shape)
m_obj = om.MObject()
sel_list.getDependNode(0, m_obj)
fn_dep = om.MFnDependencyNode(m_obj)
pos_plug = fn_dep.findPlug("worldPosition", False)
pos_obj = pos_plug.asMObject()
fn_array = om.MFnVectorArrayData(pos_obj)
pos_array = fn_array.array()
# Update locator positions
for i, loc_name in enumerate(locator_names):
if i < pos_array.length() and cmds.objExists(loc_name):
vec = pos_array[i]
cmds.xform(loc_name, translation=(vec.x, vec.y, vec.z), worldSpace=True)
except:
pass
# Execute update
if cmds.objExists(particle_shape):
update_locators()
"""
# Create script node
self.script_node = cmds.scriptNode(
name=script_name,
scriptType=2, # Execute on frame change
beforeScript=update_code,
sourceType="python"
)
print(f"Created script node: {script_name}")
print("Locators will now update automatically every frame")
print("This will persist when you save and reload the scene")
return self.script_node
def detach(self):
"""Remove the script node."""
script_name = f"{self.particle_shape}_locatorScript"
if cmds.objExists(script_name):
cmds.delete(script_name)
print(f"Removed script node: {script_name}")
def delete_locators(self):
"""Delete all created locators."""
deleted = 0
for loc in self.locators:
if cmds.objExists(loc):
cmds.delete(loc)
deleted += 1
print(f"Deleted {deleted} locators")
self.locators = []
def cleanup(self):
"""Remove script node and delete all locators."""
self.detach()
self.delete_locators()
print("Cleanup complete")
# ============================================================================
# CONVENIENCE FUNCTIONS
# ============================================================================
def setup_particle_locators(particle_shape_name):
"""
Quick setup: Create locators and attach to timeline.
Args:
particle_shape_name (str): nParticle shape node name
Returns:
NParticleLocatorSystem: System object
Example:
system = setup_particle_locators('nParticleShape1')
"""
system = NParticleLocatorSystem(particle_shape_name)
system.create_locators()
system.attach_with_script_node()
return system
def reconnect_existing_locators(particle_shape_name):
"""
Reconnect existing locators after reopening a scene.
This recreates the script node for existing locators.
Args:
particle_shape_name (str): nParticle shape node name
Returns:
NParticleLocatorSystem: System object
Example:
system = reconnect_existing_locators('nParticleShape1')
"""
system = NParticleLocatorSystem(particle_shape_name)
# Find existing locators
locator_prefix = f"{particle_shape_name}_loc_"
all_transforms = cmds.ls(type='transform')
for trans in all_transforms:
if trans.startswith(locator_prefix):
system.locators.append(trans)
if system.locators:
print(f"Found {len(system.locators)} existing locators")
system.attach_with_script_node()
else:
print("No existing locators found")
return system
def cleanup_particle_locators(particle_shape_name):
"""
Clean up everything for a particle system.
Args:
particle_shape_name (str): nParticle shape node name
Example:
cleanup_particle_locators('nParticleShape1')
"""
system = NParticleLocatorSystem(particle_shape_name)
# Find all locators
locator_prefix = f"{particle_shape_name}_loc_"
all_transforms = cmds.ls(type='transform')
for trans in all_transforms:
if trans.startswith(locator_prefix):
system.locators.append(trans)
system.cleanup()
# ============================================================================
# EXAMPLE USAGE
# ============================================================================
if __name__ == "__main__":
PARTICLE_SHAPE = 'nParticleShape1'
if cmds.objExists(PARTICLE_SHAPE):
print(f"Setting up locators for {PARTICLE_SHAPE}...")
# Create and setup
system = setup_particle_locators(PARTICLE_SHAPE)
# Store in global for easy access
globals()['particle_system'] = system
print("\n=== Setup Complete ===")
print(f"Created {len(system.locators)} locators")
print("Locators will follow particles automatically")
print("\nAfter saving and reopening the scene, run:")
print(f" system = reconnect_existing_locators('{PARTICLE_SHAPE}')")
print("\nTo clean up everything, run:")
print(f" cleanup_particle_locators('{PARTICLE_SHAPE}')")
else:
print(f"Error: '{PARTICLE_SHAPE}' not found in scene")
print("Please create an nParticle system first")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment