975 lines
40 KiB
Python
975 lines
40 KiB
Python
'''
|
|
========================================================================================================================
|
|
Author: Alan Camilo
|
|
www.alancamilo.com
|
|
Modified: Michael Klimenko
|
|
|
|
Requirements: aTools Package
|
|
|
|
------------------------------------------------------------------------------------------------------------------------
|
|
To install aTools, please follow the instructions in the file how_to_install.txt
|
|
|
|
------------------------------------------------------------------------------------------------------------------------
|
|
To unistall aTools, go to menu (the last button on the right), Uninstall
|
|
|
|
========================================================================================================================
|
|
'''
|
|
|
|
from maya import cmds
|
|
from maya import OpenMaya
|
|
from maya import OpenMayaAnim
|
|
from aTools.generalTools.aToolsGlobals import aToolsGlobals as G
|
|
from aTools.commonMods import uiMod
|
|
from aTools.commonMods import utilMod
|
|
from aTools.commonMods import animMod
|
|
from aTools.commonMods import aToolsMod
|
|
|
|
import os
|
|
import time
|
|
import datetime
|
|
import math
|
|
|
|
|
|
|
|
class AnimationCrashRecovery(object):
|
|
|
|
def __init__(self):
|
|
|
|
|
|
G.animationCrashRecovery = self
|
|
|
|
self.deferredQueue = []
|
|
self.animCurvesNames = []
|
|
self.animCurvesInfo = {}
|
|
self.nonKeyedAttrInfo = {}
|
|
self.baseFolderName = "animationCrashRecovery"
|
|
self.baseLatestFolderName = "latest"
|
|
self.baseBackupFolderName = "backup"
|
|
self.infoDataFileName = "infoData"
|
|
self.selectedObjs = []
|
|
self.ignoreAttrs = ["visibility"]
|
|
self.curveExt = "curve"
|
|
self.attrExt = "attr"
|
|
self.curvesInFile = []
|
|
self.nonKeyedAttrsInFile = []
|
|
self.mayaFileName = None
|
|
self.pause = False
|
|
self.mayaFileName = utilMod.getMayaFileName()
|
|
self.mayaFilePath = utilMod.getMayaFileName("path")
|
|
G.ACR_messages = G.ACR_messages or {"anim":[], "node":[], "scene":[], "mdg":[]}
|
|
self.blinkingLedState = False
|
|
self.saveRecommended = True
|
|
self.checkNodeCreated = True
|
|
G.lastSaveWarning = G.lastSaveWarning or None
|
|
self.redBlinkingSecs = 300#300 = 5 minutes
|
|
self.daysToKeepOldFiles = 5*86400#5days
|
|
self.nodesCreated = []
|
|
#self.daysToKeepOldFiles = 10#TMP
|
|
|
|
self.checkForCrashLog()
|
|
self.checkAndClearOldFiles()
|
|
|
|
#G.deferredManager.removeFromQueue("ACR")#TMP
|
|
|
|
def switch(self, onOff):
|
|
|
|
|
|
self.removeMessages()
|
|
utilMod.killScriptJobs("G.animationCrashRecoveryScriptJobs")
|
|
|
|
if onOff:
|
|
|
|
#self.saveAllAnimationData(update=True)
|
|
self.addAnimSceneMessages()
|
|
self.addNodeMessages()
|
|
self.addMdgMessages()
|
|
G.animationCrashRecoveryScriptJobs.append(cmds.scriptJob(runOnce = False, killWithScene = False, event =('SelectionChanged', self.addNodeMessages )))
|
|
|
|
|
|
self.recommendSaving(True)
|
|
#self.recommendSaving(False)#TMP
|
|
|
|
else:
|
|
G.deferredManager.removeFromQueue("ACR")
|
|
self.setLed("off")
|
|
|
|
def setLed(self, state):
|
|
|
|
if not cmds.image("animationCrashRecoveryLed", query=True, exists=True): return
|
|
|
|
self.blinkingRed(False)
|
|
|
|
if state == "on":
|
|
if self.saveRecommended:
|
|
image = "ACR_red"
|
|
ann = "Animation Crash Recovery recommends you to save"
|
|
G.lastSaveWarning = time.time() if not G.lastSaveWarning else G.lastSaveWarning
|
|
|
|
if time.time() - G.lastSaveWarning >= self.redBlinkingSecs: self.blinkingRed(True)
|
|
|
|
else:
|
|
image = "ACR_green"
|
|
ann = "Animation Crash Recovery is ON"
|
|
G.lastSaveWarning = None
|
|
|
|
cmds.image("animationCrashRecoveryLed", edit=True, image= uiMod.getImagePath(image), ann=ann)
|
|
|
|
elif state == "off":
|
|
cmds.image("animationCrashRecoveryLed", edit=True, image= uiMod.getImagePath("ACR_off"), ann="Animation Crash Recovery is OFF")
|
|
|
|
elif state == "blinking":
|
|
self.blinkingLedState = not self.blinkingLedState
|
|
image = "ACR_white_half" if self.blinkingLedState else "ACR_white_bright"
|
|
cmds.image("animationCrashRecoveryLed", edit=True, image= uiMod.getImagePath(image), ann="Animation Crash Recovery is saving animation")
|
|
|
|
elif state == "blinking_red":
|
|
self.blinkingLedState = not self.blinkingLedState
|
|
image = "ACR_red_half" if self.blinkingLedState else "ACR_red_bright"
|
|
cmds.image("animationCrashRecoveryLed", edit=True, image= uiMod.getImagePath(image), ann="Animation Crash Recovery HIGHLY recommends you to save")
|
|
|
|
|
|
|
|
def blinkingRed(self, onOff):
|
|
|
|
if onOff: G.aToolsBar.timeoutInterval.setInterval(self.toggleRed, .3, id="ACR_red_blinking")
|
|
else: G.aToolsBar.timeoutInterval.stopInterval("ACR_red_blinking")
|
|
|
|
|
|
def toggleRed(self):
|
|
self.setLed("blinking_red")
|
|
|
|
|
|
|
|
def optionBoxWindow(self, *args):
|
|
|
|
sceneId = aToolsMod.getSceneId()
|
|
idFolder = "%s%s%s"%(self.baseFolderName, os.sep, sceneId)
|
|
bkpFolder = "%s%s%s"%(idFolder, os.sep, self.baseBackupFolderName)
|
|
infoData = aToolsMod.loadFileWithUser(bkpFolder, self.infoDataFileName, ext="info")
|
|
infoDataFile = "%s%s%s%s%s.info"%(G.USER_FOLDER, os.sep, bkpFolder, os.sep, self.infoDataFileName)
|
|
modDate = os.path.getmtime(infoDataFile) if os.path.isfile(infoDataFile) else None
|
|
|
|
|
|
if not infoData or not modDate:
|
|
cmds.warning("There is no crash file to restore.")
|
|
return
|
|
|
|
|
|
def loadWindow():
|
|
|
|
mayaFileName = infoData["mayaFileName"]
|
|
message = "%s\n%s\n\nWarning: Loading crash files after editing your Maya file can lead to unpredictable results."%(mayaFileName, time.ctime(modDate))
|
|
|
|
formLayout = cmds.setParent(query=True)
|
|
icon = cmds.image(image= uiMod.getImagePath("ACR_white_bright"))
|
|
titleText = cmds.text(label="Do you want to load?", font="boldLabelFont", align="left")
|
|
messageText = cmds.text(label=message, align="left")
|
|
buttonLoad = cmds.button(label='Load', command='cmds.layoutDialog(dismiss="load")')
|
|
buttonLoadSel = cmds.button(label='Load On Selection', command='cmds.layoutDialog(dismiss="load_selection")', w=110)
|
|
|
|
cmds.formLayout (formLayout, edit=True, width=300, height=170,
|
|
attachPosition = [
|
|
(icon, 'left', 10, 0),
|
|
(icon, 'top', 10, 0),
|
|
(titleText, 'top', 10, 0),
|
|
(messageText, 'left', 10, 0),
|
|
(messageText, 'top', 0, 30),
|
|
(buttonLoad, 'left', 10, 0),
|
|
(buttonLoad, 'bottom', 10, 100),
|
|
(buttonLoadSel, 'bottom', 10, 100)
|
|
],
|
|
attachForm = [
|
|
(buttonLoad, 'left', 10),
|
|
(buttonLoadSel, 'right', 10)
|
|
],
|
|
attachControl = [
|
|
(titleText, 'left', 10, icon),
|
|
(buttonLoad, 'right', 5, buttonLoadSel)
|
|
])
|
|
|
|
|
|
def window(dismiss):
|
|
|
|
if dismiss == "dismiss": return
|
|
|
|
onlySelectedNodes = True if dismiss == "load_selection" else False
|
|
|
|
self.loadData(onlySelectedNodes, self.baseBackupFolderName)
|
|
|
|
|
|
window(cmds.layoutDialog(title="aTools Animation Crash Recovery", ui=loadWindow))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def checkForAnimationSaved(self, clearDeferredQueue=False, *args):
|
|
if clearDeferredQueue: self.deferredQueue = []
|
|
|
|
sceneId = aToolsMod.getSceneId()
|
|
idFolder = "%s%s%s"%(self.baseFolderName, os.sep, sceneId)
|
|
latestFolder = "%s%s%s"%(idFolder, os.sep, self.baseLatestFolderName)
|
|
infoFile = "%s%s%s%s%s.info"%(G.USER_FOLDER, os.sep, latestFolder, os.sep, self.infoDataFileName)
|
|
mayaFile = cmds.file(query=True, sceneName=True)
|
|
|
|
if not os.path.isfile(infoFile) or not os.path.isfile(mayaFile): return
|
|
|
|
mayaFileModTime = os.path.getmtime(mayaFile)
|
|
infoFileModTime = os.path.getmtime(infoFile)
|
|
|
|
if mayaFileModTime < infoFileModTime:
|
|
|
|
infoData = aToolsMod.loadFileWithUser(latestFolder, self.infoDataFileName, ext="info")
|
|
|
|
if not infoData: return
|
|
|
|
height = 170
|
|
completed = infoData["completed"]
|
|
mayaFileModTimeStr = time.ctime(mayaFileModTime)
|
|
infoFileModTimeStr = time.ctime(infoFileModTime)
|
|
message = "This Maya file:\n%s\n\n"%(mayaFileModTimeStr)
|
|
message += "Latest Animation Crash Recovery file:\n%s"%(infoFileModTimeStr)
|
|
|
|
if not completed:
|
|
message += "\n\n*Some animation may not be loaded.\nAnimation Crash Recovery did not finish saving before Maya crashed."
|
|
height += 40
|
|
|
|
self.warningForLoading(message, height)
|
|
|
|
|
|
|
|
|
|
|
|
def warningForLoading(self, message, height):
|
|
|
|
def warningWindow():
|
|
|
|
formLayout = cmds.setParent(query=True)
|
|
icon = cmds.image(image= uiMod.getImagePath("ACR_white_bright"))
|
|
titleText = cmds.text(label="You have newer animation. Do you want to load?", font="boldLabelFont", align="left")
|
|
messageText = cmds.text(label=message, align="left")
|
|
buttonLoad = cmds.button(label='Load', command='cmds.layoutDialog(dismiss="load")')
|
|
buttonMaybe = cmds.button(label='Maybe Later', command='cmds.layoutDialog(dismiss="maybe")', w=100)
|
|
|
|
|
|
cmds.formLayout (formLayout, edit=True, width=300, height=height,
|
|
attachPosition = [
|
|
(icon, 'left', 10, 0),
|
|
(icon, 'top', 10, 0),
|
|
(titleText, 'top', 10, 0),
|
|
(messageText, 'left', 10, 0),
|
|
(messageText, 'top', 0, 30),
|
|
(buttonLoad, 'left', 10, 0),
|
|
(buttonLoad, 'bottom', 10, 100),
|
|
(buttonMaybe, 'bottom', 10, 100)
|
|
],
|
|
attachForm = [
|
|
(buttonLoad, 'left', 10),
|
|
(buttonMaybe, 'right', 10)
|
|
],
|
|
attachControl = [
|
|
(titleText, 'left', 10, icon),
|
|
(buttonLoad, 'right', 5, buttonMaybe)
|
|
])
|
|
|
|
|
|
|
|
def window(dismiss):
|
|
if dismiss == "load":
|
|
self.loadData()
|
|
else:
|
|
cmds.warning("If you want to load later, go to aTools menu/Animation Crash Recovery option box")
|
|
|
|
self.saveBackup()
|
|
|
|
window(cmds.layoutDialog(title="aTools Animation Crash Recovery", ui=warningWindow))
|
|
|
|
|
|
def saveBackup(self):
|
|
|
|
sceneId = aToolsMod.getSceneId()
|
|
idFolder = "%s%s%s"%(self.baseFolderName, os.sep, sceneId)
|
|
latestFolder = "%s%s%s"%(idFolder, os.sep, self.baseLatestFolderName)
|
|
bkpFolder = "%s%s%s"%(idFolder, os.sep, self.baseBackupFolderName)
|
|
|
|
print("henlo")
|
|
print(f"backup: {bkpFolder}")
|
|
aToolsMod.deleteFolderWithUser(bkpFolder)
|
|
aToolsMod.renameFolderWithUser(latestFolder, bkpFolder)
|
|
aToolsMod.deleteFolderWithUser(latestFolder)
|
|
|
|
|
|
def getSavedData(self, crashFolder=None, onlySelectedNodes=False, *args):
|
|
|
|
idFolder = aToolsMod.getSceneId()
|
|
crashFolder = self.baseLatestFolderName if not crashFolder else crashFolder
|
|
folder = "%s%s%s%s%s"%(self.baseFolderName, os.sep, idFolder, os.sep, crashFolder)
|
|
filePath = cmds.file(query=True, sceneName=True)
|
|
fileModTime = None
|
|
|
|
|
|
if os.path.isfile(filePath):
|
|
fileModTime = os.path.getmtime(filePath)
|
|
|
|
curveFiles = aToolsMod.readFilesWithUser(folder, ext=self.curveExt)
|
|
attrFiles = aToolsMod.readFilesWithUser(folder, ext=self.attrExt)
|
|
|
|
status = "aTools Animation Crash Recovery - Step 1/3 - Loading crash files..."
|
|
utilMod.startProgressBar(status)
|
|
totalSteps = len(curveFiles + attrFiles)
|
|
firstStep = 0
|
|
thisStep = 0
|
|
estimatedTime = None
|
|
startChrono = None
|
|
progressInfo = [startChrono, firstStep, thisStep, totalSteps, estimatedTime, status]
|
|
|
|
data = self.getDataFromFiles("anim", folder, curveFiles, fileModTime, self.curveExt, progressInfo, onlySelectedNodes)
|
|
|
|
if not data: return
|
|
|
|
animData = data[0]
|
|
progressInfo = data[1]
|
|
data = self.getDataFromFiles("attr", folder, attrFiles, fileModTime, self.attrExt, progressInfo, onlySelectedNodes)
|
|
attrData = data[0]
|
|
|
|
if not data: return
|
|
|
|
utilMod.setProgressBar(endProgress=True)
|
|
|
|
return {"fileModTime":fileModTime, "animData":animData, "attrData":attrData}
|
|
|
|
|
|
|
|
def getDataFromFiles(self, animAttr, folder, infoFiles, fileModTime, ext, progressInfo, onlySelectedNodes):
|
|
currSel = animMod.getObjsSel()
|
|
data = {"data":[], "modTime":None}
|
|
infoFileModTimeList = []
|
|
startChrono, firstStep, thisStep, totalSteps, estimatedTime, status = progressInfo
|
|
initialStep = thisStep
|
|
|
|
for n, loopFile in enumerate(infoFiles):
|
|
if cmds.progressBar(G.progBar, query=True, isCancelled=True ):
|
|
utilMod.setProgressBar(endProgress=True)
|
|
return
|
|
|
|
thisStep = n+initialStep
|
|
startChrono = utilMod.chronoStart(startChrono, firstStep, thisStep, totalSteps, estimatedTime, status)
|
|
|
|
infoFileStr = loopFile.replace(":", "_aTools_")[0:-(len(ext)+1)]
|
|
infoFilePath = aToolsMod.getSaveFilePath("%s%s%s"%(folder, os.sep, infoFileStr), ext=ext)
|
|
infoFileModTimeList.append(os.path.getmtime(infoFilePath))
|
|
|
|
if infoFileModTimeList[-1] > fileModTime: #load only what is newer
|
|
object = loopFile.replace("_aTools_", ":")[0:-(len(ext)+1)]
|
|
value = aToolsMod.loadFileWithUser(folder, infoFileStr, ext=ext)
|
|
|
|
if onlySelectedNodes:
|
|
if animAttr == "anim":
|
|
obj = value["objects"][0]
|
|
else:
|
|
obj = object.split(".")[0]
|
|
|
|
if obj not in currSel: continue
|
|
|
|
|
|
data["data"].append({"_modTime":infoFileModTimeList[-1],"object":object, "value":value})
|
|
|
|
estimatedTime = utilMod.chronoEnd(startChrono, firstStep, thisStep, totalSteps)
|
|
|
|
#file mod date
|
|
data["data"].sort() #sort newer files last
|
|
if len(infoFileModTimeList) > 0:
|
|
data["modTime"] = max(infoFileModTimeList)
|
|
|
|
progressInfo = [startChrono, firstStep, thisStep, totalSteps, estimatedTime, status]
|
|
#blend animation data=================
|
|
|
|
return [data, progressInfo]
|
|
|
|
def loadData(self, onlySelectedNodes=False, crashFolder=None, *args):
|
|
|
|
cmds.waitCursor(state=True)
|
|
cmds.refresh(suspend=True)
|
|
cmds.undoInfo(openChunk=True)
|
|
utilMod.startProgressBar("aTools Animation Crash Recovery - Loading data...")
|
|
|
|
self.pause = True
|
|
savedData = self.getSavedData(crashFolder, onlySelectedNodes)
|
|
|
|
if savedData:
|
|
|
|
animData = savedData["animData"]["data"]
|
|
attrData = savedData["attrData"]["data"]
|
|
|
|
self.applyAttrData(attrData)
|
|
self.applyAnimData(animData)
|
|
if not crashFolder: self.loadInfoData()
|
|
|
|
utilMod.setProgressBar(endProgress=True)
|
|
|
|
self.pause = False
|
|
|
|
cmds.undoInfo(closeChunk=True)
|
|
cmds.refresh(suspend=False)
|
|
cmds.waitCursor(state=False)
|
|
|
|
def blendAnimData(self, acrAnimData):
|
|
|
|
blendedAnimData = {"objects":[], "animData":[]}
|
|
|
|
for loopData in acrAnimData:
|
|
data = loopData["value"]
|
|
objects = data["objects"]
|
|
animData = data["animData"]
|
|
|
|
blendedAnimData["objects"].extend(objects)
|
|
blendedAnimData["animData"].extend(animData)
|
|
|
|
return blendedAnimData
|
|
|
|
def applyAnimData(self, animData):
|
|
|
|
if len(animData) == 0 : return
|
|
animData = self.blendAnimData(animData)
|
|
animMod.applyAnimData(animData, pasteInPlace=False, showProgress=True, status="aTools Animation Crash Recovery - Step 3/3 - Applying animation data...")
|
|
|
|
|
|
|
|
def applyAttrData(self, attrData):
|
|
|
|
firstStep = 0
|
|
totalSteps = len(attrData)
|
|
estimatedTime = None
|
|
status = "aTools Animation Crash Recovery - Step 2/3 - Applying attributes data..."
|
|
startChrono = None
|
|
|
|
for thisStep, loopData in enumerate(attrData):
|
|
if cmds.progressBar(G.progBar, query=True, isCancelled=True ): return
|
|
startChrono = utilMod.chronoStart(startChrono, firstStep, thisStep, totalSteps, estimatedTime, status)
|
|
|
|
objAttr = loopData["object"]
|
|
value = loopData["value"]["value"]
|
|
|
|
if not cmds.objExists(objAttr): continue
|
|
if not cmds.getAttr(objAttr, settable=True): continue
|
|
if cmds.getAttr(objAttr, lock=True): continue
|
|
if cmds.getAttr(objAttr, type=True) == "string": continue
|
|
|
|
cmds.cutKey(objAttr)
|
|
|
|
|
|
if type(value) is list: #translate, rotate, scale
|
|
value = value[0]
|
|
cmds.setAttr(objAttr, value[0],value[1],value[2], clamp=True)
|
|
else: #translatex, translatey, etc
|
|
cmds.setAttr(objAttr, value, clamp=True)
|
|
|
|
|
|
estimatedTime = utilMod.chronoEnd(startChrono, firstStep, thisStep, totalSteps)
|
|
|
|
|
|
def getAllAnimCurves(self):
|
|
return cmds.ls(type=["animCurveTA","animCurveTL","animCurveTT","animCurveTU"])
|
|
|
|
def getAllNonKeyedAttrs(self):
|
|
return self.getNonKeyedAttrs(cmds.ls(transforms=True, visible=True))
|
|
#return self.getNonKeyedAttrs([loopObj for loopObj in cmds.ls() if "transform" in cmds.nodeType(loopObj, inherited=True)])
|
|
|
|
|
|
def saveSelectedCurve(self, *args):
|
|
|
|
if self.pause: return
|
|
|
|
curveMsg = args[0]
|
|
curves = [OpenMaya.MFnDependencyNode(curveMsg[n]).name() for n in range(curveMsg.length())]
|
|
|
|
|
|
function = lambda *args:self.sendDataToSaveDeferred(curves, [])
|
|
G.deferredManager.sendToQueue(function, 50, "ACR")
|
|
|
|
|
|
|
|
|
|
def saveSelectedAttr(self, msg, mplug, otherMplug, clientData):
|
|
|
|
if self.pause: return
|
|
|
|
if OpenMaya.MNodeMessage.kAttributeSet == (OpenMaya.MNodeMessage.kAttributeSet & msg):
|
|
#nodeName, attrName = mplug.name().split('.')
|
|
nonKeyedAttr = mplug.name()
|
|
|
|
#if cmds.keyframe(nonKeyedAttr, query=True): return
|
|
|
|
function = lambda *args:self.sendDataToSaveDeferred([], [nonKeyedAttr])
|
|
G.deferredManager.sendToQueue(function, 50, "ACR")
|
|
|
|
|
|
|
|
|
|
|
|
def getNonKeyedAttrs(self, animObjects):
|
|
objAttrs = animMod.getAllChannels(animObjects, changed=True, withAnimation=False)
|
|
nonKeyedObjAttrs = []
|
|
|
|
|
|
for n, loopObj in enumerate(animObjects):
|
|
loopObjAttrs = objAttrs[n]
|
|
if not loopObjAttrs: continue
|
|
for loopAttr in loopObjAttrs:
|
|
if loopAttr in self.ignoreAttrs: continue
|
|
objAttr = "%s.%s"%(loopObj, loopAttr)
|
|
if not cmds.objExists(objAttr): continue
|
|
frameCount = cmds.keyframe(objAttr, query=True, keyframeCount=True)
|
|
if frameCount <= 0:
|
|
nonKeyedObjAttrs.append(objAttr)
|
|
|
|
return nonKeyedObjAttrs
|
|
|
|
def saveAllAnimationData(self, update=False, *args):#nao precisa???
|
|
#print "saveAllAnimationData"
|
|
if update:
|
|
self.curvesInFile = self.getAllAnimCurves()
|
|
self.nonKeyedAttrsInFile = self.getAllNonKeyedAttrs()
|
|
|
|
self.sendDataToSaveDeferred(self.curvesInFile, self.nonKeyedAttrsInFile)
|
|
|
|
def sendDataToSaveDeferred(self, curves, nonKeyedAttrs):
|
|
|
|
if not len(curves) > 0 and not len(nonKeyedAttrs) > 0:
|
|
return
|
|
|
|
for loopCurve in curves:
|
|
|
|
curveStr = loopCurve.replace(":", "_aTools_")
|
|
if not cmds.objExists(loopCurve):
|
|
if curveStr in self.deferredQueue: self.deferredQueue.remove(curveStr)
|
|
continue
|
|
|
|
if curveStr in self.deferredQueue: continue
|
|
|
|
self.deferredQueue.append(curveStr)
|
|
function = lambda function=self.saveCurve, mayaFileName=self.mayaFileName, attrStr=curveStr, *args: self.sendToDeferredManager(function, mayaFileName, attrStr)
|
|
G.deferredManager.sendToQueue(function, 50, "ACR")
|
|
|
|
|
|
for loopNonKeyedAttr in nonKeyedAttrs:
|
|
|
|
nonKeyedAttrsStr = loopNonKeyedAttr.replace(":", "_aTools_")
|
|
|
|
if not cmds.objExists(loopNonKeyedAttr):
|
|
if nonKeyedAttrsStr in self.deferredQueue: self.deferredQueue.remove(nonKeyedAttrsStr)
|
|
continue
|
|
|
|
if cmds.keyframe(loopNonKeyedAttr, query=True): continue
|
|
|
|
if nonKeyedAttrsStr in self.deferredQueue: continue
|
|
|
|
self.deferredQueue.append(nonKeyedAttrsStr)
|
|
function = lambda function=self.saveNonKeyedAttrs, mayaFileName=self.mayaFileName, attrStr=nonKeyedAttrsStr, *args: self.sendToDeferredManager(function, mayaFileName, attrStr)
|
|
G.deferredManager.sendToQueue(function, 50, "ACR")
|
|
|
|
|
|
G.deferredManager.sendToQueue(lambda *args:self.stopBlinking(self.mayaFileName), 50, "ACR")
|
|
|
|
def stopBlinking(self, mayaFileName):
|
|
if G.deferredManager.inQueue("ACR") <= 1:
|
|
self.setLed("on")
|
|
self.saveInfoData(mayaFileName, completed=True)
|
|
self.checkDeletedNodesCreated()
|
|
|
|
|
|
def checkDeletedNodesCreated(self):
|
|
|
|
if len(G.ACR_messages["mdg"]) == 0: return
|
|
|
|
toRemove = []
|
|
|
|
for loopNode in self.nodesCreated:
|
|
if not cmds.objExists(loopNode): toRemove.append(loopNode)
|
|
|
|
for loopNode in toRemove: self.nodesCreated.remove(loopNode)
|
|
if len(self.nodesCreated) == 0: self.recommendSaving(False)
|
|
|
|
|
|
def sendToDeferredManager(self, function, mayaFileName, attrStr):
|
|
function(mayaFileName, attrStr)
|
|
|
|
def saveCurve(self, mayaFileName, curveStr):
|
|
self.setLed("blinking")
|
|
|
|
sceneId = aToolsMod.getSceneId()
|
|
curve = curveStr.replace("_aTools_", ":")
|
|
animData = animMod.getAnimData([curve])
|
|
|
|
if curveStr in self.deferredQueue: self.deferredQueue.remove(curveStr)
|
|
|
|
if animData is None: return
|
|
|
|
if sceneId not in self.animCurvesInfo: self.animCurvesInfo[sceneId] = {}
|
|
|
|
if curveStr in self.animCurvesInfo[sceneId]:
|
|
if self.animCurvesInfo[sceneId][curveStr] == animData: return
|
|
|
|
self.animCurvesInfo[sceneId][curveStr] = animData
|
|
|
|
#save curve to disk
|
|
aToolsMod.saveFileWithUser("%s%s%s%s%s"%(self.baseFolderName, os.sep, sceneId, os.sep, self.baseLatestFolderName), curveStr, animData, ext=self.curveExt)
|
|
self.saveInfoData(mayaFileName)
|
|
|
|
|
|
def saveInfoData(self, mayaFileName, completed=False):
|
|
|
|
sceneId = aToolsMod.getSceneId()
|
|
currFrame = cmds.currentTime(query=True)
|
|
currSel = cmds.ls(selection=True)
|
|
infoData = {"mayaFileName":mayaFileName, "currFrame":currFrame, "currSel":currSel, "completed":completed}
|
|
|
|
aToolsMod.saveFileWithUser("%s%s%s%s%s"%(self.baseFolderName, os.sep, sceneId, os.sep, self.baseLatestFolderName), self.infoDataFileName, infoData, ext="info")
|
|
|
|
def loadInfoData(self):
|
|
sceneId = aToolsMod.getSceneId()
|
|
infoData = aToolsMod.loadFileWithUser("%s%s%s%s%s"%(self.baseFolderName, os.sep, sceneId, os.sep, self.baseLatestFolderName), self.infoDataFileName, ext="info")
|
|
|
|
if not infoData: return
|
|
|
|
currFrame = infoData["currFrame"]
|
|
currSel = infoData["currSel"]
|
|
|
|
if currFrame: cmds.currentTime(currFrame)
|
|
if len(currSel) > 0: cmds.select(currSel, replace=True)
|
|
|
|
def saveNonKeyedAttrs(self, mayaFileName, nonKeyedAttrsStr):
|
|
self.setLed("blinking")
|
|
|
|
sceneId = aToolsMod.getSceneId()
|
|
nonKeyedAttr = nonKeyedAttrsStr.replace("_aTools_", ":")
|
|
attrData = self.getNonKeyedAttrData(nonKeyedAttr)
|
|
|
|
if nonKeyedAttrsStr in self.deferredQueue: self.deferredQueue.remove(nonKeyedAttrsStr)
|
|
|
|
if attrData is None: return
|
|
|
|
if sceneId not in self.nonKeyedAttrInfo: self.nonKeyedAttrInfo[sceneId] = {}
|
|
|
|
if nonKeyedAttrsStr in self.nonKeyedAttrInfo[sceneId]:
|
|
if self.nonKeyedAttrInfo[sceneId][nonKeyedAttrsStr] == attrData: return
|
|
|
|
self.nonKeyedAttrInfo[sceneId][nonKeyedAttrsStr] = attrData
|
|
|
|
#save curve to disk
|
|
aToolsMod.saveFileWithUser("%s%s%s%s%s"%(self.baseFolderName, os.sep, sceneId, os.sep, self.baseLatestFolderName), nonKeyedAttrsStr, attrData, ext=self.attrExt)
|
|
self.saveInfoData(mayaFileName)
|
|
|
|
|
|
def checkAndClearOldFiles(self):
|
|
|
|
allIdFolders = aToolsMod.readFoldersWithUser(self.baseFolderName)
|
|
timeNow = time.time()
|
|
|
|
for loopIdFolder in allIdFolders:
|
|
idFolder = "%s%s%s"%(self.baseFolderName, os.sep, loopIdFolder)
|
|
modDate = None
|
|
|
|
for loopInfoFile in [self.baseLatestFolderName, self.baseBackupFolderName]:
|
|
infoDataFile = "%s%s%s%s%s%s%s.info"%(G.USER_FOLDER, os.sep, idFolder, os.sep, loopInfoFile, os.sep, self.infoDataFileName)
|
|
if os.path.isfile(infoDataFile):
|
|
modDate = os.path.getmtime(infoDataFile)
|
|
break
|
|
|
|
if not modDate: return
|
|
|
|
if timeNow - modDate >= self.daysToKeepOldFiles:
|
|
aToolsMod.deleteFolderWithUser(idFolder)
|
|
|
|
|
|
|
|
|
|
|
|
def clearLatestFolder(self):
|
|
self.deferredQueue = []
|
|
G.deferredManager.removeFromQueue("ACR")
|
|
|
|
sceneId = aToolsMod.getSceneId()
|
|
idFolder = "%s%s%s"%(self.baseFolderName, os.sep, sceneId)
|
|
latestFolder = "%s%s%s"%(idFolder, os.sep, self.baseLatestFolderName)
|
|
|
|
aToolsMod.deleteFolderWithUser(latestFolder)
|
|
|
|
|
|
|
|
def getNonKeyedAttrData(self, nonKeyedAttr):
|
|
value = None
|
|
|
|
if cmds.objExists(nonKeyedAttr): value = cmds.getAttr(nonKeyedAttr)
|
|
return {"value":value}
|
|
|
|
|
|
def recommendSaving(self, trueFalse):
|
|
self.saveRecommended = trueFalse
|
|
|
|
if not trueFalse: self.addMdgMessages()
|
|
self.setLed("on")
|
|
|
|
def isCrashSaving(self):
|
|
t = datetime.date.today()
|
|
todaySt = ".%s%s%s."%(str(t.year).zfill(4),str(t.month).zfill(2),str(t.day).zfill(2))
|
|
|
|
return (todaySt in utilMod.getMayaFileName())
|
|
|
|
def beforeSave(self, *args):
|
|
pass
|
|
|
|
def afterSave(self, *args):
|
|
|
|
if self.isCrashSaving():
|
|
self.saveCrashLog(cmds.file(query=True, sceneName=True), self.mayaFilePath, self.mayaFileName)
|
|
return
|
|
|
|
self.mayaFileName = utilMod.getMayaFileName()
|
|
self.mayaFilePath = utilMod.getMayaFileName("path")
|
|
|
|
self.recommendSaving(False)
|
|
self.addMdgMessages()
|
|
self.clearLatestFolder()
|
|
|
|
def afterNew(self, *args):
|
|
#print "afterOpen"
|
|
self.mayaFileName = utilMod.getMayaFileName()
|
|
self.mayaFilePath = utilMod.getMayaFileName("path")
|
|
|
|
self.recommendSaving(False)
|
|
|
|
def beforeOpen(self, *args):
|
|
self.pause = True
|
|
|
|
def afterOpen(self, *args):
|
|
self.pause = False
|
|
#print "afterOpen"
|
|
self.mayaFileName = utilMod.getMayaFileName()
|
|
self.mayaFilePath = utilMod.getMayaFileName("path")
|
|
|
|
self.recommendSaving(False)
|
|
|
|
function = lambda *args: self.checkForAnimationSaved(clearDeferredQueue=True)
|
|
G.deferredManager.sendToQueue(function, 80, "ACR")
|
|
#self.checkForAnimationSaved(clearDeferredQueue=True)
|
|
|
|
def checkForCrashLog(self):
|
|
|
|
crashLog = self.loadCrashLog()
|
|
|
|
if crashLog and "crashFilePath" in crashLog: self.warnCrashLog(crashLog)
|
|
|
|
def saveCrashLog(self, crashFilePath, beforeCrashPath, beforeCrashName):
|
|
|
|
crashData = {"crashFilePath":crashFilePath, "beforeCrashPath":beforeCrashPath, "beforeCrashName":beforeCrashName}
|
|
aToolsMod.saveFileWithUser("%s"%(self.baseFolderName), "crashLog", crashData, ext="info")
|
|
|
|
def loadCrashLog(self):
|
|
|
|
return aToolsMod.loadFileWithUser("%s"%(self.baseFolderName), "crashLog", ext="info")
|
|
|
|
def warnCrashLog(self, crashLog):
|
|
|
|
crashFilePath = crashLog["crashFilePath"]
|
|
beforeCrashPath = crashLog["beforeCrashPath"]
|
|
beforeCrashName = crashLog["beforeCrashName"]
|
|
|
|
if not os.path.isfile(crashFilePath) or not os.path.isfile(beforeCrashPath): return
|
|
|
|
crashFileModTime = time.ctime(os.path.getmtime(crashFilePath))
|
|
beforeCrashModTime = time.ctime(os.path.getmtime(beforeCrashPath))
|
|
message = "Looks like last Maya session crashed and saved a crash file.\n\nOriginal file:\n%s\n%s\n\nCrash saved file:\n%s\n%s\n\nDo you want to open the crash saved file?"%(beforeCrashName, beforeCrashModTime, crashFilePath, crashFileModTime)
|
|
confirm = cmds.confirmDialog( title='aTools Animation Crash Recovery', message=message, button=['Yes','No'], defaultButton='Yes', cancelButton='No', dismissString='No' )
|
|
|
|
if confirm == 'Yes':
|
|
if cmds.file(query=True, sceneName=True):
|
|
message = "Save current file first? If you click NO, changes will be lost."
|
|
confirm = cmds.confirmDialog( title='aTools Animation Crash Recovery', message=message, button=['Yes','No'], defaultButton='Yes', cancelButton='No', dismissString='No' )
|
|
|
|
if confirm == 'Yes': cmds.file(save=True)
|
|
|
|
cmds.file(new=True, force=True)
|
|
cmds.file(crashFilePath, open=True, prompt=True)
|
|
|
|
aToolsMod.deleteFileWithUser("%s"%(self.baseFolderName), "crashLog", ext="info")
|
|
|
|
|
|
|
|
|
|
"""
|
|
def sceneUpdate(self, *args):
|
|
self.clearOldFiles()
|
|
self.mayaFileName = utilMod.getMayaFileName()
|
|
#print "sceneUpdate", args, self.mayaFileName
|
|
|
|
|
|
def beforeSaveCheck(self, retCode, *args):
|
|
self.clearOldFiles()
|
|
OpenMaya.MScriptUtil.setBool(retCode, True)
|
|
|
|
print "beforeSaveCheck", args, self.mayaFileName
|
|
"""
|
|
|
|
|
|
|
|
def afterNodeCreated(self, *args):
|
|
|
|
if not self.checkNodeCreated: return
|
|
|
|
nodeCreated = OpenMaya.MFnDependencyNode(args[0]).name()
|
|
nodeType = cmds.nodeType(nodeCreated)
|
|
|
|
if nodeType in ["animCurveTA","animCurveTL","animCurveTT","animCurveTU"]:
|
|
return
|
|
|
|
print(("nodeCreated", nodeCreated, nodeType))
|
|
|
|
if nodeCreated not in self.nodesCreated: self.nodesCreated.append(nodeCreated)
|
|
|
|
self.recommendSaving(True)
|
|
#self.removeMdgMessages()
|
|
|
|
def afterNodeParent(self, *args):
|
|
|
|
if not self.checkNodeCreated: return
|
|
|
|
dag = args[0]
|
|
firstObj = dag.partialPathName()
|
|
|
|
#print "firstObj",firstObj
|
|
if not firstObj: return
|
|
|
|
dag = args[1]
|
|
secondObj = dag.partialPathName()
|
|
|
|
#print "secondObj",secondObj
|
|
if not firstObj: return
|
|
|
|
#if firstObj not in self.nodesCreated: self.nodesCreated.append(firstObj)
|
|
|
|
print(("parented", firstObj, secondObj))
|
|
|
|
self.recommendSaving(True)
|
|
self.removeMdgMessages()
|
|
|
|
def addAnimSceneMessages(self):
|
|
|
|
self.removeMessages()
|
|
|
|
#ANIM MESSAGES
|
|
G.ACR_messages["anim"].append(OpenMayaAnim.MAnimMessage.addAnimCurveEditedCallback(self.saveSelectedCurve))
|
|
|
|
#SCENE MESSAGES
|
|
G.ACR_messages["scene"].append(OpenMaya.MSceneMessage.addCallback( OpenMaya.MSceneMessage.kBeforeSave, self.beforeSave))
|
|
G.ACR_messages["scene"].append(OpenMaya.MSceneMessage.addCallback( OpenMaya.MSceneMessage.kAfterSave, self.afterSave))
|
|
G.ACR_messages["scene"].append(OpenMaya.MSceneMessage.addCallback( OpenMaya.MSceneMessage.kBeforeOpen, self.beforeOpen))
|
|
G.ACR_messages["scene"].append(OpenMaya.MSceneMessage.addCallback( OpenMaya.MSceneMessage.kAfterOpen, self.afterOpen))
|
|
G.ACR_messages["scene"].append(OpenMaya.MSceneMessage.addCallback( OpenMaya.MSceneMessage.kAfterNew, self.afterNew))
|
|
#G.ACR_messages["scene"].append(OpenMaya.MSceneMessage.addCallback( OpenMaya.MSceneMessage.kSceneUpdate, self.sceneUpdate))
|
|
#G.ACR_messages["scene"].append(OpenMaya.MSceneMessage.addCheckCallback( OpenMaya.MSceneMessage.kBeforeSaveCheck, self.beforeSaveCheck))
|
|
|
|
|
|
|
|
def addMdgMessages(self):
|
|
self.removeMdgMessages()
|
|
#MDG MESSAGES
|
|
G.ACR_messages["mdg"].append(OpenMaya.MDGMessage.addNodeAddedCallback(self.afterNodeCreated))
|
|
G.ACR_messages["mdg"].append(OpenMaya.MDagMessage.addParentAddedCallback(self.afterNodeParent, "_noData_"))
|
|
|
|
|
|
def addNodeMessages(self):
|
|
self.removeNodeMessages()
|
|
#NODE MESSAGES
|
|
currSel = cmds.ls(selection=True)
|
|
MSelectionList = OpenMaya.MSelectionList()
|
|
OpenMaya.MGlobal.getActiveSelectionList(MSelectionList)
|
|
node = OpenMaya.MObject()
|
|
|
|
for n, loopSel in enumerate(currSel):
|
|
|
|
MSelectionList.getDependNode(n, node)
|
|
G.ACR_messages["node"].append(OpenMaya.MNodeMessage.addAttributeChangedCallback(node, self.saveSelectedAttr, None))
|
|
|
|
|
|
|
|
|
|
def removeMessages(self):
|
|
|
|
try:
|
|
for loopId in G.ACR_messages["anim"]:
|
|
OpenMayaAnim.MAnimMessage.removeCallback(loopId)
|
|
except: pass
|
|
|
|
self.removeNodeMessages()
|
|
|
|
try:
|
|
for loopId in G.ACR_messages["scene"]:
|
|
OpenMaya.MSceneMessage.removeCallback(loopId)
|
|
except: pass
|
|
|
|
self.removeMdgMessages()
|
|
|
|
G.ACR_messages["anim"] = []
|
|
G.ACR_messages["scene"] = []
|
|
|
|
def removeMdgMessages(self):
|
|
|
|
try:
|
|
for loopId in G.ACR_messages["mdg"]:
|
|
OpenMaya.MDGMessage.removeCallback(loopId)
|
|
except: pass
|
|
|
|
G.ACR_messages["mdg"] = []
|
|
|
|
def removeNodeMessages(self):
|
|
try:
|
|
for loopId in G.ACR_messages["node"]:
|
|
OpenMaya.MNodeMessage.removeCallback(loopId)
|
|
except: pass
|
|
|
|
G.ACR_messages["node"] = []
|
|
|
|
|
|
"""
|
|
import maya.OpenMaya as om
|
|
import maya.OpenMayaAnim as oma
|
|
|
|
def undoTest(*args):
|
|
print 'Checking Undo callback'
|
|
|
|
|
|
def undoRedoCallback(arg):
|
|
global callbackIDs
|
|
|
|
Null = om.MObject()
|
|
objs = cmds.ls(sl=1)
|
|
|
|
if arg == 'add':
|
|
|
|
undoID = oma.MAnimMessage.addAnimCurveEditedCallback(undoTest)
|
|
#undoID = oma.MAnimMessage.addAnimKeyframeEditedCallback(undoTest)
|
|
#undoID = oma.MAnimMessage.addNodeAnimKeyframeEditedCallback(undoTest)
|
|
#undoID = oma.MAnimMessage.addAnimKeyframeEditCheckCallback(undoTest)
|
|
#undoID = oma.MAnimMessage.addAnimKeyframeEditedCallback(undoTest)
|
|
|
|
|
|
callbackIDs = [undoID]
|
|
|
|
elif arg == 'remove':
|
|
try:
|
|
for i in callbackIDs:
|
|
oma.MAnimMessage.removeCallback(i)
|
|
except:
|
|
print 'There is no ID to delete'
|
|
|
|
|
|
undoRedoCallback("add")
|
|
undoRedoCallback("remove")
|
|
|
|
MNodeMessage.addAttributeChangedCallback
|
|
|
|
"""
|
|
|