Euler Filter for Cinema 4D


#15

that would be awesome. :applause:


#16

Hi Clement, yeah I have similar issues with the navigation and selection tools in C4D. It’s weird as I jump between several 3d apps pretty regularly from C4D to Maya, ZBrush, Topogun and UV layout, and C4D is the only one where I struggle with the navigation. I’ve tried all of the different options and I use the script that was written by a member here to replicate the behavior I find more predictable (which helps but takes a bit of working with to use) but it’s still something that never feels natural to me.

I also run into issues with the selection tools - I find I’ll often select a particular controller, it will highlight for a moment but then the selection will jump to another object, almost as if C4D is assigning different selection priority to different objects even when they are the same type (typically curves) it’s weird.

I find that for me Maya is generally an easier package to animate characters in and I also love it from a rigging point of view because the nodal nature of it allows you to see the ‘layout’ of the nodes in the Node Editor and very quickly assess the connections between objects, it’s really nice to get that clear overview without digging through hierarchies in the Object Manager. Sort of like the difference between being in a hedge maze and having a ladder to see the entire map at once :slight_smile: It’s also much easier to script for IMO due to how deeply implemented MEL is and finally there’s the scene response with rigged characters and heavy animation data which always feels much more fluid to me than a comparable rig in C4D.

So yes, I find I have a much easier time of it with Maya for character stuff. To balance things though I will say that parts of Maya are clunky too. Recently I’ve tried out the Bullet physics in Maya and almost immediately I ran into some pretty fundamental bugs with it, and when I tried the same things in C4D they worked much better, I got the results I needed without fuss.

So there’s no ‘perfect’ app in my book, each has different strengths, but character work is my focus so I follow the path of least resistance for that. The projects I work on tend to have very short deadlines so I’m focused on having the fastest workflow possible.

That’s great news though that Per Anders has a Euler filter script though, fingers crossed it works well as remember trying one out before on the beta that didn’t seem to work as expected.

Cheers,
Brian


#17

I’ve always been able to get my head around issues. Though this one has me stumped.

Exciting stuff Per, the (cinema 4d) world is watching…


#18

Thanks Per. I’m starting on making a rig that will have mocap stuff but they want to be able to animate on top of that so this will help when/if we run into problems.

I know it would have helped on my last C4D character project. We had maya animators running into issues when using C4D and they just to run the euler filter to fix them.


#19

Clamping on, I’ve had this issue as well.


#20

Here you chaps go, I’ve not done extensive testing, but this script is a slightly tarted up version of my old euler filter script. It’s a python script, the attached zip also contains a small icon if you fancy it. Simply select an object, it’s tracks or even just specific keys and hit the command to apply. It is set to only work with the first timeline window (so if you make use of four timeline windows be aware of this), this is simply a limitation due to python not being able to determine which is the active window, but apart from that you should be good to go. It should also work with the f-curve mode of the timeline, and in theory it should adjust the f-curve tangents for you too.

Have fun, happy thanksgiving.

