Query DG Evaluation Order??

Become a member of the CGSociety

Connect, Share, and Learn with our Large Growing CG Art Community. It's Free!

THREAD CLOSED
 
Thread Tools Search this Thread Display Modes
  02 February 2013
Thanks Keilun.

To sum up what I'm doing, I'm setting the position of a group of objects in a complex heirarchy (character rig). To avoid double transforming objects, Ill need to set positions (using xform, or move), in an order relative to the DG/DAG.

Say I have a few objects, and world space positions Id like to apply to each one. (this is a simple example, real world situations will have constraints, linked matrices and who knows what other kind of connections )

mainControl
--bodyControl
-----armControl

Potential problem:
If positions are set on objects lower in the DAG/DG hierarchy (armControl), then an upstream node position is set (mainControl), there will be an unwanted double transformation (on armControl).

So to avoid this issue, Ill need to figure out what needs to be set first. (first mainControl, then bodyControl, then armControl)

Interesting idea about disabling transforms. Not sure if I want to mess with that though.

It looks like theres not a solution that combines DAG and DG connections. Id have to use both listConnections+listRelatives or MItDependencyGraph+MItDag either way, if I'm not mistaken.
 
  02 February 2013
Ok, I think I see now. In that case yeah, disabling transform inheritance is not desirable.

Yes, DAG and DG are two separate graphs. DAG is the scene hierarchy graph (parenting) and DG (connections, eg. constraints, driven keys, etc.) is the evaluation graph. So you will need to use both in order to achieve what you're looking for.

I would probably start with the DAG to get a base set of relationships that you are guaranteed are acyclic (DAG = directed acyclic graph). Use the DAG relationships to generate your order of evaluation.

After that, you can look at the DG relationships and look at incorporating those. This is probably the trickier segment as there likely will be cycles particularly with constraints. I would guess in most cases, you would want to use the DAG relationship to order with DG relationships as a secondary measure, but there's enough variance from rig to rig, it's difficult to visualize a general solution.
 
  02 February 2013
Keilun,

Yeah, its a tricky problem.

The code I posted earlier works (using listConnections+listRelatives) to sort this out. Although Rob implied it would be easier to do with MItDependencyGraph. Although, it looks like MItDependencyGraph might have the same limitation as what I was doing already.

I'm all ears if there's a better solution!

Thanks!!
 
  02 February 2013
Why are you trying to avoid using constraints?
 
  02 February 2013
I just discovered that part of what I was attempting to do is called 'Topological Sorting'.

Pretty interesting actually.
http://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm

JCapper, just trying to create an order of operations for setting the positions of objects. I wanted to do it cleanly without adding extra nodes or connections. If objects already have constraints or animated pairblends, there might be unpredictable results with adding MORE connections. Something I wanted to avoid.

This seems to work pretty well for transforms in maya, who knows it might work with other node types as well.

Here's how to use it to return a list of objects sorted relative to the DAG/DG:
for n in sortNodesBasedOnDG(cmds.ls(sl=True), returnShortNames=True, printInfo=True):
       	print n


