diff --git a/python/tweenMachine.png b/python/tweenMachine.png new file mode 100644 index 0000000..66b51f2 Binary files /dev/null and b/python/tweenMachine.png differ diff --git a/python/tweenMachine.py b/python/tweenMachine.py new file mode 100644 index 0000000..4da775a --- /dev/null +++ b/python/tweenMachine.py @@ -0,0 +1,1182 @@ +""" +tweenMachine.py + +author: Justin S Barrett + +description: Tool for creating breakdown or "tween" keyframes between the + previous and next keys, using a slider to adjust the bias/weight + that the surrounding keys have over the new key. + +usage: import tweenMachine + tweenMachine.start() + +revisions: + - 2013.04.12 - 3.0.0 - jbarrett + - Initial publish after conversion to Python + - 2015.05.22 - 3.0.0b1 - jbarrett + - First public beta release (limited feature set) + - 2015.07.11 - 3.0.0b1a - jbarrett + - Fixed: Overshoot mode reset properly from settings when restarting TM + - Fixed: When grabbing the global key tangent, make sure it's a string + - Fixed: If there are no curves to tween, force an empty list + - Changed: Disabled toolbar opt for Maya 2013 until a fix can be found + - 2016.06.07 - 3.0.0b1b - jbarrett + - Work around issue with keyTangent command in Maya 2016 Extension 2 + - 2017.04.20 - 3.0.0b1c - jbarrett + - Added Help menu + +to-do: + +""" + +# ------------------------------------------------------------------------- +# ----------------------------------------------------------- Imports ----- + +# Built-in +import os +import urllib2 +import xml.etree.cElementTree as etree + +# Third-party +import maya.cmds as mc +import maya.mel as mel + +# Custom + +# ------------------------------------------------------------------------- +# ----------------------------------------------------------- Globals ----- + +__version__ = "3.0.0 b1c" +MAYA_VERSION = mc.about(version=True) + + +# ------------------------------------------------------------------------- +# --------------------------------------------------------- Functions ----- + +def clear_menu(menu): + """ + Clear the specified menu of its current contents + """ + try: + [mc.deleteUI(i) for i in mc.menu(menu, q=True, ia=True)] + except: + pass + + +def defer_delete(item): + """ + Defer the deletion of a UI item to prevent Maya from crashing when that UI + item is still active as it's deleted + """ + mc.evalDeferred("mc.deleteUI('" + item + "')") + + +def find_ui(uitype): + found = mc.lsUI(type=uitype) + if found is None: + return "" + for item in found: + try: + doctag = eval("mc." + uitype + "(item, q=True, docTag=True)") + except RuntimeError: + doctag = "" + if doctag == "tweenMachine": + return item + return "" + + +def start(): + """ + Convenience function to open the main tweenMachine instance + """ + TMWindowUI() + + +def inactive(): + """ + Display a warning when a feature is not active + """ + mc.warning("This tweenMachine feature is not currently active.") + + +def tween(bias, nodes=None): + """ + Create the in-between key(s) on the specified nodes + """ + if isinstance(nodes, list) and not nodes: + nodes = None + # Find the current frame, where the new key will be added + currenttime = mc.timeControl("timeControl1", q=True, ra=True)[0] + # Figure out which nodes to pull from + if nodes is not None: + pullfrom = nodes + else: + pullfrom = mc.ls(sl=True) + if not pullfrom: + return + # If attributes are selected, use them to build curve node list + attributes = mc.channelBox("mainChannelBox", q=True, sma=True) + if attributes: + curves = [] + for attr in attributes: + for node in pullfrom: + fullnode = "%s.%s" % (node, attr) + if not mc.objExists(fullnode): + continue + tmp = mc.keyframe(fullnode, q=True, name=True) + if not tmp: + continue + curves += tmp + # Otherwise get curves for all nodes + else: + curves = mc.keyframe(pullfrom, q=True, name=True) + mc.waitCursor(state=True) + # Wrap the main operation in a try/except to prevent the waitcursor from + # sticking if something should fail + try: + # If we have no curves, force a list + if curves is None: + curves = [] + # Process all curves + for curve in curves: + # Find time for next and previous keys... + time_prev = mc.findKeyframe(curve, which="previous") + time_next = mc.findKeyframe(curve, which="next") + # Find previous and next tangent types + try: + in_tan_prev = mc.keyTangent(curve, time=(time_prev,), q=True, + itt=True)[0] + out_tan_prev = mc.keyTangent(curve, time=(time_prev,), q=True, + ott=True)[0] + in_tan_next = mc.keyTangent(curve, time=(time_next,), q=True, + itt=True)[0] + out_tan_next = mc.keyTangent(curve, time=(time_next,), q=True, + ott=True)[0] + # Workaround for keyTangent error in Maya 2016 Extension 2 + except RuntimeError: + in_tan_prev = mel.eval("keyTangent -time %s -q -itt %s" % (time_prev, curve))[0] + out_tan_prev = mel.eval("keyTangent -time %s -q -ott %s" % (time_prev, curve))[0] + in_tan_next = mel.eval("keyTangent -time %s -q -itt %s" % (time_next, curve))[0] + out_tan_next = mel.eval("keyTangent -time %s -q -ott %s" % (time_next, curve))[0] + # Set new in and out tangent types + in_tan_new = out_tan_prev + out_tan_new = in_tan_next + # However, if any of the types (previous or next) is "fixed", + # use the global (default) tangent instead + if "fixed" in [in_tan_prev, out_tan_prev, in_tan_next, out_tan_next]: + in_tan_new = mc.keyTangent(q=True, g=True, itt=True)[0] + out_tan_new = mc.keyTangent(q=True, g=True, ott=True)[0] + elif out_tan_next == "step": + out_tan_new = out_tan_next + # Find previous and next key values + value_prev = mc.keyframe(curve, time=(time_prev,), q=True, + valueChange=True)[0] + value_next = mc.keyframe(curve, time=(time_next,), q=True, + valueChange=True)[0] + value_new = value_prev + ((value_next - value_prev) * bias) + # Set new keyframe and tangents + mc.setKeyframe(curve, t=(currenttime,), v=value_new, + ott=out_tan_new) + if in_tan_new != "step": + mc.keyTangent(curve, t=(currenttime,), itt=in_tan_new) + # If we're using the special tick, set that appropriately + if SETTINGS["use_special_tick"]: + mc.keyframe(curve, tds=True, t=(currenttime,)) + except: + raise + finally: + mc.waitCursor(state=False) + mc.currentTime(currenttime) + mel.eval("global string $gMainWindow;") + windowname = mel.eval("$temp = $gMainWindow") + mc.setFocus(windowname) + + +# ------------------------------------------------------------------------- +# ----------------------------------------------------------- Classes ----- + +class TMData(object): + """ + Core code for data organization (groups and sets) + """ + + def __init__(self): + # Try to read preferences from option variables; otherwise use defaults + self.element = None + self.node = None + self.name = "selected" + self.groups = [] + # Try to read the existing XML data + oldnodes = mc.ls("tmXML*") + newnodes = mc.ls("tweenMachineData") + # if True: + # return + if oldnodes: + # If we have more than one, use the first one, but warn the user + self.node = oldnodes[0] + if len(oldnodes) > 1: + mc.warning("Multiple tweenMachine data nodes found. Using" + + self.node) + # If the data is in the old format (tmXML has children), convert it + if mc.listRelatives(self.node, children=True): + print "# tweenMachine: Old data found. Converting." + self.root = etree.XML("") + self.tree = etree.ElementTree(self.root) + # Create base elements + buttons_element = etree.SubElement(self.root, "buttons") + buttons_element.set("height", str(SETTINGS["button_height"])) + groups_element = etree.SubElement(self.root, "groups") + # Convert option data + slider_vis_node = mc.ls("tmSliderVis*")[0] + slider_vis_value = int(mc.getAttr(slider_vis_node + ".data")) + button_vis_node = mc.ls("tmSliderVis*")[0] + button_vis_value = int(mc.getAttr(button_vis_node + ".data")) + if slider_vis_value and button_vis_value: + show_mode = "both" + else: + if slider_vis_value: + show_mode = "slider" + else: + show_mode = "buttons" + SETTINGS["show_mode"] = show_mode + # Convert button data + buttons_node = mc.ls("tmButtons*")[0] + for node in mc.listRelatives(buttons_node, children=True): + suffix = node[-1] + bcolor = str(mc.getAttr("tmButtonRGB%s.data" % suffix)) + bvalue = str(mc.getAttr("tmButtonValue%s.data" % suffix)) + button_element = etree.SubElement(buttons_element, "button") + button_element.set("rgb", bcolor) + button_element.set("value", bvalue) + # Convert groups and sets + group_node = mc.ls("tmGroups*")[0] + for node in mc.ls(group_node + "|tmGroup*"): + # Get the group name and order + gname = str(mc.getAttr(node + ".id")) + gorder = str(mc.getAttr(node + ".order")) + group_element = etree.SubElement(groups_element, "group") + group_element.set("name", gname) + group_element.set("index", gorder) + # Get the sets in this group + for setnode in mc.listRelatives(node, children=True): + setname = str(mc.getAttr(setnode + ".id")) + setorder = str(mc.getAttr(setnode + ".order")) + # Create a set node and add the set objects to it + set_element = etree.SubElement(group_element, "set") + set_element.set("name", setname) + set_element.set("index", setorder) + setobjs = [] + for objnode in mc.listRelatives(setnode, children=True): + setobjs.append(str(mc.getAttr(objnode + ".data"))) + set_element.text = " ".join(setobjs) + # Otherwise get the data from the node + else: + self.root = etree.XML(mc.getAttr(node + ".data")) + self.tree = etree.ElementTree(self.root) + # Otherwise start from scratch + else: + self.root = etree.XML(""" + +