import c4d
 import math
 from c4d import gui
 
 #Cinema 4D Euler Fiter Command
 #Select an object, it's tracks, or keys and run to apply the filter
 #Have no selection to apply the filter to the entire scene!
 #Per-Anders Edwards 2013
 #Happy Thanksgiving!
 
 #check for selection, limited to just the first timeline in order to keep thigns simple
 #it could be possible to extend this to cover all four timelines
 #but determining the active f-curve/key window between all four isn't possible in python currently
 def checkSelect(obj):
 	#Check selection within all four timelines
 	if c4d.IsCommandChecked(456001191):
 		if obj.GetNBit(c4d.NBIT_TL1_SELECT):
 			return True
 		
 		#Check selection within all four timelines
 		if obj.GetNBit(c4d.NBIT_TL1_SELECT2):
 			return True
 		
 	#Check selection within all four fcurve managers
 	if c4d.IsCommandChecked(465001190):
 		if obj.GetNBit(c4d.NBIT_TL1_FCSELECT):
 			return True
 
 	
 	#Key specific check
 	if obj.GetNBit(c4d.NBIT_CKEY_ACTIVE):
 		return True
 	
 	#No selection
 	return False
 
 #main euler filter function
 def main():
 	arr = doc.GetActiveObjects(c4d.GETACTIVEOBJECTFLAGS_CHILDREN)
 	if len(arr) == 0:
 		op = doc.GetFirstObject()
 		while op:
 			arr.append(op)
 			if op.GetDown():
 				op = op.GetDown()
 			else:
 				while (not op.GetNext()) and op.GetUp():
 					op = op.GetUp()
 				op = op.GetNext()
 				
 	hasSelectedTracks = False
 	hasSelectedKeys = False
 	
 	#find out if there's a selection to use
 	for op in arr:
 		track = op.GetFirstCTrack()
 
 		while track is not None and not hasSelectedTracks and not hasSelectedKeys:
 			if track.GetDescriptionID()[0].id == c4d.ID_BASEOBJECT_REL_ROTATION or track.GetDescriptionID()[0].id == c4d.ID_BASEOBJECT_ABS_ROTATION:
 					
 				if checkSelect(track):
 					hasSelectedTracks = True
 					break
 				
 				curve = track.GetCurve()
 				while curve is not None and not hasSelectedKeys:
 					keyCount = curve.GetKeyCount()
 					for i in range(0, keyCount):
 						key = curve.GetKey(i)
 						if checkSelect(key):
 							hasSelectedKeys = True
 							break
 						
 					curve = curve.GetNext()
 			
 			track = track.GetNext()
 		
 	#Filter the keys to be within 180 degrees of their previous neighbors
 	for op in arr:
 		track = op.GetFirstCTrack()
 		
 		while track is not None:
 			lastValue = 0.0
 			if track.GetDescriptionID()[0].id == c4d.ID_BASEOBJECT_REL_ROTATION or track.GetDescriptionID()[0].id == c4d.ID_BASEOBJECT_ABS_ROTATION:
 				if not hasSelectedTracks or checkSelect(track):
 					curve = track.GetCurve()
 					while curve is not None:
 						keyCount = curve.GetKeyCount()
 						for i in range(0,keyCount):
 							key = curve.GetKey(i)
 							kval = key.GetValue()
 							
 							if hasSelectedKeys and not checkSelect(key):
 								lastValue = kval
 								continue
 							
 							if i != 0:
 								#Start an undo for the change made
 								doc.AddUndo(c4d.UNDO_CHANGE, key)
 								
 								#Calculate the modulo key offset
 								mval = math.fmod((kval - lastValue), math.pi * 2.0)
 								delta = (lastValue + mval) - kval
 								
 								#Now handle the key tangents to maintain a nice predictable effect
 								if i > 0:
 									#left tangent
 									leftKey = curve.GetKey(i - 1)
 									leftKVal = leftKey.GetValue()			 
 									dif = kval - leftKVal
 									if dif != 0.0:
 										doc.AddUndo(c4d.UNDO_CHANGE, leftKey)
 										kLeftVal = key.GetValueLeft()
 										leftKRightVal = leftKey.GetValueRight()
 										
 										proportion = (delta + dif) / dif
 									
 										kLeftVal = kLeftVal * proportion
 										leftKRightVal = leftKRightVal * proportion
 										leftKey.SetValueRight(curve, leftKRightVal)
 										key.SetValueLeft(curve, kLeftVal)
 									
 								if i < keyCount - 1:
 									#right tangent
 									rightKey = curve.GetKey(i + 1)
 									rightKVal = rightKey.GetValue()			 
 									dif = kval - rightKVal
 									if dif != 0.0:
 										doc.AddUndo(c4d.UNDO_CHANGE, rightKey)
 										kRightVal = key.GetValueRight()
 										rightKLeftVal = rightKey.GetValueLeft()
 										
 										proportion = (delta + dif) / dif
 									
 										kRightVal = kRightVal * proportion
 										rightKLeftVal = rightKLeftVal * proportion
 										rightKey.SetValueLeft(curve, rightKLeftVal)
 										key.SetValueRight(curve, kRightVal)								   
 								
 								#finally set the key value itself
 								kval = lastValue + mval
 								key.SetValue(curve, kval)
 								
 								
 							lastValue = kval
 							
 						curve = curve.GetNext()
 					
 			track = track.GetNext()
 		
 	c4d.EventAdd()
 
 if __name__=='__main__':
 	main()
 

#21

Could you share or point me out to such file? I downloaded a couple of free mocap files for some testing but they behave all fine.

Thanks in advance,

bunk


#22

I’ve also added it to my wiki for ease of access in future and to the scripts thread in the scripting subforum here : http://www.peranders.com/wiki/Euler_Filter


#23

