Last active
October 27, 2025 20:23
-
-
Save achayan/b33f3bbbc6e6b2c4cce6e94f46b78e6e to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| """ | |
| 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