Uninstance all instances in a Maya scene
From over on my mel wiki:
Maya makes it easy to create an instance of a node, but I’ve found no built-in way to query what nodes in the scene are instanced. Poking around a bit in the API however, it only takes a few lines of code:
# Python code import maya.cmds as mc import maya.OpenMaya as om def getInstances(): instances = [] iterDag = om.MItDag(om.MItDag.kBreadthFirst) while not iterDag.isDone(): instanced = om.MItDag.isInstanced(iterDag) if instanced: instances.append(iterDag.fullPathName()) iterDag.next() return instances
This function will return a list of all the instances in the scene, nice! I have yet to find any other function that can do this via mel.
But how would you actually uninstance them? I worked on this problem quite a bit: If you simply have a bunch of leaf nodes that are instanced, it’s not hard to do. But if you have a say ten spheres all instanced, then you make two groups of five, then instance those groups, and parent all the groups, then instance that parent… very quickly things get hairy: You try and uninstance a leaf by duplicating it, and now the duplicate appears in every other parental instance!
Like I just mentioned, most solutions I’ve found explain you can just ‘duplicate the instance’ to solve this problem, and this works on an individual instance that has no parent node that is an instance. But when you have the situation I list above, the solution I found is this: You can actually duplicate the instances parent, then delete the original parent, which will (via a loop) cleanly uninstance everything in the scene. And since the getInstances() function returns a list parent-first, this works out great:
def uninstance(): instances = getInstances() while len(instances): parent = mc.listRelatives(instances[0], parent=True, fullPath=True)[0] mc.duplicate(parent, renameChildren=True) mc.delete(parent) instances = getInstances()
The dirty mel way of detecting instances is to use the listRelatives command and query your object using the ‘allParents’ flag.
If an object has more than one parent it is instanced.
Thanks for the tip. I’d used that method before going down the path I listed above, because I thought there were edge-cases with it when dealing with instanced transforms (with no shapes) at root level. But I just tried again, and my edge cases went away 😉 So indeed, it looks like you could do the whole thing in mel that way.
Hi, I’m not sure if I’m talking about the same thing, but recently I’ve had to find a way to replace a transform node’s instanced shape because the initial shape was for preview purposes only. I was able to remove the instanced shapes from the transform using “parent -rm dagTransform|instancedDagShape”. Then, to get the instanced shapes associated with a transform I simply used “parent -r -s instancedDagShape dagTransform”.
Hi AK Eric,
thank you very much for your script, this thing caused me many problems last week at work. I am a totally ignorant in Maya API, I just don’t understand why, how, etc. I am pretty decent in Python Maya though. So I am trying to rewrite your code so it works with only the selection. It works to a certain extent, but some objects are not uninstanced – I guess because the level of grouping is deeper than a first “pass”. I tried to make it work with the “getAllPaths” command of the MDagPath, with no success – I have barely an idea of the api code I am writing. In an ideal world, all objects that are instances of the same kind of the selection would be uninstanced, and the rest of the scene would be untouched.
This is what I have so far, if you have a second to help it would be very kind, my scene is a nightmare of objects disappearing when I combine : (
import maya.OpenMaya as om
def getInstances():
is_inst = []
# create a list of the selection
selection = om.MSelectionList()
om.MGlobal.getActiveSelectionList(selection)
# create iterator of the selection
selection_iter = om.MItSelectionList(selection)
obj = om.MObject()
while not selection_iter.isDone():
selection_iter.getDependNode(obj)
dagPath = om.MDagPath.getAPathTo(obj)
dagInst = om.MDagPath.isInstanced(dagPath)
if dagInst:
is_inst.append(dagPath.fullPathName())
print ‘ yes’
print dagPath.fullPathName()
selection_iter.next()
def uninstance():
instances = getInstances()
while len(instances):
parent = mc.listRelatives(instances[0], parent=True, fullPath=True)[0]
mc.duplicate(parent, renameChildren=True)
mc.delete(parent)
instances = getInstances()
uninstance()
I was excited to find this post as we have a lot of uninstancing to do here at work.
However, the method seems to be terribly inefficient with deeply nested instances and a large number of DAG nodes. For example, one scene file I tested returns 12260 instances through the getInstances() function. With the uninstance() function calling getInstances() for every one of those instances this means waiting for 40 minutes to uninstance the entire scene!
I took the basic idea and have written my own version. Instead of getting all the instances in the entire scene I decided to check the DAG leaf nodes. For the instanced leaf nodes I get the full path to those nodes and then look for the highest level node (closest to root node) in that path that is also instanced. I generate a list of those nodes (parents to so-called “instance groups”) once then iterate through them duplicating and deleting them. It’s possible that one run through will not find all the instances so I repeat that process as many times as necessary (in the case of the complex scene I mentioned it has to run twice).
The benefits I see in this method are a smaller data set to begin with (in my case: 2434 “instance groups” to work with instead of 12260 instances) as well as linear scaling with the size of the data set, O(n) instead of O(n^2). This processed the complex scene above over 200x as fast: 10 seconds instead of 40 minutes!
Possible downfalls include the fact that I’ve only tested this on a limited number of scene files, I’m fairly new to Maya so it’s possible I’ve overlooked some test cases, and there may be more cleanup that can be done to this code (working fast on deadline). Anywho, try it out and let me know if it works for you:
import itertools
import time
from maya import cmds
from maya import OpenMaya as om
#Functions begin here
def is_instance(n):
#Return True if n is an instanced node
parents = cmds.listRelatives(n, ap=True)
return parents and len(parents) > 1
def get_parents(n):
#Return a list of n’s parents if n is an instance, otherwise return None
try:
parents = cmds.listRelatives(n, ap=True, f=True)
except ValueError:
return None
if parents and len(parents) > 1:
return parents
else:
return None
def find_first_instance(p):
#Return the parent of the instanced node closest to the root in the full path p
current = “”
node_names = filter(None, p.split(“|”))
for name in node_names:
current = current + “|” + name
if is_instance(current):
return current.rsplit(“|”, 1)[0]
#Execution begins here
start_time = time.clock()
instances = [True]
passes = 0
total_count = 0
#Repeat the process until there are no instances
while instances:
count = 0
#Get the full path to all DAG leaf nodes
dag_leaf_nodes = cmds.ls(dag=True, l=True, lf=True)
#Get all parents of the instanced DAG leaf nodes
instances = list(itertools.chain.from_iterable(filter(None, map(get_parents, dag_leaf_nodes))))
#Get the instanced nodes highest in the hierarchy
nodes = list(set(map(find_first_instance, instances)))
#Duplicate and then remove all of the highest instanced nodes
for node in nodes:
cmds.duplicate(node, rc=True)
cmds.delete(node)
count += 1
#Increment counters and display info
if instances:
passes += 1
total_count += count
om.MGlobal.displayInfo(“Completed pass {0} of uninstancing: removed {1} instance groups”.format(passes, count))
#Done!
end_time = time.clock()
t = end_time – start_time
om.MGlobal.displayInfo(“{0} instance groups removed after {1} passes in {2:.3f} seconds”.format(total_count, passes, t))
Thanks for the code: it’s funny how different productions have different problems. I usually run into just a few instances at a time, so I’d never stress tested the code on a large volume. FYI, I have another solution written in PyMel over on my mel wiki:
http://mayamel.tiddlyspot.com/#%5B%5BHow%20can%20I%20'uninstance'%20a%20node%3F%5D%5D
I have no idea how it compares speed wise, but more solutions can’t hurt 😉
Here is the uninstance python script for Maya that has O(N) complexity which is so much faster for complex scenes than this article’s solution.
It basically uses same technique for original but buggy Instance To Object command in maya however fixes a few things suchs as treversal order to make it work seamlessly.
def uninstance():
cmds.select(hierarchy=True)
objs = cmds.ls(long=True, selection=True, transforms=True)
objs.sort(key=lambda x : x.count(‘|’))
for obj in objs:
obj_dup = cmds.duplicate(obj)
cmds.delete(obj)
obj_dup_arr = obj.split(“|”)
obj_dup_name = obj_dup_arr[len(obj_dup_arr) – 1]
cmds.rename(obj_dup[0], obj_dup_name)
uninstance()