Thanks Per but unfortunately it doesn’t work correctly, as I mentioned on the previous page it needs to be able to detect a flip when it’s not caused by a full 360 in one axis.

Quick example of where it fails.

http://www.graphite9.com/MiscClips/EullersDayOff/EullersDayOff.html

Cheers,
Brian


#24

Edit, actually dont’ worry i was able to rectrate, though your capture was so small i had to peer hard on my monitor to see what values you had. Sure that should be fixable.


#25

Sorry silly new version of Camtasia makes them small like that by default. You can click the full-screen button in the time bar to see it a little better.

To clarify the values of each key are 1: 0,0,0 then 2: 180,180,180 then 3: 360,360,360 and finally back to 0,0,0 for the fourth key. I created the same values in both examples. The Maya Euler Filter version sees the 2nd key (180,180,180) as a flip but the Python version only sees the difference between the last two keys as a flip so it makes the last key 360,360,360 too but leaves the 2nd key (180,180,180) as is so the flip still occurs. With the Maya one all four keys end up being 0,0,0.

Cheers,
Brian


#26

Yup, found that out, try this :

I wont necessarily be able to resolve correctly if you only allow it a single key on a single track rather than all hpb tracks as you're locking it out from changing the other keys then, and if a key only exists on one track it will generate new keys on the other tracks in order to force generate a decent rotation.
import c4d
  import math
  from c4d import gui
  
  #Cinema 4D Euler Fiter Command
  #Select an object, it's tracks, or keys and run to apply the filter
  #Have no selection to apply the filter to the entire scene!
  #Per-Anders Edwards 2013
  #Happy Thanksgiving!
  
  #check for selection, limited to just the first timeline in order to keep thigns simple
  #it could be possible to extend this to cover all four timelines
  #but determining the active f-curve/key window between all four isn't possible in python currently
  def checkSelect(obj):
  	#Check selection in timeline
  	if c4d.IsCommandChecked(465001191) is True:
  		if obj.GetNBit(c4d.NBIT_TL1_SELECT):
  			return True
  		
  		if obj.GetNBit(c4d.NBIT_TL1_SELECT2):
  			return True
  		
  	#Check selection in fcurve manager
  	if c4d.IsCommandChecked(465001190) is True:
  		if obj.GetNBit(c4d.NBIT_TL1_FCSELECT):
  			return True
  
  	
  	#Key specific check
  	if obj.GetNBit(c4d.NBIT_CKEY_ACTIVE):
  		return True
  	
  	#No selection
  	return False
  
  def shiftKey(doc, key, keyIndex, curve, newValue):
  	keyCount = curve.GetKeyCount()
  	kval = key.GetValue()
  	delta = newValue - kval
  	
  	if keyIndex > 0:
  		#left tangent
  		leftKey = curve.GetKey(keyIndex - 1)
  		leftKVal = leftKey.GetValue()			 
  		dif = kval - leftKVal
  		if dif != 0.0:
  			doc.AddUndo(c4d.UNDO_CHANGE, leftKey)
  			kLeftVal = key.GetValueLeft()
  			leftKRightVal = leftKey.GetValueRight()
  			
  			proportion = (delta + dif) / dif
  		
  			kLeftVal = kLeftVal * proportion
  			leftKRightVal = leftKRightVal * proportion
  			leftKey.SetValueRight(curve, leftKRightVal)
  			key.SetValueLeft(curve, kLeftVal)
  		
  	if keyIndex < keyCount - 1:
  		#right tangent
  		rightKey = curve.GetKey(keyIndex + 1)
  		rightKVal = rightKey.GetValue()			 
  		dif = kval - rightKVal
  		if dif != 0.0:
  			doc.AddUndo(c4d.UNDO_CHANGE, rightKey)
  			kRightVal = key.GetValueRight()
  			rightKLeftVal = rightKey.GetValueLeft()
  			
  			proportion = (delta + dif) / dif
  		
  			kRightVal = kRightVal * proportion
  			rightKLeftVal = rightKLeftVal * proportion
  			rightKey.SetValueLeft(curve, rightKLeftVal)
  			key.SetValueRight(curve, kRightVal)								   
  	
  	#finally set the key value itself
  	key.SetValue(curve, newValue)
  
  #main euler filter function
  def main():
  	arr = doc.GetActiveObjects(c4d.GETACTIVEOBJECTFLAGS_CHILDREN)
  	if len(arr) == 0:
  		op = doc.GetFirstObject()
  		while op:
  			arr.append(op)
  			if op.GetDown():
  				op = op.GetDown()
  			else:
  				while (not op.GetNext()) and op.GetUp():
  					op = op.GetUp()
  				op = op.GetNext()
  				
  	hasSelectedTracks = False
  	hasSelectedKeys = False
  	
  	#find out if there's a selection to use
  	for op in arr:
  		track = op.GetFirstCTrack()
  		
  		while track is not None and not hasSelectedTracks and not hasSelectedKeys:
  			if track.GetDescriptionID()[0].id == c4d.ID_BASEOBJECT_REL_ROTATION or track.GetDescriptionID()[0].id == c4d.ID_BASEOBJECT_ABS_ROTATION:
  					
  				if checkSelect(track):
  					hasSelectedTracks = True
  					break
  				
  				curve = track.GetCurve()
  				while curve is not None and not hasSelectedKeys:
  					keyCount = curve.GetKeyCount()
  					for i in range(0, keyCount):
  						key = curve.GetKey(i)
  						if checkSelect(key):
  							hasSelectedKeys = True
  							break
  						
  					curve = curve.GetNext()
  			
  			track = track.GetNext()
  		
  	#Filter the keys to be within 180 degrees of their previous neighbors
  	for op in arr:
  		trackH = op.FindCTrack([c4d.ID_BASEOBJECT_REL_ROTATION,c4d.VECTOR_X])
  		trackP = op.FindCTrack([c4d.ID_BASEOBJECT_REL_ROTATION,c4d.VECTOR_Y])
  		trackB = op.FindCTrack([c4d.ID_BASEOBJECT_REL_ROTATION,c4d.VECTOR_Z])
  		
  		if hasSelectedTracks and not (checkSelect(trackH) or checkSelect(trackP) or checkSelect(trackB)):
  			continue
     
  		curveH = trackH.GetCurve() if trackH is not None else None
  		curveP = trackP.GetCurve() if trackP is not None else None
  		curveB = trackB.GetCurve() if trackB is not None else None
  		
  		keyTimeList = []
  		
  		for curve in [curveH, curveP, curveB]:
  			if curve is None:
  				continue
  			keyCount = curve.GetKeyCount()
  			for i in range(0, keyCount):
  				key = curve.GetKey(i)
  				if not hasSelectedKeys or checkSelect(key):
  					keyTimeList.append(key.GetTime())
  		
  		keyTimeList.sort()
  		lastKey = keyTimeList[-1]
  		for i in range(len(keyTimeList) - 2, -1, -1):
  			if lastKey == keyTimeList[i]:
  				del(keyTimeList[i])
  			else:
  				lastKey = keyTimeList[i]
  		
  		trackList = [trackH, trackP, trackB] 
  		
  		for keyTime in keyTimeList:
  			valH = trackH.GetValue(doc, keyTime, doc.GetFps()) if trackH is not None else op[c4d.ID_BASEOBJECT_REL_ROTATION,c4d.VECTOR_X]
  			valP = trackP.GetValue(doc, keyTime, doc.GetFps()) if trackP is not None else op[c4d.ID_BASEOBJECT_REL_ROTATION,c4d.VECTOR_Y]
  			valB = trackB.GetValue(doc, keyTime, doc.GetFps()) if trackB is not None else op[c4d.ID_BASEOBJECT_REL_ROTATION,c4d.VECTOR_Z]
  			
  			rotationMatrix = c4d.utils.HPBToMatrix(c4d.Vector(valH, valP, valB), op[c4d.ID_BASEOBJECT_ROTATION_ORDER])
  			hpb = c4d.utils.MatrixToHPB(rotationMatrix, op[c4d.ID_BASEOBJECT_ROTATION_ORDER])
  			
  			for curve, val in [[curveH, hpb.x], [curveP, hpb.y], [curveB, hpb.z]]:
  				if curve is None:
  					continue
  				
  				key, index = curve.FindKey(keyTime).values()
  				
  				if key is None:
  					key, index = curve.addKey(keyTime)
  					doc.AddUndo(c4d.UNDO_NEW, key)
  					
  				if hasSelectedKeys and not checkSelect(key):
  					continue 
  				
  				doc.AddUndo(c4d.UNDO_CHANGE, key)	
  				
  				lastValue = curve.GetKey(index - 1).GetValue() if index > 0 else key.GetValue()
  				
  				offset = math.fmod((val - lastValue), math.pi * 2.0) + lastValue
  				
  				shiftKey(doc, key, index, curve, lastValue)
  	
  	c4d.EventAdd()
  
  if __name__=='__main__':
  	main()
     