**warning, might be some bad formatting from the cgsociety forum software

       import maya.cmds as cmds
       import maya.mel as mel
       ##################################################  ##################################################  ##
       def getClosestUpstreamNodeFromList(objectA, objectList, **kwargs):
       	#kwarg stuff
       	printInfo = False
       	if 'printInfo' in kwargs:
       		printInfo = kwargs.pop('printInfo')
       
       	#setup printInfo timer
       	if printInfo:
       		start = cmds.timerX()
       
       	#probably not needed, but just in case there are any weird circular dependencies
       	maxIterations = 50
       	
       	#convert objects to long names, in case theres duplicates
       	objectA = cmds.ls(objectA, long=True)
       	objectList = cmds.ls(objectList, long=True)
       	
       	#just incase objectA happens to be in the object list, get rid of it
       	if objectA[0] in objectList:
       		objectList.pop(objectList.index(objectA[0]))
       
       	#place to store returned objects
       	storageBin = []
       	queryParents = []
       	queryConnections = []
       	query = objectA
       	result = []
       	i=''
       
       	for i in range(maxIterations):
       		#break if no new results returned
       		#print query
       		if query == []:
       			break
       		
       		#gather DAG and DG connections
       		queryParents = list(set(cmds.ls(cmds.listRelatives(query, parent=True, fullPath=True ), long=True)))
       		queryConnections = list(set(cmds.ls(cmds.listConnections(query, d=False, s=True), long=True)))
       		
       		#break if the query is returning the exact same thing as last time, aka no change
       		if sorted(list(set(query))) == sorted(list(set(queryParents + queryConnections))):
       			break
       		
       		#new data for next loop
       		else:
       			query = list(set(queryParents + queryConnections))
       		
       		#store all data
       		storageBin = list(set(storageBin + query))
       		
       		#check if match is found
       		matchingNodes = list(set(objectList).intersection(set(storageBin))  )
       		if matchingNodes:
       			#output results, breaks
       			result = matchingNodes
       			break
       
       	#print out timer info if needed		
       	if printInfo:
       		totalTime = cmds.timerX(startTime=start)
       		print 'search took ' + str(i) + ' iterations'
       		print ('total time to check dependency '), totalTime, ('seconds')
       
       	#return
       	return result
       
       ##################################################  ##################################################  ##
       def sortNodesBasedOnDG(objectList, **kwargs):
       	#kwarg stuff
       	returnShortNames = False
       	if 'returnShortNames' in kwargs:
       		returnShortNames = kwargs.pop('returnShortNames')
       
       	printInfo = False
       	if 'printInfo' in kwargs:
       		printInfo = kwargs.pop('printInfo')
       
       	#setup printInfo timer
       	if printInfo:
       		start = cmds.timerX()
       	
       	#get long names for objects
       	objectList = cmds.ls(objectList, long=True)
       	
       	#get list of upstream nodes
       	upstreamNodesDict = {}
       	for object in objectList:
       		upstreamNodesDict[object] = getClosestUpstreamNodeFromList(object,objectList)
       	
       	#sort list
       	sortedlist = strongly_connected_components(upstreamNodesDict)
       	
       	#print out timer info if needed		
       	if printInfo:
       		totalTime = cmds.timerX(startTime=start)
       		print 'sort nodes based on DAG/DG took ', totalTime, ('seconds')
       	
       	for i in range(len(sortedlist)):
 		#convert touple to list and REVERSE
		#this seems to work with nodes that appear to be circularly linked
 		sortedlist[i] = list(sortedlist[i])
 		sortedlist[i].reverse()
 		
 		#this returns more userfriendly short names
 		if returnShortNames:
 			for ii in range(len(sortedlist[i])):
 				sortedlist[i][ii]=cmds.ls(sortedlist[i][ii], long=False)[0]
       
       	#return 
       	return sortedlist
       	
       ##################################################  ##################################################  ##
       def strongly_connected_components(graph):
       	"""
       	Tarjan's Algorithm (named for its discoverer, Robert Tarjan) is a graph theory algorithm
       	for finding the strongly connected components of a graph.
       	
       	Based on: http://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm  
       	"""
       
       	index_counter = [0]
       	stack = []
       	lowlinks = {}
       	index = {}
       	result = []
       	
       	def strongconnect(node):
       		# set the depth index for this node to the smallest unused index
       		index[node] = index_counter[0]
       		lowlinks[node] = index_counter[0]
       		index_counter[0] += 1
       		stack.append(node)
       	
       		# Consider successors of `node`
       		try:
       			successors = graph[node]
       		except:
       			successors = []
       		for successor in successors:
       			if successor not in lowlinks:
       				# Successor has not yet been visited; recurse on it
       				strongconnect(successor)
       				lowlinks[node] = min(lowlinks[node],lowlinks[successor])
       			elif successor in stack:
       				# the successor is in the stack and hence in the current strongly connected component (SCC)
       				lowlinks[node] = min(lowlinks[node],index[successor])
       		
       		# If `node` is a root node, pop the stack and generate an SCC
       		if lowlinks[node] == index[node]:
       			connected_component = []
       			
       			while True:
       				successor = stack.pop()
       				connected_component.append(successor)
       				if successor == node: break
       			component = tuple(connected_component)
       			# storing the result
       			result.append(component)
       	
       	for node in graph:
       		if node not in lowlinks:
       			strongconnect(node)
       	
       	return result

Last edited by backwheelbates : 02 February 2013 at 04:32 PM. Reason: update code
 
  02 February 2013
Post

Quote: Why are you trying to avoid using constraints?

I agree, because in your specific case, it seems a very hard process to figure out who's controling who, and in which order...
So if you can afford to use contraints, maybe you could try this:

_ Get a list of all objects to modify position/rotation of.
_ Iterate through them in any order:
___ ParentConstraint (maintainOffset off) each object by whichever driver suits correctly
___ Append the constraint created into a list
_ After the loop, delete the content of the constraints list

Didn't try, and I'm not 100% sure this approach is working, because deleting all the temporary constraints may trigger a graph refresh that could make you loose the position/rotation matching, but it's so simple to test that it's worth a try...

Nicolas
 
  02 February 2013
Hi nico_the_last. Thanks for the ideas! If I were to go the constraint route, that's exactly what I'd do.