Also here : http://www.peranders.com/wiki/Euler_Filter_2

edit - Forgot to add the attachment of the command in a neat little package for those that want it :


#27

Well i set up a test for the first one you did and it performed admirably. It took care of about 90-95% of the problems. I was rendering out a version to show you, but now i’ll try the second version before rendering out an animation.

Needless to say you are a superstar for doing this Per…

Here is a still from the test while i try the new script…


#28

Trouble in paradise.

In the image below the lower graph shows where my problems stem from, You can see the values bounce 360 up & down (even though its about the same value).

With FerrisEuler 1 the lower graph was fixed.

With FerrisEuler 2 it was too, but the upper graph was flattened?!

Maybe i should post an example mo-cap file for testing purposes?


#29

Please do post any files you have problems with, I’m sure they can be resolved. The real question is whether flattened or not was the motion correct?

As of right now the second version requires all three tracks in order to solve correctly, however it should be possible to change this.


#30

Here, try this version, this changes the behavior in that it will try to force setting the keys on the other tracks if they exist provided one key is selected, which is a bit cheeky, if that’s undesirable it’s easy to change :

import c4d
 import math
 from c4d import gui
 
 #Cinema 4D Euler Fiter Command
 #Select an object, it's tracks, or keys and run to apply the filter
 #Have no selection to apply the filter to the entire scene!
 #Per-Anders Edwards 2013
 #Happy Thanksgiving!
 
 #check for selection, limited to just the first timeline in order to keep thigns simple
 #it could be possible to extend this to cover all four timelines
 #but determining the active f-curve/key window between all four isn't possible in python currently
 def checkSelect(obj):
 	#Check selection in timeline
 	if c4d.IsCommandChecked(465001191) is True:
 		if obj.GetNBit(c4d.NBIT_TL1_SELECT):
 			return True
 		
 		if obj.GetNBit(c4d.NBIT_TL1_SELECT2):
 			return True
 		
 	#Check selection in fcurve manager
 	if c4d.IsCommandChecked(465001190) is True:
 		if obj.GetNBit(c4d.NBIT_TL1_FCSELECT):
 			return True
 
 	
 	#Key specific check
 	if obj.GetNBit(c4d.NBIT_CKEY_ACTIVE):
 		return True
 	
 	#No selection
 	return False
 
 def shiftKey(doc, key, keyIndex, curve, newValue):
 	keyCount = curve.GetKeyCount()
 	kval = key.GetValue()
 	
 	if kval == newValue:
 		return
 	
 	delta = newValue - kval
 	
 	if keyIndex > 0:
 		#left tangent
 		leftKey = curve.GetKey(keyIndex - 1)
 		leftKVal = leftKey.GetValue()			 
 		dif = kval - leftKVal
 		if dif != 0.0:
 			doc.AddUndo(c4d.UNDO_CHANGE, leftKey)
 			kLeftVal = key.GetValueLeft()
 			leftKRightVal = leftKey.GetValueRight()
 			
 			proportion = (delta + dif) / dif
 		
 			kLeftVal = kLeftVal * proportion
 			leftKRightVal = leftKRightVal * proportion
 			leftKey.SetValueRight(curve, leftKRightVal)
 			key.SetValueLeft(curve, kLeftVal)
 		
 	if keyIndex < keyCount - 1:
 		#right tangent
 		rightKey = curve.GetKey(keyIndex + 1)
 		rightKVal = rightKey.GetValue()			 
 		dif = kval - rightKVal
 		if dif != 0.0:
 			doc.AddUndo(c4d.UNDO_CHANGE, rightKey)
 			kRightVal = key.GetValueRight()
 			rightKLeftVal = rightKey.GetValueLeft()
 			
 			proportion = (delta + dif) / dif
 		
 			kRightVal = kRightVal * proportion
 			rightKLeftVal = rightKLeftVal * proportion
 			rightKey.SetValueLeft(curve, rightKLeftVal)
 			key.SetValueRight(curve, kRightVal)								   
 	
 	#finally set the key value itself
 	key.SetValue(curve, newValue)
 
 #main euler filter function
 def main():
 	arr = doc.GetActiveObjects(c4d.GETACTIVEOBJECTFLAGS_CHILDREN)
 	if len(arr) == 0:
 		op = doc.GetFirstObject()
 		while op:
 			arr.append(op)
 			if op.GetDown():
 				op = op.GetDown()
 			else:
 				while (not op.GetNext()) and op.GetUp():
 					op = op.GetUp()
 				op = op.GetNext()
 				
 	hasSelectedTracks = False
 	hasSelectedKeys = False
 	
 	#find out if there's a selection to use
 	for op in arr:
 		track = op.GetFirstCTrack()
 		
 		while track is not None and not hasSelectedTracks and not hasSelectedKeys:
 			if track.GetDescriptionID()[0].id == c4d.ID_BASEOBJECT_REL_ROTATION or track.GetDescriptionID()[0].id == c4d.ID_BASEOBJECT_ABS_ROTATION:
 					
 				if checkSelect(track):
 					hasSelectedTracks = True
 					break
 				
 				curve = track.GetCurve()
 				while curve is not None and not hasSelectedKeys:
 					keyCount = curve.GetKeyCount()
 					for i in range(0, keyCount):
 						key = curve.GetKey(i)
 						if checkSelect(key):
 							hasSelectedKeys = True
 							break
 						
 					curve = curve.GetNext()
 			
 			track = track.GetNext()
 		
 	#Filter the keys to be within 180 degrees of their previous neighbors
 	for op in arr:
 		trackH = op.FindCTrack([c4d.ID_BASEOBJECT_REL_ROTATION,c4d.VECTOR_X])
 		trackP = op.FindCTrack([c4d.ID_BASEOBJECT_REL_ROTATION,c4d.VECTOR_Y])
 		trackB = op.FindCTrack([c4d.ID_BASEOBJECT_REL_ROTATION,c4d.VECTOR_Z])
 		
 		if hasSelectedTracks and not (checkSelect(trackH) or checkSelect(trackP) or checkSelect(trackB)):
 			continue
    
 		curveH = trackH.GetCurve() if trackH is not None else None
 		curveP = trackP.GetCurve() if trackP is not None else None
 		curveB = trackB.GetCurve() if trackB is not None else None
 		
 		keyTimeList = []
 		
 		for curve in [curveH, curveP, curveB]:
 			if curve is None:
 				continue
 			keyCount = curve.GetKeyCount()
 			for i in range(0, keyCount):
 				key = curve.GetKey(i)
 				if not hasSelectedKeys or checkSelect(key):
 					doc.AddUndo(c4d.UNDO_CHANGE, key)
 					keyTimeList.append([key.GetTime().Get(), key.GetTime()])
 		
 		keyTimeList.sort()
 		lastKey = keyTimeList[-1]
 		for i in range(len(keyTimeList) - 2, -1, -1):
 			if lastKey == keyTimeList[i]:
 				del(keyTimeList[i])
 			else:
 				lastKey = keyTimeList[i]
 		
 		#doing this four iterations seems to solve the whole thing in the cleanest way
 		lastKeyVal = c4d.Vector(0.0, 0.0, 0.0)
 		for keyTimeEntry in keyTimeList:
 			keyTime = keyTimeEntry[1]
 			valH = trackH.GetValue(doc, keyTime, doc.GetFps()) if trackH is not None else op[c4d.ID_BASEOBJECT_REL_ROTATION,c4d.VECTOR_X]
 			valP = trackP.GetValue(doc, keyTime, doc.GetFps()) if trackP is not None else op[c4d.ID_BASEOBJECT_REL_ROTATION,c4d.VECTOR_Y]
 			valB = trackB.GetValue(doc, keyTime, doc.GetFps()) if trackB is not None else op[c4d.ID_BASEOBJECT_REL_ROTATION,c4d.VECTOR_Z]
 			
 			valVector = c4d.Vector(valH, valP, valB)
 			
 			if keyTime == keyTimeList[0][1]:
 				lastKeyVal = valVector
 				continue
 			
 			hpb = c4d.utils.GetOptimalAngle(lastKeyVal, valVector, op[c4d.ID_BASEOBJECT_ROTATION_ORDER])
 			
 			for curve, val, lastValue in [[curveH, hpb.x, lastKeyVal.x], [curveP, hpb.y, lastKeyVal.y], [curveB, hpb.z, lastKeyVal.z]]:
 				if curve is None:
 					continue
 				
 				key, index = curve.FindKey(keyTime).values()
 				
 				if key is None:
 					key, index = curve.addKey(keyTime)
 					doc.AddUndo(c4d.UNDO_NEW, key)
 				
 				shiftKey(doc, key, index, curve, val)
 				
 			lastKeyVal = hpb
 	
 	c4d.EventAdd()
 
 if __name__=='__main__':
 	main()
 

#31

And here’s a version that respects your key/track selections a little bit better (although no guarantees on the output if you select only a single track!), and fixes a bug with adding keys automatically.

import c4d
 import math
 from c4d import gui
 
 #Cinema 4D Euler Fiter Command
 #Select an object, it's tracks, or keys and run to apply the filter
 #Have no selection to apply the filter to the entire scene!
 #Per-Anders Edwards 2013
 #Happy Thanksgiving!
 
 #check for selection, limited to just the first timeline in order to keep thigns simple
 #it could be possible to extend this to cover all four timelines
 #but determining the active f-curve/key window between all four isn't possible in python currently
 def checkSelect(obj):
 	#Check selection in timeline
 	if c4d.IsCommandChecked(465001191) is True:
 		if obj.GetNBit(c4d.NBIT_TL1_SELECT):
 			return True
 		
 		if obj.GetNBit(c4d.NBIT_TL1_SELECT2):
 			return True
 		
 	#Check selection in fcurve manager
 	if c4d.IsCommandChecked(465001190) is True:
 		if obj.GetNBit(c4d.NBIT_TL1_FCSELECT):
 			return True
 
 	
 	#Key specific check
 	if obj.GetNBit(c4d.NBIT_CKEY_ACTIVE):
 		return True
 	
 	#No selection
 	return False
 
 def shiftKey(doc, key, keyIndex, curve, newValue):
 	keyCount = curve.GetKeyCount()
 	kval = key.GetValue()
 	
 	if kval == newValue:
 		return
 	
 	delta = newValue - kval
 	
 	if keyIndex > 0:
 		#left tangent
 		leftKey = curve.GetKey(keyIndex - 1)
 		leftKVal = leftKey.GetValue()			 
 		dif = kval - leftKVal
 		if dif != 0.0:
 			doc.AddUndo(c4d.UNDO_CHANGE, leftKey)
 			kLeftVal = key.GetValueLeft()
 			leftKRightVal = leftKey.GetValueRight()
 			
 			proportion = (delta + dif) / dif
 		
 			kLeftVal = kLeftVal * proportion
 			leftKRightVal = leftKRightVal * proportion
 			leftKey.SetValueRight(curve, leftKRightVal)
 			key.SetValueLeft(curve, kLeftVal)
 		
 	if keyIndex < keyCount - 1:
 		#right tangent
 		rightKey = curve.GetKey(keyIndex + 1)
 		rightKVal = rightKey.GetValue()			 
 		dif = kval - rightKVal
 		if dif != 0.0:
 			doc.AddUndo(c4d.UNDO_CHANGE, rightKey)
 			kRightVal = key.GetValueRight()
 			rightKLeftVal = rightKey.GetValueLeft()
 			
 			proportion = (delta + dif) / dif
 		
 			kRightVal = kRightVal * proportion
 			rightKLeftVal = rightKLeftVal * proportion
 			rightKey.SetValueLeft(curve, rightKLeftVal)
 			key.SetValueRight(curve, kRightVal)								   
 	
 	#finally set the key value itself
 	key.SetValue(curve, newValue)
 
 #main euler filter function
 def main():
 	arr = doc.GetActiveObjects(c4d.GETACTIVEOBJECTFLAGS_CHILDREN)
 	if len(arr) == 0:
 		op = doc.GetFirstObject()
 		while op:
 			arr.append(op)
 			if op.GetDown():
 				op = op.GetDown()
 			else:
 				while (not op.GetNext()) and op.GetUp():
 					op = op.GetUp()
 				op = op.GetNext()
 				
 	hasSelectedTracks = False
 	hasSelectedKeys = False
 	
 	#find out if there's a selection to use
 	for op in arr:
 		track = op.GetFirstCTrack()
 		
 		while track is not None and not hasSelectedTracks and not hasSelectedKeys:
 			if track.GetDescriptionID()[0].id == c4d.ID_BASEOBJECT_REL_ROTATION or track.GetDescriptionID()[0].id == c4d.ID_BASEOBJECT_ABS_ROTATION:
 					
 				if checkSelect(track):
 					hasSelectedTracks = True
 					break
 				
 				curve = track.GetCurve()
 				while curve is not None and not hasSelectedKeys:
 					keyCount = curve.GetKeyCount()
 					for i in range(0, keyCount):
 						key = curve.GetKey(i)
 						if checkSelect(key):
 							hasSelectedKeys = True
 							break
 						
 					curve = curve.GetNext()
 			
 			track = track.GetNext()
 		
 	#Filter the keys to be within 180 degrees of their previous neighbors
 	for op in arr:
 		trackH = op.FindCTrack([c4d.ID_BASEOBJECT_REL_ROTATION,c4d.VECTOR_X])
 		trackP = op.FindCTrack([c4d.ID_BASEOBJECT_REL_ROTATION,c4d.VECTOR_Y])
 		trackB = op.FindCTrack([c4d.ID_BASEOBJECT_REL_ROTATION,c4d.VECTOR_Z])
 		
 		if hasSelectedTracks and not (checkSelect(trackH) or checkSelect(trackP) or checkSelect(trackB)):
 			continue
    
 		curveH = trackH.GetCurve() if trackH is not None else None
 		curveP = trackP.GetCurve() if trackP is not None else None
 		curveB = trackB.GetCurve() if trackB is not None else None
 		
 		keyTimeList = []
 		
 		for curve in [curveH, curveP, curveB]:
 			if curve is None:
 				continue
 			keyCount = curve.GetKeyCount()
 			for i in range(0, keyCount):
 				key = curve.GetKey(i)
 				if not hasSelectedKeys or checkSelect(key):
 					doc.AddUndo(c4d.UNDO_CHANGE, key)
 					keyTimeList.append([key.GetTime().Get(), key.GetTime()])
 		
 		keyTimeList.sort()
 		lastKey = keyTimeList[-1]
 		for i in range(len(keyTimeList) - 2, -1, -1):
 			if lastKey == keyTimeList[i]:
 				del(keyTimeList[i])
 			else:
 				lastKey = keyTimeList[i]
 		
 		lastKeyVal = c4d.Vector(0.0, 0.0, 0.0)
 		for keyTimeEntry in keyTimeList:
 			keyTime = keyTimeEntry[1]
 			valH = trackH.GetValue(doc, keyTime, doc.GetFps()) if trackH is not None else op[c4d.ID_BASEOBJECT_REL_ROTATION,c4d.VECTOR_X]
 			valP = trackP.GetValue(doc, keyTime, doc.GetFps()) if trackP is not None else op[c4d.ID_BASEOBJECT_REL_ROTATION,c4d.VECTOR_Y]
 			valB = trackB.GetValue(doc, keyTime, doc.GetFps()) if trackB is not None else op[c4d.ID_BASEOBJECT_REL_ROTATION,c4d.VECTOR_Z]
 			
 			valVector = c4d.Vector(valH, valP, valB)
 			
 			if keyTime == keyTimeList[0][1]:
 				lastKeyVal = valVector
 				continue
 			
 			hpb = c4d.utils.GetOptimalAngle(lastKeyVal, valVector, op[c4d.ID_BASEOBJECT_ROTATION_ORDER])
 			
 			for curve, val, lastValue in [[curveH, hpb.x, lastKeyVal.x], [curveP, hpb.y, lastKeyVal.y], [curveB, hpb.z, lastKeyVal.z]]:
 				if curve is None:
 					continue
 				
 				if hasSelectedTracks and not checkSelect(curve.GetTrack()):
 					continue
 				
 				try:
 					key, index = curve.FindKey(keyTime).values()
 				except:
 					try:
 						index, key = curve.AddKey(keyTime).values()
 						doc.AddUndo(c4d.UNDO_NEW, key)
 						if hasSelectedKeys:
 							key.ChangeNBit(c4d.NBIT_TL1_SELECT, True)
 					except:
 						continue
 					
 				if not hasSelectedKeys or checkSelect(key):
 					shiftKey(doc, key, index, curve, val)
 				
 			lastKeyVal = hpb
 	
 	c4d.EventAdd()
 
 if __name__=='__main__':
 	main()
 
   

#32

thanks Per ! . I’m testing it now .


#33

I will try these out and i’ll post an example file later too…


#34

Hello Per
Thank you