Although Ive been using the above code for the past few days (and NOT using any constraints) in production, and it seems to work reliably on complex hierarchies (thanks to Tarjan's sorting algorithm).
 
  02 February 2013
Sorry, I didn't read the part talking about the fact that some nodes may have some animation and/or constraints.

Anyway, assuming you're not trying to modify the position or rotation of an object which have those channels driven by a constraint, you may be able to use another trick.

Here the way we deal with snapping objA to objB the safest way is to make a duplicate -po of objA (basically just duplicating the transform), unlock all transform attrs of objA_dupli, snap objA_dupli to objB via a temporary constraint, and copy back values from objA_dupli to objA. This way, if objA has animation, you don't end up with additionnal pairBlends, and it's not "reference sensitive", and the values copied are "local" ones.

So perhaps you could use the same approach by doing 2 loops:
Loop1: parsing all animation controls and creating the constraints between duplicated controls and their target, keeping track of the duplicated dummies created.
Loop2: copy back all possible values from the duplis to the real animation controls

Then delete all dummies (constraints should also be deleted)

This is kind of brute force, but iterating should be fast.
As I said earlier, I'm not 100% sure that when you delete the dummies, the graph evaluation doesn't makes offsets happen.
I'm not in front of maya here, but when I find some time at work, I'll definitely test this out on one of our rigs, which is similar to what you described in terms of broken hierarchy/constrained controls, etc...

Nicolas
 
  02 February 2013
Hi nico_the_last.

My original post was about finding an order of operations to apply to transformations, and I think that's working so far. Although Ive been struggling to get an object matching tool to work 100% of the time on all objects. So your comment is well timed.

You're idea is really interesting!

So, I whipped it up, and so far, this is the most reliable object position/rotation matching tool Ive ever used.

For the most part there are lots of object alignment tools out there that work really well. Although there are always a few weird cases where they don't work as expected. Even applying matrices hasnt been 100% reliable with flipped scale, or frozen transforms. So Im really impressed this is working on everything so far!

Im curious if this works for you and other people as well.

I didnt exactly set it up for running from a selection, but you could just do this:
matchAtoB(cmds.ls(sl=True)[0], cmds.ls(sl=True)[1])



def matchAtoB(objectA, objectB):
 	#duplicate A
 	A_duplicate = cmds.duplicate( objectA, po=True )[0]
 
 	#unlock all of duplicate A's arrtibutes
 	basicTransforms = ['translateX','translateY','translateZ','rotateX','  rotateY','rotateZ']
 	for attr in basicTransforms:
 		#unlock attr
 		cmds.setAttr((A_duplicate + '.' + attr), lock=False)
 
 	#snap duplicate A to B
 	cmds.delete(cmds.parentConstraint( objectB, A_duplicate))
 
 	#get list of unlocked channels in the channel box
 	nonKeyableChannels = cmds.listAttr(objectA, channelBox=True, unlocked=True, scalar=True)
 	keyableChannels = cmds.listAttr(objectA, keyable=True, unlocked=True, scalar=True)
 	channels = []
 	if type(nonKeyableChannels)==list:
 		channels = channels + nonKeyableChannels
 	if type(keyableChannels)==list:
 		channels = channels + keyableChannels
 	channels = list(set(channels))
 	
 	for attr in basicTransforms:
 		#apply attributes to A where possible
 		if attr in channels:
 			cmds.setAttr((objectA + '.' + attr), cmds.getAttr((A_duplicate + '.' + attr)))
 	
 	#get rid of temp transform
 	cmds.delete(A_duplicate)
 
 
  02 February 2013
Quote: ...frozen transforms...

Brrrrrrrr

Yeah it works well for us, as it's the way we handle one-to-one snapping.
 
  03 March 2013
(cross-linking some threads here)


Hey nico_the_last,

Check out this post. Thanks to Mark-J and his studio pack, he has a snap tool that uses OpenMaya getTranslate/rotate functions. Looks like a much cleaner/reliable snapping solution.

http://forums.cgsociety.org/showpos...42&postcount=16

Last edited by backwheelbates : 03 March 2013 at 05:00 PM.
 
  03 March 2013
Nice, will check it out!

Thanks for the tip...
 
  03 March 2013
Thread automatically closed

This thread has been automatically closed as it remained inactive for 12 months. If you wish to continue the discussion, please create a new thread in the appropriate forum.
__________________
CGTalk Policy/Legalities
Note that as CGTalk Members, you agree to the terms and conditions of using this website.
 
Thread Closed share thread



Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

vB code is On
Smilies are On
[IMG] code is On
HTML code is Off
CGSociety
Society of Digital Artists
www.cgsociety.org

Powered by vBulletin
Copyright 2000 - 2006,
Jelsoft Enterprises Ltd.
Minimize Ads
Forum Jump
Miscellaneous

All times are GMT. The time now is 02:00 PM.


Powered by vBulletin
Copyright ©2000 - 2017, Jelsoft Enterprises Ltd.