version = "2.02" from pyfbsdk import * import pythonidelib import RS.Tools.UI from PySide import QtGui, QtCore import sys from pyfbsdk_additions import * from RS.Utils.ContextManagers import SceneExpressionsDisabled import xml.etree.ElementTree as ET import math import csv import tempfile import os.path import time #mainUIFile = "X:/wildwest/script/motionbuildersandbox/Mike/2014/MotionWarp/UI/MotionWarp.ui" #mainPYFile = "X:/wildwest/script/motionbuildersandbox/Mike/2014/MotionWarp/UI/MotionWarp.py" #RS.Tools.UI.CompileUi(mainUIFile, mainPYFile) sys.path.append("X:/wildwest/script/motionbuildersandbox/Mike/2014/MotionWarp/") import UI reload(UI) class MotionWarp(RS.Tools.UI.QtMainWindowBase, UI.MotionWarp.Ui_MotionWarp): def __init__(self): RS.Tools.UI.QtMainWindowBase.__init__(self, None, title='Motion Warp ' + version) # run ui from designer file self.setupUi(self) # TEST FOR IMAGE OVERLAY SHOWING ANGLE CHOSEN # self.zonalStartRotationImage = QtGui.QLabel(self.zonalStartCanvas) # self.zonalStartRotationImage.setAlignment(QtCore.Qt.AlignCenter) # self.zonalStartRotationImage.setObjectName("zonalStartRotationImage") # self.zonalStartRotationImage.setGeometry(0, 0, 168, 168) # self.rotationImage = QtGui.QPixmap("X:/wildwest/script/motionbuildersandbox/Arran/Proto/MotionWarp/UI/Angle.png") # self.zonalStartRotationImage.setPixmap(self.rotationImage) # self.verticalLayout_13.addWidget(self.zonalStartRotationImage) # diag = (self.zonalStartRotationImage.width()**2 + self.zonalStartRotationImage.height()**2)**0.5 # self.zonalStartRotationImage.setMinimumSize(diag, diag) # self.zonalStartRotationImage.setAlignment(QtCore.Qt.AlignCenter) self.windowSizeY = 350 self.windowSizeX = 443 # def rotateImage(): # t.r = QtGui.QTransform() # t.updatedNumBox = t.zonalStart_offsetAngleNumBox.value() # t.r.rotate(t.updatedNumBox) # t.rotated_pixmap = t.rotationImage.transformed(t.r) # t.zonalStartRotationImage.setPixmap(t.rotated_pixmap) def setupFunctionality(t): models = FBModelList() FBGetSelectedModels(models) t.selected_obj = [] t.selected_obj_name = [] parseMotionWarpXML(t) if len(models) == 0: FBMessageBox("Message", "Nothing selected", "OK", None, None) elif t.XMLRead == False: print str(t.XMLRead) FBMessageBox("Message", ("Could not find: " + t.motionWarpPath), "OK", None, None) else: # set up arrays t.null = [] t.midNull = [] t.pathNull = [] t.path = [] t.pathConstraint = [] t.midNullConstraint = [] t.constraint = [] for obj in models: t.selected_obj.append(obj) t.selected_obj_name.append(obj.Name) t.numOfObjects = len(models) if t.numOfObjects == 1: t.turn_applyMotionWarpBtn.setText(QtGui.QApplication.translate("MotionWarp", "Apply MotionWarp to '" + t.selected_obj_name[0] + "'", None, QtGui.QApplication.UnicodeUTF8)) t.spline_applyMotionWarpBtn.setText(QtGui.QApplication.translate("MotionWarp", "Apply MotionWarp to '" + t.selected_obj_name[0] + "'", None, QtGui.QApplication.UnicodeUTF8)) t.zonalStart_applyMotionWarpBtn.setText(QtGui.QApplication.translate("MotionWarp", "Apply MotionWarp to '" + t.selected_obj_name[0] + "'", None, QtGui.QApplication.UnicodeUTF8)) t.zonalStop_applyMotionWarpBtn.setText(QtGui.QApplication.translate("MotionWarp", "Apply MotionWarp to '" + t.selected_obj_name[0] + "'", None, QtGui.QApplication.UnicodeUTF8)) t.zonalAdvanced_applyMotionWarpBtn.setText(QtGui.QApplication.translate("MotionWarp", "Apply MotionWarp to '" + t.selected_obj_name[0] + "'", None, QtGui.QApplication.UnicodeUTF8)) else: t.turn_applyMotionWarpBtn.setText(QtGui.QApplication.translate("MotionWarp", "Apply MotionWarp to " + str( t.numOfObjects) + " objects", None, QtGui.QApplication.UnicodeUTF8)) t.spline_applyMotionWarpBtn.setText(QtGui.QApplication.translate("MotionWarp", "Apply MotionWarp to " + str( t.numOfObjects) + " objects", None, QtGui.QApplication.UnicodeUTF8)) t.zonalStart_applyMotionWarpBtn.setText(QtGui.QApplication.translate("MotionWarp", "Apply MotionWarp to " + str( t.numOfObjects) + " objects", None, QtGui.QApplication.UnicodeUTF8)) t.zonalStop_applyMotionWarpBtn.setText(QtGui.QApplication.translate("MotionWarp", "Apply MotionWarp to " + str( t.numOfObjects) + " objects", None, QtGui.QApplication.UnicodeUTF8)) t.zonalAdvanced_applyMotionWarpBtn.setText(QtGui.QApplication.translate("MotionWarp", "Apply MotionWarp to " + str( t.numOfObjects) + " objects", None, QtGui.QApplication.UnicodeUTF8)) t.pinned = False t.turn_applyMotionWarpBtn.clicked.connect(goButtonCallBack) t.spline_applyMotionWarpBtn.clicked.connect(goButtonCallBack) t.zonalStart_applyMotionWarpBtn.clicked.connect(goButtonCallBack) t.zonalStop_applyMotionWarpBtn.clicked.connect(goButtonCallBack) t.zonalAdvanced_applyMotionWarpBtn.clicked.connect(goButtonCallBack) t.zonalStart_subtract45Btn.clicked.connect(sub45_start) t.zonalStart_add45Btn.clicked.connect(add45_start) t.zonalStop_subtract45Btn.clicked.connect(sub45_stop) t.zonalStop_add45Btn.clicked.connect(add45_stop) t.pinned = False if t.numOfObjects == 1: t.turn_deleteMotionWarpBtn.setText(QtGui.QApplication.translate("MotionWarp", "Delete MotionWarp from '" + t.selected_obj_name[0] + "'", None, QtGui.QApplication.UnicodeUTF8)) t.spline_deleteMotionWarpBtn.setText(QtGui.QApplication.translate("MotionWarp", "Delete MotionWarp from '" + t.selected_obj_name[0] + "'", None, QtGui.QApplication.UnicodeUTF8)) t.zonalStart_deleteMotionWarpBtn.setText(QtGui.QApplication.translate("MotionWarp", "Delete MotionWarp from '" + t.selected_obj_name[0] + "'", None, QtGui.QApplication.UnicodeUTF8)) t.zonalStop_deleteMotionWarpBtn.setText(QtGui.QApplication.translate("MotionWarp", "Delete MotionWarp from '" + t.selected_obj_name[0] + "'", None, QtGui.QApplication.UnicodeUTF8)) t.zonalAdvanced_deleteMotionWarpBtn.setText(QtGui.QApplication.translate("MotionWarp", "Delete MotionWarp from '" + t.selected_obj_name[0] + "'", None, QtGui.QApplication.UnicodeUTF8)) else: t.turn_deleteMotionWarpBtn.setText(QtGui.QApplication.translate("MotionWarp", "Delete MotionWarp from " + str( t.numOfObjects) + " objects", None, QtGui.QApplication.UnicodeUTF8)) t.spline_deleteMotionWarpBtn.setText(QtGui.QApplication.translate("MotionWarp", "Delete MotionWarp from " + str( t.numOfObjects) + " objects", None, QtGui.QApplication.UnicodeUTF8)) t.zonalStart_deleteMotionWarpBtn.setText(QtGui.QApplication.translate("MotionWarp", "Delete MotionWarp from " + str( t.numOfObjects) + " objects", None, QtGui.QApplication.UnicodeUTF8)) t.zonalStop_deleteMotionWarpBtn.setText(QtGui.QApplication.translate("MotionWarp", "Delete MotionWarp from " + str( t.numOfObjects) + " objects", None, QtGui.QApplication.UnicodeUTF8)) t.zonalAdvanced_deleteMotionWarpBtn.setText(QtGui.QApplication.translate("MotionWarp", "Delete MotionWarp from " + str( t.numOfObjects) + " objects", None, QtGui.QApplication.UnicodeUTF8)) t.turn_deleteMotionWarpBtn.clicked.connect(deleteButtonCallBack) t.spline_deleteMotionWarpBtn.clicked.connect(deleteButtonCallBack) t.zonalStart_deleteMotionWarpBtn.clicked.connect(deleteButtonCallBack) t.zonalStop_deleteMotionWarpBtn.clicked.connect(deleteButtonCallBack) t.zonalAdvanced_deleteMotionWarpBtn.clicked.connect(deleteButtonCallBack) setupTurnParameterList(t) t.zonalStart_keyHelperBtn.clicked.connect(zonalKeyHelperCallBack) t.zonalStop_keyHelperBtn.clicked.connect(zonalKeyHelperCallBack) t.zonalAdvanced_keyHelperBtn.clicked.connect(zonalKeyHelperCallBack) t.zonalStart_keyHelperBtn.setEnabled(False) t.zonalStop_keyHelperBtn.setEnabled(False) t.zonalAdvanced_keyHelperBtn.setEnabled(False) setupPlotModeList(t) t.tabBox.currentChanged.connect(tabBoxChanged) t.advancedSettings = False t.advancedSettingsWidget.hide() t.advancedSettingsBtn.clicked.connect(toggleAdvancedSettings) t.resize(t.windowSizeX, t.windowSizeY) t.setMinimumSize(QtCore.QSize(t.windowSizeX, t.windowSizeY)) t.setMaximumSize(QtCore.QSize(t.windowSizeX, t.windowSizeY)) t.statusbar.hide() t.progressBar.hide() t.progress = 0 t.progressBar.setValue(t.progress) #t.zonalStart_offsetAngleNumBox.valueChanged.connect(rotateImage) t.show() def toggleAdvancedSettings(): if t.advancedSettings == False: t.advancedSettings = True t.advancedSettingsWidget.show() t.windowSizeY += 56 t.resize(t.windowSizeX, t.windowSizeY) t.setMinimumSize(QtCore.QSize(t.windowSizeX, t.windowSizeY)) t.setMaximumSize(QtCore.QSize(t.windowSizeX, t.windowSizeY)) elif t.advancedSettings == True: t.advancedSettings = False t.advancedSettingsWidget.hide() t.windowSizeY -= 56 t.resize(t.windowSizeX, t.windowSizeY) t.setMinimumSize(QtCore.QSize(t.windowSizeX, t.windowSizeY)) t.setMaximumSize(QtCore.QSize(t.windowSizeX, t.windowSizeY)) def sub45_start(): currentValue = t.zonalStart_offsetAngleNumBox.value() currentValue -= 45 t.zonalStart_offsetAngleNumBox.setValue(currentValue) def add45_start(): currentValue = t.zonalStart_offsetAngleNumBox.value() currentValue += 45 t.zonalStart_offsetAngleNumBox.setValue(currentValue) def sub45_stop(): currentValue = t.zonalStop_offsetAngleNumBox.value() currentValue -= 45 t.zonalStop_offsetAngleNumBox.setValue(currentValue) def add45_stop(): currentValue = t.zonalStop_offsetAngleNumBox.value() currentValue += 45 t.zonalStop_offsetAngleNumBox.setValue(currentValue) def plotToSkeleton( character ): character.PlotAnimation( FBCharacterPlotWhere.kFBCharacterPlotOnSkeleton, plotOptions() ) def tabBoxChanged(): if t.tabBox.currentIndex() == 0: t.advanced_dimensionComboBox.setEnabled(False) else: t.advanced_dimensionComboBox.setEnabled(True) def calcAV(AvVel, currentDropDownIndex): multiplier = float(t.angularVelMultiplier[currentDropDownIndex]) y = float(t.y_addition[currentDropDownIndex]) if t.turn_autoAVLargeTurnRadio.isChecked(): return (multiplier * AvVel + y)/2 else: return (multiplier * AvVel + y) def FlattenKeys(fcurve): for key in fcurve.Keys: key.TangentMode = FBTangentMode.kFBTangentModeClampProgressive key.LeftTangentWeight = 0.3333; key.RightTangentWeight = 0.3333; key.LeftDerivative = 0.0; key.RightDerivative = 0.0; def parseMotionWarpXML(t): t.motionWarpPath = r"X:\wildwest\script\motionbuildersandbox\Mike\2014\MotionWarpData.xml" print t.motionWarpPath try: t.tree = ET.ElementTree(file=t.motionWarpPath) t.root = t.tree.getroot() except: t.XMLRead = False return t.XMLRead t.angularVelListNames = [] t.angularVelMultiplier = [] t.y_addition = [] t.minRadius = [] for child in t.root: for x in child.iter("type"): getRadius = child.find('minRadius').text t.minRadius.append(getRadius) getMultiplier = child.find('multiplier').text t.angularVelMultiplier.append(getMultiplier) getOffset = child.find('offset').text t.y_addition.append(getOffset) getName = x.get("name") t.angularVelListNames.append(getName) t.XMLRead = True return t.XMLRead def setupTurnParameterList(t): t.turn_AutoAVComboBox.clear() t.turn_AutoAVComboBox.addItems(t.angularVelListNames) def setupPlotModeList(t): t.plotSettings_comboBox.clear() t.plotSettings_comboBox.addItem("Plot Character") t.plotSettings_comboBox.addItem("Plot Selected Nodes") def clamp(n, minn, maxn): return max(min(maxn, n), minn) def ToggleDevices(): for device in FBSystem().Scene.Devices: device.Online = not device.Online def clamp(n, minn, maxn): return max(min(maxn, n), minn) def clean_cos(angle): angle = min(1, max(angle, -1)) if (round(angle, 4) == 1.0): angle = 0.0 return angle def DoesSaveFileExist(): csvpath = tempfile.gettempdir() + '\\motionwarp_save_file.txt' return os.path.isfile(csvpath) def FindPropertyAnimationNode(property_name, model): index = 0 # handle compound attributes if '/' in property_name: split_property_name = property_name.split('/') if split_property_name[1] == 'X': index = 0 if split_property_name[1] == 'Y': index = 1 if split_property_name[1] == 'Z': index = 2 property = model.PropertyList.Find(split_property_name[0]) if property: animationNode = property.GetAnimationNode().Nodes[index] return animationNode else: print "no property found for " + model.Name # simple non-compound property type else: property = model.PropertyList.Find(property_name) if property: animationNode = property.GetAnimationNode() return animationNode else: print "no property found for " + model.Name def SerializeCurve(model_name, property_name, offset_angle, fcurve): ''' Returns a list of dictionaries representing each of the keys in the given FCurve. ''' keyDataList = [] num_of_keys = len(fcurve.Keys) for i in range(num_of_keys): key = fcurve.Keys[i] keyData = { 'model_name': model_name, 'property_name': property_name, 'time': key.Time.Get(), 'value': key.Value, 'interpolation': int(key.Interpolation), 'tangent-mode': int(key.TangentMode), 'constant-mode': int(key.TangentConstantMode), 'left-derivative': key.LeftDerivative, 'right-derivative': key.RightDerivative, 'left-weight': key.LeftTangentWeight, 'right-weight': key.RightTangentWeight, 'offset_angle': offset_angle } keyDataList.append(keyData) return keyDataList def storeZonalValues(): csvdata = [] csvpath = tempfile.gettempdir() + '\\motionwarp_save_file.txt' properties = ['Yaw', 'Translation Offset'] if t.tabBox.currentIndex() == 2: offset_angle = t.zonalStart_offsetAngleNumBox.value() elif t.tabBox.currentIndex() == 3: offset_angle = t.zonalStop_offsetAngleNumBox elif t.tabBox.currentIndex() == 4: offset_angle = t.zonalAdvanced_offsetAngleNumBox01 else: offset_angle = 0 for i in range(0, t.numOfObjects): for j in range(0, len(properties)): property = t.pathConstraint[i].PropertyList.Find(properties[j]) if property: animationNode = property.GetAnimationNode() if animationNode: if len(animationNode.Nodes) > 0: for node in animationNode.Nodes: fcurve = node.FCurve # create a list per key keyDataList = SerializeCurve(t.selected_obj[i].Name, properties[j] + '/' + node.Name, offset_angle, fcurve) for key in keyDataList: csvdata.append(key) else: fcurve = animationNode.FCurve # create a list per key keyDataList = SerializeCurve(t.selected_obj[i].Name, properties[j], offset_angle, fcurve) for key in keyDataList: csvdata.append(key) # save out csv file with open(csvpath, 'w') as csvfile: fieldnames = ['model_name', 'property_name', 'time', 'value', 'interpolation', 'tangent-mode', 'constant-mode', 'left-derivative', 'right-derivative', 'left-weight', 'right-weight', 'offset_angle'] writer = csv.DictWriter(csvfile, fieldnames=fieldnames) writer.writeheader() for element in csvdata: writer.writerow(element) def restoreZonalValues(): # reset min and max ranges for slider_index in range(len(t.zonal.startSlider)): t.zonal.startSlider[slider_index].range_min = 1000000000 t.zonal.endSlider[slider_index].range_max = -1000000000 csvdata = [] csvpath = tempfile.gettempdir() + '\\motionwarp_save_file.txt' model_dictionary = CreateDictionaryFromModels(t.selected_obj) properties = ['Yaw', 'Translation Offset/X', 'Translation Offset/Y', 'Translation Offset/Z'] # load from tmp file with open(csvpath) as csvfile: reader = csv.DictReader(csvfile) for row in reader: csvdata.append(row) new_fcurve_cache = [] curr_fcurve_cache = [] for row in csvdata: # only check against current selected models if row['model_name'] in model_dictionary: index = GetConstraintIndexFromModelName(row['model_name']) animationNode = FindPropertyAnimationNode(row['property_name'], t.pathConstraint[index]) if animationNode: fcurve = animationNode.FCurve if fcurve: propertykey = str(t.pathConstraint[index].Name + row['property_name']) # is this a new curve? if not IsCurveAlreadyCached(new_fcurve_cache, index, propertykey): # create new tmp FCurve new_fcurve_cache.append(FBFCurve()) new_fcurve_cache[-1].propertyname = row['property_name'] new_fcurve_cache[-1].propertykey = propertykey new_fcurve_cache[-1].index = index # load anim data DeserializeCurve(new_fcurve_cache[-1], row) # cache old fcurve pointer too curr_fcurve_cache.append(fcurve) else: fcurve_from_cache = GetCurveFromCache(new_fcurve_cache, index, propertykey) # load anim data DeserializeCurve(fcurve_from_cache, row) else: print "no animation node found!!!!" for i in range(len(new_fcurve_cache)): ScaleCurveValues(new_fcurve_cache[i], curr_fcurve_cache[i]) ################################################## # check range extents of frame time # used to update UI slider positions ################################################## min_frame = curr_fcurve_cache[i].Keys[0].Time.GetFrame() max_frame = curr_fcurve_cache[i].Keys[-1].Time.GetFrame() if min_frame < t.zonal.startSlider[new_fcurve_cache[i].index].range_min: t.zonal.startSlider[new_fcurve_cache[i].index].range_min = min_frame if max_frame > t.zonal.endSlider[new_fcurve_cache[i].index].range_max: t.zonal.endSlider[new_fcurve_cache[i].index].range_max = max_frame def IsCurveAlreadyCached(new_fcurve_cache, index, propertykey): for fcurve in new_fcurve_cache: if fcurve.index == index: if fcurve.propertykey == propertykey: return True return False def GetCurveFromCache(new_fcurve_cache, index, propertykey): for fcurve in new_fcurve_cache: if fcurve.index == index: if fcurve.propertykey == propertykey: return fcurve print "Warning! Curve was not found in cache when it should have been!" def CreateDictionaryFromModels(models): model_dictionary = {} for model in models: model_dictionary[model.Name] = model return model_dictionary def GetConstraintIndexFromModelName(model_name): index = 0 for constraint in t.pathConstraint: if constraint.ModelName == model_name: return index else: index += 1 def TangentWeightIsDefault(tangentWeight): ''' Returns whether the given tangent weight is equal to the default value of 1/3, taking floating-point precision into account. ''' return tangentWeight > 0.3333 and tangentWeight < 0.3334 def DeserializeCurve(fcurve, row): ''' Populates the given FCurve based on keyframe data listed in serialized form. Expects key data to be ordered by time. ''' keyIndex = fcurve.KeyAdd(FBTime(int(row['time'])), float(row['value'])) key = fcurve.Keys[keyIndex] key.Interpolation = FBInterpolation.values[float(row['interpolation'])] key.TangentMode = FBTangentMode.values[float(row['tangent-mode'])] if key.TangentMode == FBTangentMode.kFBTangentModeTCB: key.TangentMode = FBTangentMode.kFBTangentModeBreak key.TangentConstantMode = FBTangentConstantMode.values[float(row['constant-mode'])] key.LeftDerivative = float(row['left-derivative']) key.RightDerivative = float(row['right-derivative']) if not TangentWeightIsDefault(float(row['left-weight'])): key.LeftTangentWeight = float(row['left-weight']) if not TangentWeightIsDefault(float(row['right-weight'])): key.RightTangentWeight = float(row['right-weight']) def ScaleCurveValues(new_fcurve, curr_fcurve): # scale new curve to match extents of old curve new_curve_min = GetMinKeyValue(new_fcurve) new_curve_max = GetMaxKeyValue(new_fcurve) new_curve_min_time = new_fcurve.Keys[0].Time.GetFrame() new_curve_max_time = new_fcurve.Keys[-1].Time.GetFrame() new_curve_dir = GetDirectionOfFCurve(new_fcurve) new_curve_range = abs(new_curve_min - new_curve_max) new_curve_range_time = abs(new_curve_max_time - new_curve_min_time) old_curve_min = GetMinKeyValue(curr_fcurve) old_curve_max = GetMaxKeyValue(curr_fcurve) old_curve_min_time = curr_fcurve.Keys[0].Time.GetFrame() old_curve_max_time = curr_fcurve.Keys[-1].Time.GetFrame() old_curve_dir = GetDirectionOfFCurve(curr_fcurve) old_curve_range = abs(old_curve_min - old_curve_max) old_curve_range_time = abs(old_curve_max_time - old_curve_min_time) # if there is no value range then just scale time if abs(new_curve_range) < 0.01: scale_ratio = float(new_curve_range_time) / old_curve_range_time offset = int(new_curve_min_time - old_curve_min_time) ScaleFCurveTime(curr_fcurve, FBTime(0, 0, 0, old_curve_min_time), scale_ratio, offset) return else: # then scale values of new fcurve to match existing curve value extents for key in new_fcurve.Keys: normalised_value = 1.0 if new_curve_dir * old_curve_dir > 0: normalised_value = abs(key.Value - new_curve_min) / new_curve_range else: normalised_value = abs(key.Value - new_curve_max) / new_curve_range key.Value = old_curve_min + (normalised_value * old_curve_range) # copy fcurve curr_fcurve.EditClear() curr_fcurve.KeyReplaceBy(new_fcurve) def GetMinKeyValue(fcurve): min = 1000000000.0 for key in fcurve.Keys: if key.Value < min: min = key.Value return min def GetMaxKeyValue(fcurve): max = -1000000000.0 for key in fcurve.Keys: if key.Value > max: max = key.Value return max def GetDirectionOfFCurve(fcurve): if fcurve.Keys[0].Value <= fcurve.Keys[-1].Value: return 1 else: return -1 ''' def GetAbsMaxValueOfFCurve( fcurve, ref sign ): abs_max_value = 0.0 for key in fcurve.Keys: abs_value = abs( key.Value ) if abs_value > abs_max_value: abs_max_value = key.Value return abs_max_value ''' def closeZonalHelperWindow(): FBDestroyToolByName("Zonal Key Helper") def clearSelection(): ''' Clear selection function ''' # Get selected models modelList = FBModelList() FBGetSelectedModels(modelList, None, True) # Deselect models for model in modelList: model.Selected = False # deselect constraints and unfocus any custom properties for constraint in t.pathConstraint: yaw_prop = constraint.PropertyList.Find("Yaw") if yaw_prop: # select custom property yaw_prop.SetFocus(False) translation_prop = constraint.PropertyList.Find("Translation Offset") if translation_prop: # select custom property translation_prop.SetFocus(False) constraint.Selected = False def cleanupAndReset(): '''delete all constraints/paths and reset arrays to starting state''' for j in range(0, t.numOfObjects): # delete constraints t.constraint[j].FBDelete() t.pathConstraint[j].FBDelete() # delete nulls t.null[j].FBDelete() t.pathNull[j].FBDelete() # delete path t.path[j].FBDelete() # reset arrays t.null = [] t.midNull = [] t.pathNull = [] t.path = [] t.pathConstraint = [] t.midNullConstraint = [] t.constraint = [] # delete keycontrols and reset t.keyGroup.RemoveAllProperties() t.keyGroup.FBDelete() if t.numOfObjects == 1: t.turn_applyMotionWarpBtn.setText(QtGui.QApplication.translate("MotionWarp", "Apply MotionWarp to '" + t.selected_obj_name[0] + "'", None, QtGui.QApplication.UnicodeUTF8)) t.spline_applyMotionWarpBtn.setText(QtGui.QApplication.translate("MotionWarp", "Apply MotionWarp to '" + t.selected_obj_name[0] + "'", None, QtGui.QApplication.UnicodeUTF8)) t.zonalStart_applyMotionWarpBtn.setText(QtGui.QApplication.translate("MotionWarp", "Apply MotionWarp to '" + t.selected_obj_name[0] + "'", None, QtGui.QApplication.UnicodeUTF8)) t.zonalStop_applyMotionWarpBtn.setText(QtGui.QApplication.translate("MotionWarp", "Apply MotionWarp to '" + t.selected_obj_name[0] + "'", None, QtGui.QApplication.UnicodeUTF8)) t.zonalAdvanced_applyMotionWarpBtn.setText(QtGui.QApplication.translate("MotionWarp", "Apply MotionWarp to '" + t.selected_obj_name[0] + "'", None, QtGui.QApplication.UnicodeUTF8)) else: t.turn_applyMotionWarpBtn.setText(QtGui.QApplication.translate("MotionWarp", "Apply MotionWarp to " + str( t.numOfObjects) + " objects", None, QtGui.QApplication.UnicodeUTF8)) t.spline_applyMotionWarpBtn.setText(QtGui.QApplication.translate("MotionWarp", "Apply MotionWarp to " + str( t.numOfObjects) + " objects", None, QtGui.QApplication.UnicodeUTF8)) t.zonalStart_applyMotionWarpBtn.setText(QtGui.QApplication.translate("MotionWarp", "Apply MotionWarp to " + str( t.numOfObjects) + " objects", None, QtGui.QApplication.UnicodeUTF8)) t.zonalStop_applyMotionWarpBtn.setText(QtGui.QApplication.translate("MotionWarp", "Apply MotionWarp to " + str( t.numOfObjects) + " objects", None, QtGui.QApplication.UnicodeUTF8)) t.zonalAdvanced_applyMotionWarpBtn.setText(QtGui.QApplication.translate("MotionWarp", "Apply MotionWarp to " + str( t.numOfObjects) + " objects", None, QtGui.QApplication.UnicodeUTF8)) t.turn_deleteMotionWarpBtn.setEnabled(False) t.spline_deleteMotionWarpBtn.setEnabled(False) t.zonalStart_deleteMotionWarpBtn.setEnabled(False) t.zonalStop_deleteMotionWarpBtn.setEnabled(False) t.zonalAdvanced_deleteMotionWarpBtn.setEnabled(False) t.pinned = False t.turnTab.setEnabled(True) t.splineTab.setEnabled(True) t.zonalStartTab.setEnabled(True) t.zonalStopTab.setEnabled(True) t.zonalAdvancedtab.setEnabled(True) t.manualAVTab.setEnabled(True) t.manualTurnTab.setEnabled(True) t.autoAVTab.setEnabled(True) t.zonalStart_keyHelperBtn.setEnabled(False) t.zonalStop_keyHelperBtn.setEnabled(False) t.zonalAdvanced_keyHelperBtn.setEnabled(False) #################################################################### # UI callbacks #################################################################### def sliderUpdated(control, event): t.status.Caption = str(int(control.Value * 10) + 2) def deleteButtonCallBack(): if t.tabBox.currentIndex() == 2 or t.tabBox.currentIndex() == 3: if hasattr(t, 'zonal'): if not t.zonal.TypeInfo == 203: closeZonalHelperWindow() cleanupAndReset() def zonalKeyHelperCallBack(): t.zonal = FBCreateUniqueTool("Zonal Key Helper") PopulateZonalUI(t.zonal) def goButtonCallBack(): # Disable all expressions for the duration of this event t.progressBar.show() t.windowSizeY += 30 t.resize(t.windowSizeX, t.windowSizeY) t.setMinimumSize(QtCore.QSize(t.windowSizeX, t.windowSizeY)) t.setMaximumSize(QtCore.QSize(t.windowSizeX, t.windowSizeY)) with SceneExpressionsDisabled(): # get time span global take_start_frame global take_end_frame global current_frame t.take_start_frame = FBSystem().CurrentTake.LocalTimeSpan.GetStart().GetFrame() t.take_end_frame = FBSystem().CurrentTake.LocalTimeSpan.GetStop().GetFrame() t.current_frame = FBSystem().LocalTime.GetFrame() # HORRENDOUS HACK ALERT # this fixes an issue with turns. Need to figure out why this fixes it when time permits :( t.take_end_frame += 1 # update slider to startframe FBPlayerControl().Goto(FBTime(0, 0, 0, t.take_start_frame)) FBSystem().Scene.Evaluate() t.progress = 0 t.progressBar.setValue(t.progress) for i in range(0, t.numOfObjects): ######################################## # Initialise ######################################## if t.pinned == False: clearSelection() # create null t.null.append(FBModelNull('constraint_null for ' + t.selected_obj_name[i])) # create null t.midNull.append(FBModelNull('mid_null')) # create path null t.pathNull.append(FBModelNull('path_null for ' + t.selected_obj_name[i])) # create path curve t.path.append(FBModelPath3D('MotionWarp 3d Curve for ' + t.selected_obj_name[i])) # if it's the mover then make the 3D curve a different colour since we don't want to edit this one manually in most cases if (t.selected_obj_name[i] == 'mover'): # get curve color property color_property = t.path[-1].PropertyList.Find('Curve Color') if color_property: color_property.Data = FBColor(0.0, 0.0, 1.0) t.path[i].Show = True ##################################################################################### # set keying group # need to plot translation/rotation ##################################################################################### ltran = t.null[i].PropertyList.Find('Translation (Lcl)') lrot = t.null[i].PropertyList.Find('Rotation (Lcl)') t.keyGroup = FBKeyingGroup("MotionWarpKeyGroup", FBKeyingGroupType.kFBKeyingGroupGlobal) # Add Properties to Custom Key Group t.keyGroup.AddProperty(ltran) t.keyGroup.AddProperty(lrot) # # SetActive, activate the keying group. t.keyGroup.SetActive(True) # # SetEnabled, will make available the keying group in keying group list of the key control UI. t.keyGroup.SetEnabled(True) # make sure system in on current layer FBSystem().CurrentTake.SetCurrentLayer(0) t.progress += ((20 / t.numOfObjects) * i) t.progressBar.setValue(t.progress) if t.pinned == False: # turn off expressions for optimal performance # ToggleDevices() # lists for storing object pos and rotations trans_array = [] trans_warp_weighting = [] path_start_position = [] rot_array = [] count = 0 # ---------------------------------------------------------------------------------- # Get Mover position of current character # This will be used as a reference point for offsets # if no Mover is found use initialised vector instead # ---------------------------------------------------------------------------------- SEARCH_STRING = 'mover' mover_start_position = FBVector3d() mover_end_position = FBVector3d() invMoverMx = FBMatrix() moverWorldMx = FBMatrix() mover_world_scaleV3 = FBSVector() t.moverObject = '' character = FBApplication().CurrentCharacter if character is not None: t.moverObject = FBFindModelByLabelName(character.OwnerNamespace.Name + ':' + SEARCH_STRING) if t.moverObject is None: print "WARNING! " + SEARCH_STRING + " not Found!" # ---------------------------------------------------------------------------------- # loop for every frame - get pos/rot of selected object # ---------------------------------------------------------------------------------- for frame in range(t.take_start_frame, t.take_end_frame + 1): t.progress += (20 / len(range(t.take_start_frame, t.take_end_frame)) * i) t.progressBar.setValue(t.progress) FBPlayerControl().Goto(FBTime(0, 0, 0, frame)) FBSystem().Scene.Evaluate() # get selected translation and rotation for object for i in range(0, t.numOfObjects): obj_trans = FBVector3d() obj_rot = FBVector3d() t.selected_obj[i].GetVector(obj_trans, FBModelTransformationType.kModelTranslation, True) t.selected_obj[i].GetVector(obj_rot, FBModelTransformationType.kModelRotation, True) ############################################################################################################################## # if we're on the last object also grab mover inv matrix and position ############################################################################################################################## if (i == t.numOfObjects - 1): if (frame == t.take_start_frame): # get start position t.moverObject.GetVector(mover_start_position, FBModelTransformationType.kModelTranslation, True) # get world and inverse matrices for Mover t.moverObject.GetMatrix(invMoverMx, FBModelTransformationType.kModelInverse_Transformation, True) t.moverObject.GetMatrix(moverWorldMx, FBModelTransformationType.kModelTransformation, True) FBMatrixToScaling(mover_world_scaleV3, moverWorldMx) if (frame == t.take_end_frame): t.moverObject.GetVector(mover_end_position, FBModelTransformationType.kModelTranslation, True) current_frame_mover_pos = FBVector3d() current_frame_invMoverMx = FBMatrix() t.moverObject.GetMatrix(current_frame_invMoverMx, FBModelTransformationType.kModelInverse_Transformation, True) t.moverObject.GetVector(current_frame_mover_pos, FBModelTransformationType.kModelTranslation, True) ############################################################################################################################## # store translation/rotation trans_array.append(obj_trans) rot_array.append(obj_rot) t.midNull[i].Selected = True if (frame == t.take_start_frame): # store path position start path_start_position.append(obj_trans) count += 1 # ---------------------------------------------------------------------------------- # calculate path lengths # ---------------------------------------------------------------------------------- path_length = [] frame_range = t.take_end_frame - t.take_start_frame for i in range(0, t.numOfObjects): if t.advanced_dimensionComboBox.currentIndex() == 1: path_length.append( FBVector3d((trans_array[(frame_range - 1) * t.numOfObjects + i][0] - trans_array[i][0]), (trans_array[(frame_range - 1) * t.numOfObjects + i][1] - trans_array[i][1]), (trans_array[(frame_range - 1) * t.numOfObjects + i][2] - trans_array[i][2]) ).Length() ) else: path_length.append( FBVector3d((trans_array[(frame_range - 1) * t.numOfObjects + i][0] - trans_array[i][0]), 0, (trans_array[(frame_range - 1) * t.numOfObjects + i][2] - trans_array[i][2]) ).Length() ) # ---------------------------------------------------------------------------------- # loop for every frame - set pos/rot on null # ---------------------------------------------------------------------------------- count = 0 if t.tabBox.currentIndex() == 0: curve_steps = 2 else: curve_steps = int(t.spline_splinePointNumSlider.value() * 10) + 2 frame_step = float(frame_range / (curve_steps - 1.0)) frame_step_total = t.take_start_frame + frame_step frame = t.take_start_frame while frame <= t.take_end_frame: # update slider FBPlayerControl().Goto(FBTime(0, 0, 0, frame)) FBSystem().Scene.Evaluate() for i in range(0, t.numOfObjects): # set up path curve on first frame if (frame == t.take_start_frame): # path starts at each objects own starting frame pathStart = FBVector4d(trans_array[count * t.numOfObjects + i][0], trans_array[count * t.numOfObjects + i][1], trans_array[count * t.numOfObjects + i][2], 0) t.path[i].PathKeyStartAdd(pathStart) # set pivot to be same as mover as default pivot = t.path[i].PropertyList.Find('Scaling Pivot') if pivot: pivot.Data = mover_start_position pivot = t.path[i].PropertyList.Find('Rotation Pivot') if pivot: pivot.Data = mover_start_position # remove next three indices as they are created automatically and are unwanted # each time we remove index1 the curve indices are recalculated so we need to do this three times # looks odd! t.path[i].PathKeyRemove(1) t.path[i].PathKeyRemove(1) t.path[i].PathKeyRemove(1) trans_warp_weighting.append(0.0) else: # only if we're on a 'frame step' keyframe do we want to create a spline point (or end frame) if (frame >= frame_step_total or frame == t.take_end_frame): # Add path key. Fix the height to be the same as the starting height if t.advanced_dimensionComboBox.currentIndex() == 1: t.path[i].PathKeyEndAdd( FBVector4d(trans_array[count * t.numOfObjects + i][0], trans_array[count * t.numOfObjects + i][1], trans_array[count * t.numOfObjects + i][2], 0)) else: t.path[i].PathKeyEndAdd( FBVector4d(trans_array[count * t.numOfObjects + i][0], trans_array[i][1], trans_array[count * t.numOfObjects + i][2], 0)) # Fix for bug. if the end frame is to close to the penultimate keyframe the tangent stretches out the curve. # Reduce tangent length on penultimate key to zero if (frame == t.take_end_frame and t.tabBox.currentIndex() == 0): # linear tangent for penultimate key keyIndex = t.path[i].PathKeyGetCount() - 2 keyPosition = t.path[i].PathKeyGet(keyIndex) # set tangent to be the same position as the keyframe (creating a linear section between this and the last path key) t.path[i].PathKeySetLeftTangent(keyIndex, keyPosition, True) # if we're at the end of the object list *and* we're on a frame step then increment frame step total if (i == t.numOfObjects - 1): frame_step_total += frame_step # trans warp weighting is an estimated percentage of the overall motionwarp path moved in this frame # this will allow the motion warp path to more accurately follow the selected object # calc % distance travelled this frame of overall path length (very approximate) if t.advanced_dimensionComboBox.currentIndex() == 1: current_length = FBVector3d( (trans_array[count * t.numOfObjects + i][0] - trans_array[i][0]), (trans_array[count * t.numOfObjects + i][1] - trans_array[i][1]), (trans_array[count * t.numOfObjects + i][2] - trans_array[i][2]) ).Length() else: current_length = FBVector3d( (trans_array[count * t.numOfObjects + i][0] - trans_array[i][0]), 0, (trans_array[count * t.numOfObjects + i][2] - trans_array[i][2]) ).Length() if not path_length[i] == 0.0: trans_warp_weighting.append((current_length / path_length[i]) * 100.0) # if path length is zero use the frame progression to update the weighting percentage else: trans_warp_weighting.append((frame / frame_range) * 100.0) # set selected translation and rotation for object t.midNull[i].SetVector(trans_array[count * t.numOfObjects + i], FBModelTransformationType.kModelTranslation, True) t.midNull[i].SetVector(rot_array[count * t.numOfObjects + i], FBModelTransformationType.kModelRotation, True) FBSystem().Scene.Evaluate() # key null FBPlayerControl().Key() if (frame == t.take_end_frame): break # increment frame and count frame += 1 count += 1 # clamp frame range to end frame if (frame > t.take_end_frame): frame = t.take_end_frame count = frame_range for j in range(0, t.numOfObjects): ################################################################################### # constrain path_null to path ################################################################################### lMgr = FBConstraintManager() # get index to parent/child constraint lIndex = None for i in range(0, lMgr.TypeGetCount()): if lMgr.TypeGetName(i) == 'Path': lIndex = i break # create constraint path_constraint = lMgr.TypeCreateConstraint(lIndex) path_constraint.Name = 'MotionWarp Path Constraint for ' + t.selected_obj_name[j] # add ref objects for i in range(0, path_constraint.ReferenceGroupGetCount()): if path_constraint.ReferenceGroupGetName(i) == 'Path Source': path_constraint.ReferenceAdd(i, t.path[j]) elif path_constraint.ReferenceGroupGetName(i) == 'Constrained Object': path_constraint.ReferenceAdd(i, t.pathNull[j]) # snap/activate constraint path_constraint.Active = True # store ref to constraint objects t.pathConstraint.append(path_constraint) # store ref to the model related to the constraint t.pathConstraint[len(t.pathConstraint) - 1].ModelName = t.selected_obj_name[j] if t.tabBox.currentIndex() == 0: path_constraint.PropertyList.Find("Follow Path").Data = False else: path_constraint.PropertyList.Find("Follow Path").Data = True # path_constraint.PropertyList.Find("Follow Path").Data = True path_constraint.PropertyList.Find("Up Vector").Data = 2 path_constraint.PropertyList.Find("Front Vector").Data = 0 ############################### # alter path warp to match object % ############################### warpProperty = path_constraint.PropertyList.Find("Warp") warpAn = warpProperty.GetAnimationNode() warpFc = warpAn.FCurve count = 0 for key in range(t.take_start_frame, t.take_end_frame): warpFc.KeyAdd(FBTime(0, 0, 0, key), trans_warp_weighting[count * t.numOfObjects + j]) count += 1 ################################################################################### # constrain constraint_null to midd_null ################################################################################### lMgr = FBConstraintManager() # get index to parent/child constraint lIndex = None for i in range(0, lMgr.TypeGetCount()): if lMgr.TypeGetName(i) == 'Parent/Child': lIndex = i break # create constraint lPosConst = lMgr.TypeCreateConstraint(lIndex) # add ref objects for i in range(0, lPosConst.ReferenceGroupGetCount()): if lPosConst.ReferenceGroupGetName(i) == 'Source (Parent)': lPosConst.ReferenceAdd(i, t.midNull[j]) elif lPosConst.ReferenceGroupGetName(i) == 'Constrained object (Child)': lPosConst.ReferenceAdd(i, t.null[j]) # activate constraint lPosConst.Active = True # store ref to constraint objects t.midNullConstraint.append(lPosConst) ################################################################################### # parent constraint null ################################################################################### # this is where the magic happens :) # we've contrained the null to the midnull so it can't change but we change the heirarchy and plot # so that the offset per frame now becomes a local transformation and we keep the existing world space transformation # through this counter animation t.null[j].Parent = t.pathNull[j] ################################################################################### # plot and remove mid null ################################################################################### clearSelection() for i in range(0, t.numOfObjects): t.null[i].Selected = True # ToggleDevices() FBSystem().CurrentTake.PlotTakeOnSelected(plotOptions()) # ToggleDevices() FBSystem().Scene.Evaluate() for i in range(0, t.numOfObjects): # delete mid null t.midNull[i].FBDelete() # delete constraint t.midNullConstraint[i].FBDelete() ################################################################################### # finally constrain selected object to constraint null ################################################################################### for j in range(0, t.numOfObjects): lMgr = FBConstraintManager() # get index to parent/child constraint lIndex = None for i in range(0, lMgr.TypeGetCount()): if lMgr.TypeGetName(i) == 'Parent/Child': lIndex = i break # create constraint lPosConst = lMgr.TypeCreateConstraint(lIndex) lPosConst.Name = ' Parent/Child Constraint to MotionWarp Path for ' + t.selected_obj_name[j] # add ref objects for i in range(0, lPosConst.ReferenceGroupGetCount()): if lPosConst.ReferenceGroupGetName(i) == 'Source (Parent)': lPosConst.ReferenceAdd(i, t.null[j]) elif lPosConst.ReferenceGroupGetName(i) == 'Constrained object (Child)': lPosConst.ReferenceAdd(i, t.selected_obj[j]) # snap/activate constraint lPosConst.Snap() # store ref to constraint objects t.constraint.append(lPosConst) ################################################################################### # Circle Mode # # if mode selected move spline points around into circle ################################################################################### if t.tabBox.currentIndex() == 0: ################################################################################### # turn on path constraint and set yaw offset to angle between world X axis (1,0,0) and Mover Y axis direction ################################################################################### for path_constraint in t.pathConstraint: path_constraint.PropertyList.Find("Follow Path").Data = True # calc angle world_x_axis_in_mover_space = FBVector4d() FBVectorMatrixMult(world_x_axis_in_mover_space, invMoverMx, FBVector4d(mover_start_position[0], mover_start_position[1], mover_start_position[2], 0.0) + FBVector4d(100.0, 0.0, 0.0, 0.0)) dot = world_x_axis_in_mover_space.DotProduct(FBVector4d(0.0, 1.0, 0.0, 0.0)) # catch for zero angle (1.0) since it causes math.acos to error # requires dot rounded since very tiny floats were also slipping through comparison check # but still causing math.acos to bomb out dot = clean_cos(dot) angle_between_x_axes = math.acos(dot) * 180.0 / math.pi if world_x_axis_in_mover_space[0] >= 0.0: path_constraint.PropertyList.Find("Yaw").Data = -angle_between_x_axes else: path_constraint.PropertyList.Find("Yaw").Data = angle_between_x_axes worldCoords = FBVector4d() objectPositionInMoverSpace = FBVector4d() ModelMx = FBMatrix() TransformedModelMx = FBMatrix() # unit to metre conversion in MB scale = 100.0 ################################################################################################ # cache user direction choice for circle - (note: flip value) ################################################################################################ for j in range(0, t.numOfObjects): ################################################################################################ # calcluate radius # either direct from UI if radius mode is selected, or calculated from the angular velocity ################################################################################################ if t.turnTabBox.currentIndex() == 1: radius = t.manualTurn_RadiusNumBox.value() if t.manualTurn_counterClockwiseRadio.isChecked(): direction = -1 else: direction = 1 elif t.turnTabBox.currentIndex() == 0: if t.autoAV_counterClockwiseRadio.isChecked(): direction = -1 else: direction = 1 average_velocity_ms = (path_length[j] / scale) / (frame_range / 30.0) print "average_velocity_ms is " + str(average_velocity_ms) # calculate the desired angular velocity of from overall average speed currentDropDownSelection = t.turn_AutoAVComboBox.currentIndex() calculated_angular_velocity = calcAV(average_velocity_ms, currentDropDownSelection) print "calculated angular velocity is " + str(calculated_angular_velocity) radius = average_velocity_ms / calculated_angular_velocity selectedMininumRadius = float(t.minRadius[currentDropDownSelection]) if t.turn_autoAVLargeTurnRadio.isChecked(): # clamp value of radius if it's too tight if radius < (selectedMininumRadius * 2): radius = (selectedMininumRadius * 2) else: if radius < selectedMininumRadius: radius = selectedMininumRadius print "calculated radius is " + str(radius) else: if t.manualAV_counterClockwiseRadio.isChecked(): direction = -1 else: direction = 1 average_velocity_ms = (path_length[j] / scale) / (frame_range / 30.0) print "average_velocity_ms is " + str(average_velocity_ms) radius = average_velocity_ms / t.manualAV_AVNumBox.value() print "calculated radius is " + str(radius) prescaled_radius = radius # centre point is the circle origin with respect to the mover ( of radius) FBVectorMatrixMult(worldCoords, moverWorldMx, FBVector4d(radius * direction, 0.0, 0.0, 0.0)) centrePoint = FBVector3d(worldCoords[0], worldCoords[1], worldCoords[2]) print "########################################" print "OBJECT >> " + t.selected_obj_name[j] print "########################################" # make sure we have at least five points... while (t.path[j].PathKeyGetCount() < 5): t.path[j].PathKeyEndAdd(FBVector4d()) # remove any extraneous points over 5... while (t.path[j].PathKeyGetCount() > 5): t.path[j].PathKeyRemove(t.path[j].PathKeyGetCount() - 1) # create four point circle points and edit tangents circle_height = t.path[j].PathKeyGet(0)[1] # convert selected object to Mover coord space FBVectorMatrixMult(objectPositionInMoverSpace, invMoverMx, FBVector4d(path_start_position[j][0], path_start_position[j][1], path_start_position[j][2], 0.0)) # copy to a FBVector3d type for ease of use objectOffsetFromMover = FBVector3d(objectPositionInMoverSpace[0], objectPositionInMoverSpace[1], objectPositionInMoverSpace[2]) radius = (FBVector3d(path_start_position[j][0], 0.0, path_start_position[j][2]) - FBVector3d( centrePoint[0], 0.0, centrePoint[2])).Length() / scale # magic number/constant below is (4/3)*tan(math.pi/8) based on four control points # see http://stackoverflow.com/questions/1734745/how-to-create-circle-with-b%C3%A9zier-curves tangent_length = 0.552284749831 * radius ############################################################################################ # calculate circle angle offset ############################################################################################ v2 = path_start_position[j] - centrePoint v2[1] = 0.0 v1 = mover_start_position - centrePoint v1[1] = 0.0 v1.Normalize() v2.Normalize() dot = v1.DotProduct(v2) # catch for zero angle (1.0) since it causes math.acos to error # requires dot rounded since very tiny floats were also slipping through comparison check # but still causing math.acos to bomb out if (round(dot, 4) == 1.0): circleAngleOffset = 0.0 else: circleAngleOffset = math.acos(dot) # if object position is in front or behind mover then flip angle depending on circle direction if (objectOffsetFromMover[1] > 0.0 and direction == 1) or ( objectOffsetFromMover[1] < 0.0 and direction == -1): circleAngleOffset *= -1 ############################################################################################ # create circle with all points rotated by circleAngleOffset ############################################################################################ ################################### # Circle Point 0 ################################### circlePoint = FBVector3d(0.0, 0.0, 0.0) rotatedCirclePoint = RotateVectorRadians(circlePoint, circleAngleOffset) # add any offset between object and mover rotatedCirclePoint += objectOffsetFromMover # convert to WS & set FBVectorMatrixMult(worldCoords, moverWorldMx, FBVector4d(rotatedCirclePoint[0], rotatedCirclePoint[1], rotatedCirclePoint[2], 1.0)) # cache circle point position for tangent use transformedCirclePoint = FBVector4d(worldCoords[0], worldCoords[1], worldCoords[2], 0.0) t.path[j].PathKeySet(0, transformedCirclePoint) # rotate tangent tangent = FBVector3d(0.0, tangent_length, 0.0) rotatedTangent = RotateVectorRadians(tangent, circleAngleOffset) # convert to WS FBVectorMatrixMult(worldCoords, moverWorldMx, FBVector4d(rotatedTangent[0], rotatedTangent[1], rotatedTangent[2], 0.0)) # create relative vector from mover to tangent end, and add to circle point worldCoords = FBVector4d((worldCoords[0] - mover_start_position[0]) + transformedCirclePoint[0], (worldCoords[1] - mover_start_position[1]) + transformedCirclePoint[1], (worldCoords[2] - mover_start_position[2]) + transformedCirclePoint[2], 0.0) # have to set both tangent sides on the first point for some strange reason only known to MB..... t.path[j].PathKeySetLeftTangent(0, FBVector4d(worldCoords[0], worldCoords[1], worldCoords[2], 0.0), True) t.path[j].PathKeySetRightTangent(0, FBVector4d(worldCoords[0], worldCoords[1], worldCoords[2], 0.0), True) ################################### # Circle Point 1 ################################### circlePoint = FBVector3d(direction * radius, radius, 0.0) rotatedCirclePoint = RotateVectorRadians(circlePoint, circleAngleOffset) # add any offset between object and mover rotatedCirclePoint += objectOffsetFromMover # convert to WS & set FBVectorMatrixMult(worldCoords, moverWorldMx, FBVector4d(rotatedCirclePoint[0], rotatedCirclePoint[1], rotatedCirclePoint[2], 0.0)) # cache circle point position for tangent use transformedCirclePoint = FBVector4d(worldCoords[0], worldCoords[1], worldCoords[2], 0.0) t.path[j].PathKeySet(1, transformedCirclePoint) # rotate tangent tangent = FBVector3d(-direction * tangent_length, 0.0, 0.0) rotatedTangent = RotateVectorRadians(tangent, circleAngleOffset) # convert to WS FBVectorMatrixMult(worldCoords, moverWorldMx, FBVector4d(rotatedTangent[0], rotatedTangent[1], rotatedTangent[2], 0.0)) # create relative vector from mover to tangent end, and add to circle point worldCoords = FBVector4d((worldCoords[0] - mover_start_position[0]) + transformedCirclePoint[0], (worldCoords[1] - mover_start_position[1]) + transformedCirclePoint[1], (worldCoords[2] - mover_start_position[2]) + transformedCirclePoint[2], 0.0) t.path[j].PathKeySetLeftTangent(1, FBVector4d(worldCoords[0], worldCoords[1], worldCoords[2], 0.0), True) ################################### # Circle Point 2 ################################### circlePoint = FBVector3d(direction * radius * 2.0, 0.0, 0.0) rotatedCirclePoint = RotateVectorRadians(circlePoint, circleAngleOffset) # add any offset between object and mover rotatedCirclePoint += objectOffsetFromMover # convert to WS & set FBVectorMatrixMult(worldCoords, moverWorldMx, FBVector4d(rotatedCirclePoint[0], rotatedCirclePoint[1], rotatedCirclePoint[2], 0.0)) # cache circle point position for tangent use transformedCirclePoint = FBVector4d(worldCoords[0], worldCoords[1], worldCoords[2], 0.0) t.path[j].PathKeySet(2, transformedCirclePoint) # rotate tangent tangent = FBVector3d(0.0, tangent_length, 0.0) rotatedTangent = RotateVectorRadians(tangent, circleAngleOffset) # convert to WS FBVectorMatrixMult(worldCoords, moverWorldMx, FBVector4d(rotatedTangent[0], rotatedTangent[1], rotatedTangent[2], 0.0)) # create relative vector from mover to tangent end, and add to circle point worldCoords = FBVector4d((worldCoords[0] - mover_start_position[0]) + transformedCirclePoint[0], (worldCoords[1] - mover_start_position[1]) + transformedCirclePoint[1], (worldCoords[2] - mover_start_position[2]) + transformedCirclePoint[2], 0.0) t.path[j].PathKeySetLeftTangent(2, FBVector4d(worldCoords[0], worldCoords[1], worldCoords[2], 0.0), True) ################################### # Circle Point 3 ################################### circlePoint = FBVector3d(direction * radius, -radius, 0.0) rotatedCirclePoint = RotateVectorRadians(circlePoint, circleAngleOffset) # add any offset between object and mover rotatedCirclePoint += objectOffsetFromMover # convert to WS & set FBVectorMatrixMult(worldCoords, moverWorldMx, FBVector4d(rotatedCirclePoint[0], rotatedCirclePoint[1], rotatedCirclePoint[2], 0.0)) # cache circle point position for tangent use transformedCirclePoint = FBVector4d(worldCoords[0], worldCoords[1], worldCoords[2], 0.0) t.path[j].PathKeySet(3, worldCoords) # rotate tangent tangent = FBVector3d(direction * tangent_length, 0.0, 0.0) rotatedTangent = RotateVectorRadians(tangent, circleAngleOffset) # convert to WS FBVectorMatrixMult(worldCoords, moverWorldMx, FBVector4d(rotatedTangent[0], rotatedTangent[1], rotatedTangent[2], 0.0)) # create relative vector from mover to tangent end, and add to circle point worldCoords = FBVector4d((worldCoords[0] - mover_start_position[0]) + transformedCirclePoint[0], (worldCoords[1] - mover_start_position[1]) + transformedCirclePoint[1], (worldCoords[2] - mover_start_position[2]) + transformedCirclePoint[2], 0.0) t.path[j].PathKeySetLeftTangent(3, FBVector4d(worldCoords[0], worldCoords[1], worldCoords[2], 0.0), True) ################################### # Circle Point 4 ################################### circlePoint = FBVector3d(0.0, 0.0, 0.0) rotatedCirclePoint = RotateVectorRadians(circlePoint, circleAngleOffset) # add any offset between object and mover rotatedCirclePoint += objectOffsetFromMover # convert to WS & set FBVectorMatrixMult(worldCoords, moverWorldMx, FBVector4d(rotatedCirclePoint[0], rotatedCirclePoint[1], rotatedCirclePoint[2], 0.0)) # cache circle point position for tangent use transformedCirclePoint = FBVector4d(worldCoords[0], worldCoords[1], worldCoords[2], 0.0) t.path[j].PathKeySet(4, worldCoords) # rotate tangent tangent = FBVector3d(0.0, -tangent_length, 0.0) rotatedTangent = RotateVectorRadians(tangent, circleAngleOffset) # convert to WS FBVectorMatrixMult(worldCoords, moverWorldMx, FBVector4d(rotatedTangent[0], rotatedTangent[1], rotatedTangent[2], 0.0)) # create relative vector from mover to tangent end, and add to circle point worldCoords = FBVector4d((worldCoords[0] - mover_start_position[0]) + transformedCirclePoint[0], (worldCoords[1] - mover_start_position[1]) + transformedCirclePoint[1], (worldCoords[2] - mover_start_position[2]) + transformedCirclePoint[2], 0.0) t.path[j].PathKeySetLeftTangent(4, FBVector4d(worldCoords[0], worldCoords[1], worldCoords[2], 0.0), True) ############################### # alter curve pivot to sit at centre and move null to beginning ############################### pivot = t.path[j].PropertyList.Find('Scaling Pivot') if pivot: pivot.Data = FBVector3d(centrePoint[0], path_start_position[j][1], centrePoint[2]) pivot = t.path[j].PropertyList.Find('Rotation Pivot') if pivot: pivot.Data = FBVector3d(centrePoint[0], path_start_position[j][1], centrePoint[2]) # move null to beginning of circle t.null[j].SetVector(FBVector3d(path_start_position[j][0], circle_height, path_start_position[j][2]), FBModelTransformationType.kModelTranslation, True) ############################### # alter path warp to match object % ############################### warpProperty = t.pathConstraint[j].PropertyList.Find("Warp") warpAn = warpProperty.GetAnimationNode() warpFc = warpAn.FCurve keys = warpProperty.GetAnimationNode().FCurve.Keys for key in keys: key.Value = (key.Value / 100.0) * ( (path_length[j] / (2.0 * math.pi * prescaled_radius * scale)) * 100.0) # handle circle wrap if (key.Value > 100.0): key.Value -= 100.0 * int(key.Value / 100.0) print "Original path length was ~ " + str(path_length[j]) + ", circle circumference is " + str( 2.0 * math.pi * radius * scale) print "Warp value has been scaled proportionally by ~" + str( (path_length[j] / (2.0 * math.pi * radius * scale)) * 100.0) + " % " t.progress += (20 / t.numOfObjects * j) t.progressBar.setValue(t.progress) ################################################################################### # Zonal Start Mode # # if mode selected rotate spline points around at angle but maintain start pose ################################################################################### if t.tabBox.currentIndex() == 2: print "Zonal Start Mode!" # note. Reverse the angle to match convention rotationAngleAV3 = FBVector3d(0.0, -t.zonalStart_offsetAngleNumBox.value(), 0.0) for i in range(0, t.numOfObjects): # grab starting position pre rotation startKeyPositionV4 = t.path[i].PathKeyGet(0) # rotate splines t.path[i].Rotation = FBVector3d(t.path[i].Rotation[0] + rotationAngleAV3[0], t.path[i].Rotation[1] + rotationAngleAV3[1], t.path[i].Rotation[2] + rotationAngleAV3[2]) # have to force a refresh then get key position again. Note, PathKeyGet( 0 ) does not work after rotation, even with a scene refresh. MB bug. Total_GlobalPathEvaluate does work... FBSystem().Scene.Evaluate() rotatedKeyPositionV4 = t.path[i].Total_GlobalPathEvaluate(0.0) # calc difference in new position and use this to set translation offset offsetV4 = rotatedKeyPositionV4 - startKeyPositionV4 # set offsets so pose at start is the same yaw_prop = t.pathConstraint[i].PropertyList.Find("Yaw") if yaw_prop: yaw_prop.SetAnimated(True) existing_data = yaw_prop.Data # set key on first frame yaw_prop.GetAnimationNode().FCurve.KeyAdd(FBTime(0, 0, 0, t.take_start_frame), existing_data - rotationAngleAV3[1]) # set key on last frame yaw_prop.GetAnimationNode().FCurve.KeyAdd(FBTime(0, 0, 0, t.take_end_frame), existing_data) FlattenKeys(yaw_prop.GetAnimationNode().FCurve) translation_prop = t.pathConstraint[i].PropertyList.Find("Translation Offset") if translation_prop: translation_prop.SetAnimated(True) existing_data = translation_prop.Data # set key on first frame translation_prop.GetAnimationNode().Nodes[0].FCurve.KeyAdd(FBTime(0, 0, 0, t.take_start_frame), existing_data[0] - offsetV4[0]) translation_prop.GetAnimationNode().Nodes[1].FCurve.KeyAdd(FBTime(0, 0, 0, t.take_start_frame), existing_data[1] - offsetV4[1]) translation_prop.GetAnimationNode().Nodes[2].FCurve.KeyAdd(FBTime(0, 0, 0, t.take_start_frame), existing_data[2] - offsetV4[2]) # set key on last frame translation_prop.GetAnimationNode().Nodes[0].FCurve.KeyAdd(FBTime(0, 0, 0, t.take_end_frame), existing_data[0]) translation_prop.GetAnimationNode().Nodes[1].FCurve.KeyAdd(FBTime(0, 0, 0, t.take_end_frame), existing_data[1]) translation_prop.GetAnimationNode().Nodes[2].FCurve.KeyAdd(FBTime(0, 0, 0, t.take_end_frame), existing_data[2]) for node in translation_prop.GetAnimationNode().Nodes: FlattenKeys(node.FCurve) t.progress += (20 / t.numOfObjects * i) t.progressBar.setValue(t.progress) ################################################################################### # Enable Zonal Helper UI ################################################################################### t.zonalStart_keyHelperBtn.setEnabled(True) ################################################################################### # Zonal Stop Mode # # if mode selected rotate spline points around at angle but maintain start pose ################################################################################### if t.tabBox.currentIndex() == 3: print "Zonal Stop Mode!" # note. Reverse the angle to match convention rotationAngleAV3 = FBVector3d(0.0, -t.zonalStop_offsetAngleNumBox.value(), 0.0) # create a new matrix with the rotation angle rotationAngleMx = FBMatrix() FBTRSToMatrix(rotationAngleMx, FBVector4d(), rotationAngleAV3, FBSVector()) for i in range(0, t.numOfObjects): # grab starting position pre rotation of last point endKeyPositionV4 = t.path[i].PathKeyGet(t.path[i].PathKeyGetCount() - 1) # create vector as offset from mover offsetObjectV3 = FBVector3d(endKeyPositionV4[0], endKeyPositionV4[1], endKeyPositionV4[2]) - mover_end_position rotatedOffsetObjectV4 = FBVector4d() # multiply offsetObjectV3 by rotationAngleMx to find new transformed position FBVectorMatrixMult(rotatedOffsetObjectV4, rotationAngleMx, FBVector4d(offsetObjectV3[0], offsetObjectV3[1], offsetObjectV3[2], 1.0)) # move back to mover rotatedObjectV3 = mover_end_position + FBVector3d(rotatedOffsetObjectV4[0], rotatedOffsetObjectV4[1], rotatedOffsetObjectV4[2]) # work out difference between prerotate and rotated translationOffsetV3 = rotatedObjectV3 - FBVector3d(endKeyPositionV4[0], endKeyPositionV4[1], endKeyPositionV4[2]) # set offsets so pose at end is rotated yaw_prop = t.pathConstraint[i].PropertyList.Find("Yaw") if yaw_prop: yaw_prop.SetAnimated(True) existing_data = yaw_prop.Data # set key on first frame yaw_prop.GetAnimationNode().FCurve.KeyAdd(FBTime(0, 0, 0, t.take_start_frame), existing_data) # set key on last frame yaw_prop.GetAnimationNode().FCurve.KeyAdd(FBTime(0, 0, 0, t.take_end_frame), existing_data + rotationAngleAV3[1]) FlattenKeys(yaw_prop.GetAnimationNode().FCurve) translation_prop = t.pathConstraint[i].PropertyList.Find("Translation Offset") if translation_prop: translation_prop.SetAnimated(True) existing_data = translation_prop.Data # set key on first frame translation_prop.GetAnimationNode().Nodes[0].FCurve.KeyAdd(FBTime(0, 0, 0, t.take_start_frame), existing_data[0]) translation_prop.GetAnimationNode().Nodes[1].FCurve.KeyAdd(FBTime(0, 0, 0, t.take_start_frame), existing_data[1]) translation_prop.GetAnimationNode().Nodes[2].FCurve.KeyAdd(FBTime(0, 0, 0, t.take_start_frame), existing_data[2]) # set key on last frame translation_prop.GetAnimationNode().Nodes[0].FCurve.KeyAdd(FBTime(0, 0, 0, t.take_end_frame), existing_data[0] + translationOffsetV3[0]) translation_prop.GetAnimationNode().Nodes[1].FCurve.KeyAdd(FBTime(0, 0, 0, t.take_end_frame), existing_data[1] + translationOffsetV3[1]) translation_prop.GetAnimationNode().Nodes[2].FCurve.KeyAdd(FBTime(0, 0, 0, t.take_end_frame), existing_data[2] + translationOffsetV3[2]) for node in translation_prop.GetAnimationNode().Nodes: FlattenKeys(node.FCurve) ################################################################################### # Enable Zonal Helper UI ################################################################################### t.zonalStop_keyHelperBtn.setEnabled(True) t.progress += (20 / t.numOfObjects * i) t.progressBar.setValue(t.progress) ################################################################################### # Zonal Advanced Mode # # if mode selected allows user to set both an offset to the moving direction and end heading # mainly for creating strafing style assets ################################################################################### if t.tabBox.currentIndex() == 4: FBSystem().Scene.Evaluate() print "Zonal Advanced Mode!" # note. Reverse the angle to match convention rotationAngleAV3 = FBVector3d(0.0, -t.zonalAdvanced_offsetAngleNumBox01.value(), 0.0) for i in range(0, t.numOfObjects): # grab starting position pre rotation startKeyPositionV4 = t.path[i].PathKeyGet(0) # rotate splines t.path[i].Rotation = FBVector3d(t.path[i].Rotation[0] + rotationAngleAV3[0], t.path[i].Rotation[1] + rotationAngleAV3[1], t.path[i].Rotation[2] + rotationAngleAV3[2]) # have to force a refresh then get key position again. Note, PathKeyGet( 0 ) does not work after rotation, even with a scene refresh. MB bug. Total_GlobalPathEvaluate does work... FBSystem().Scene.Evaluate() rotatedKeyPositionV4 = t.path[i].Total_GlobalPathEvaluate(0.0) # calc difference in new position and use this to set translation offset offsetV4 = rotatedKeyPositionV4 - startKeyPositionV4 # set offsets so pose at start is the same yaw_prop = t.pathConstraint[i].PropertyList.Find("Yaw") if yaw_prop: yaw_prop.SetAnimated(True) existing_data = yaw_prop.Data # set key on first frame yaw_prop.GetAnimationNode().FCurve.KeyAdd(FBTime(0, 0, 0, t.take_start_frame), existing_data - rotationAngleAV3[1]) # set key on last frame yaw_prop.GetAnimationNode().FCurve.KeyAdd(FBTime(0, 0, 0, t.take_end_frame), existing_data) FlattenKeys(yaw_prop.GetAnimationNode().FCurve) translation_prop = t.pathConstraint[i].PropertyList.Find("Translation Offset") if translation_prop: translation_prop.SetAnimated(True) existing_data = translation_prop.Data # set key on first frame translation_prop.GetAnimationNode().Nodes[0].FCurve.KeyAdd(FBTime(0, 0, 0, t.take_start_frame), existing_data[0] - offsetV4[0]) translation_prop.GetAnimationNode().Nodes[1].FCurve.KeyAdd(FBTime(0, 0, 0, t.take_start_frame), existing_data[1] - offsetV4[1]) translation_prop.GetAnimationNode().Nodes[2].FCurve.KeyAdd(FBTime(0, 0, 0, t.take_start_frame), existing_data[2] - offsetV4[2]) # set key on last frame translation_prop.GetAnimationNode().Nodes[0].FCurve.KeyAdd(FBTime(0, 0, 0, t.take_end_frame), existing_data[0]) translation_prop.GetAnimationNode().Nodes[1].FCurve.KeyAdd(FBTime(0, 0, 0, t.take_end_frame), existing_data[1]) translation_prop.GetAnimationNode().Nodes[2].FCurve.KeyAdd(FBTime(0, 0, 0, t.take_end_frame), existing_data[2]) for node in translation_prop.GetAnimationNode().Nodes: FlattenKeys(node.FCurve) t.progress += (20 / t.numOfObjects * i) t.progressBar.setValue(t.progress) #################################################################################### # incorporate second offset #################################################################################### # go to end frame and eval FBPlayerControl().Goto(FBTime(0, 0, 0, t.take_end_frame)) FBSystem().Scene.Evaluate() # create matrix around mover end position rotationAngleBV3 = FBVector3d(0.0, -t.zonalAdvanced_offsetAngleNumBox02.value(), 0.0) straferotationAngleAV3 = -rotationAngleAV3 + rotationAngleBV3 pre_strafe_mover_end_pos = FBVector3d() t.moverObject.GetVector(pre_strafe_mover_end_pos, FBModelTransformationType.kModelTranslation, True) rotationAngleMx = FBMatrix() FBTRSToMatrix(rotationAngleMx, FBVector4d(pre_strafe_mover_end_pos[0], pre_strafe_mover_end_pos[1], pre_strafe_mover_end_pos[2], 0.0), straferotationAngleAV3, FBSVector()) # go through each object and transform current ws position by rotationAngleMx # this should give us the position of object as if it had not been rotated by the spline for i in range(0, t.numOfObjects): current_ws_obj_pos = FBVector3d() t.selected_obj[i].GetVector(current_ws_obj_pos, FBModelTransformationType.kModelTranslation, True) transformed_ws_obj_pos = FBVector4d() FBVectorMatrixMult(transformed_ws_obj_pos, rotationAngleMx, FBVector4d(current_ws_obj_pos[0] - pre_strafe_mover_end_pos[0], current_ws_obj_pos[1] - pre_strafe_mover_end_pos[1], current_ws_obj_pos[2] - pre_strafe_mover_end_pos[2], 0.0)) final_offset_ws_pos = transformed_ws_obj_pos - FBVector4d(current_ws_obj_pos[0], current_ws_obj_pos[1], current_ws_obj_pos[2], 0.0) # finally set prop values yaw_prop = t.pathConstraint[i].PropertyList.Find("Yaw") if yaw_prop: existing_data = yaw_prop.Data # update key on last frame to be same as first yaw_prop.GetAnimationNode().FCurve.Keys[-1].Value = yaw_prop.GetAnimationNode().FCurve.Keys[ 0].Value - t.zonalAdvanced_offsetAngleNumBox01.value() translation_prop = t.pathConstraint[i].PropertyList.Find("Translation Offset") if translation_prop: existing_data = translation_prop.Data # update keys on last frame translation_prop.GetAnimationNode().Nodes[0].FCurve.Keys[-1].Value = final_offset_ws_pos[0] translation_prop.GetAnimationNode().Nodes[1].FCurve.Keys[-1].Value = final_offset_ws_pos[1] translation_prop.GetAnimationNode().Nodes[2].FCurve.Keys[-1].Value = final_offset_ws_pos[2] ################################################################################### # Enable Zonal Helper UI ################################################################################### t.zonalAdvanced_keyHelperBtn.setEnabled(True) ################################################################################### # update UI ################################################################################### # turn expressions back on # ToggleDevices() t.turn_deleteMotionWarpBtn.setEnabled(True) t.spline_deleteMotionWarpBtn.setEnabled(True) t.zonalStart_deleteMotionWarpBtn.setEnabled(True) t.zonalStop_deleteMotionWarpBtn.setEnabled(True) t.zonalAdvanced_deleteMotionWarpBtn.setEnabled(True) # prevent user from altering Mode if t.tabBox.currentIndex() == 0: if t.turnTabBox.currentIndex() == 1: t.manualAVTab.setEnabled(False) t.autoAVTab.setEnabled(False) elif t.turnTabBox.currentIndex() == 2: t.manualTurnTab.setEnabled(False) t.autoAVTab.setEnabled(False) else: t.manualTurnTab.setEnabled(False) t.manualAVTab.setEnabled(False) t.splineTab.setEnabled(False) t.zonalStartTab.setEnabled(False) t.zonalStopTab.setEnabled(False) t.zonalAdvancedtab.setEnabled(False) elif t.tabBox.currentIndex() == 1: t.turnTab.setEnabled(False) t.zonalStartTab.setEnabled(False) t.zonalStopTab.setEnabled(False) t.zonalAdvancedtab.setEnabled(False) elif t.tabBox.currentIndex() == 2: t.turnTab.setEnabled(False) t.splineTab.setEnabled(False) t.zonalStopTab.setEnabled(False) t.zonalAdvancedtab.setEnabled(False) elif t.tabBox.currentIndex() == 3: t.turnTab.setEnabled(False) t.splineTab.setEnabled(False) t.zonalStartTab.setEnabled(False) t.zonalAdvancedtab.setEnabled(False) else: t.turnTab.setEnabled(False) t.splineTab.setEnabled(False) t.zonalStartTab.setEnabled(False) t.zonalStopTab.setEnabled(False) if t.numOfObjects == 1: t.turn_applyMotionWarpBtn.setText(QtGui.QApplication.translate("MotionWarp", "Plot MotionWarp to " + str( t.numOfObjects) + " objects", None, QtGui.QApplication.UnicodeUTF8)) t.spline_applyMotionWarpBtn.setText(QtGui.QApplication.translate("MotionWarp", "Plot MotionWarp to " + str( t.numOfObjects) + " objects", None, QtGui.QApplication.UnicodeUTF8)) t.zonalStart_applyMotionWarpBtn.setText(QtGui.QApplication.translate("MotionWarp", "Plot MotionWarp to " + str( t.numOfObjects) + " objects", None, QtGui.QApplication.UnicodeUTF8)) t.zonalStop_applyMotionWarpBtn.setText(QtGui.QApplication.translate("MotionWarp", "Plot MotionWarp to " + str( t.numOfObjects) + " objects", None, QtGui.QApplication.UnicodeUTF8)) t.zonalAdvanced_applyMotionWarpBtn.setText(QtGui.QApplication.translate("MotionWarp", "Plot MotionWarp to " + str( t.numOfObjects) + " objects", None, QtGui.QApplication.UnicodeUTF8)) else: t.turn_applyMotionWarpBtn.setText(QtGui.QApplication.translate("MotionWarp", "Plot MotionWarp on " + str( t.numOfObjects) + " objects", None, QtGui.QApplication.UnicodeUTF8)) t.spline_applyMotionWarpBtn.setText(QtGui.QApplication.translate("MotionWarp", "Plot MotionWarp on " + str( t.numOfObjects) + " objects", None, QtGui.QApplication.UnicodeUTF8)) t.zonalStart_applyMotionWarpBtn.setText(QtGui.QApplication.translate("MotionWarp", "Plot MotionWarp on " + str( t.numOfObjects) + " objects", None, QtGui.QApplication.UnicodeUTF8)) t.zonalStop_applyMotionWarpBtn.setText(QtGui.QApplication.translate("MotionWarp", "Plot MotionWarp on " + str( t.numOfObjects) + " objects", None, QtGui.QApplication.UnicodeUTF8)) t.zonalAdvanced_applyMotionWarpBtn.setText(QtGui.QApplication.translate("MotionWarp", "Plot MotionWarp on " + str( t.numOfObjects) + " objects", None, QtGui.QApplication.UnicodeUTF8)) ################################################################################### # restore user timeslider ################################################################################### FBPlayerControl().Goto(FBTime(0, 0, 0, t.current_frame)) t.pinned = True else: ######################################## # Apply Motionwarp! ######################################## # if user has selected character mode then simply plot current character if t.plotSettings_comboBox.currentIndex() == 0: current_character = FBApplication().CurrentCharacter plotToSkeleton(current_character) # else plot each individual selected node else: clearSelection() for j in range(0, t.numOfObjects): # plot selected object t.selected_obj[j].Selected = True FBSystem().CurrentTake.PlotTakeOnSelected(plotOptions()) # eval scene following plot FBSystem().Scene.Evaluate() # if we were in a zonal mode store the settings to custom properties if t.tabBox.currentIndex() == 1 or t.tabBox.currentIndex() == 2 or t.tabBox.currentIndex() == 3: storeZonalValues() if hasattr(t, 'zonal'): closeZonalHelperWindow() # and we're done... cleanupAndReset() hideProgressBar() def hideProgressBar(): t.progress = 100 t.progressBar.setValue(t.progress) t.progressBar.setStyleSheet("QProgressBar::chunk{background-color:rgb(255, 171, 0);}") time.sleep(0.5) t.progressBar.hide() t.windowSizeY -= 30 t.resize(t.windowSizeX, t.windowSizeY) t.setMinimumSize(QtCore.QSize(t.windowSizeX, t.windowSizeY)) t.setMaximumSize(QtCore.QSize(t.windowSizeX, t.windowSizeY)) t.progress = 0 t.progressBar.setValue(t.progress) def plotOptions(): plot_options = FBPlotOptions() plot_options.PlotAllTakes = False plot_options.PlotOnFrame = True plot_options.PlotPeriod = FBTime(0, 0, 0, 1) plot_options.RotationFilterToApply = FBRotationFilter.kFBRotationFilterUnroll plot_options.UseConstantKeyReducer = False plot_options.ConstantKeyReducerKeepOneKey = True plot_options.PlotTranslationOnRootOnly = False return plot_options def RotateVectorRadians(vector, radians): ca = math.cos(radians) sa = math.sin(radians) return FBVector3d(ca * vector[0] - sa * vector[1], sa * vector[0] + ca * vector[1], vector[2]) #################################################################### # Zonal Methods #################################################################### def PhaseToKeyFrame(phase): frame = t.take_start_frame + ((t.take_end_frame - t.take_start_frame) * phase) return frame def KeyFrameToPhase(keyframe): phase = float(keyframe - t.take_start_frame) / float(t.take_end_frame - t.take_start_frame) return phase #################################################################### # Zonal UI callbacks #################################################################### def ScaleFCurveTime(pFCurve, pTimePivot, pScaleValue, offset): lPivotDouble = pTimePivot.GetSecondDouble() lTime = FBTime(0) for lKey in pFCurve.Keys: lTime.SetSecondDouble(lPivotDouble + (lKey.Time.GetSecondDouble() - lPivotDouble) * pScaleValue) lTime += FBTime(0, 0, 0, int(offset)) lKey.Time = lTime def Scale(pAnimationNode, pTimePivot, pScaleValue, offset): if len(pAnimationNode.Nodes) == 0: ScaleFCurveTime(pAnimationNode.FCurve, pTimePivot, pScaleValue, offset) else: for lNode in pAnimationNode.Nodes: Scale(lNode, pTimePivot, pScaleValue, offset) def zonalSliderUpdated(control, event=''): properties = ['Yaw', 'Translation Offset/X', 'Translation Offset/Y', 'Translation Offset/Z'] # if start slider, make sure end slider isn't any lower in value if control.StartSlider: if t.zonal.startSlider[control.Id].Value >= t.zonal.endSlider[control.Id].Value: t.zonal.startSlider[control.Id].Value = t.zonal.endSlider[control.Id].Value - KeyFrameToPhase( t.take_start_frame + 1) # if end slider, make sure start slider isn't any higher in value else: if t.zonal.endSlider[control.Id].Value <= t.zonal.startSlider[control.Id].Value: t.zonal.endSlider[control.Id].Value = t.zonal.startSlider[control.Id].Value + KeyFrameToPhase( t.take_start_frame + 1) # calculate new times for yaw and translation offset based on slider positions new_start_frame = int(PhaseToKeyFrame(t.zonal.startSlider[control.Id].Value)) new_end_frame = int(PhaseToKeyFrame(t.zonal.endSlider[control.Id].Value)) old_start_frame = int(t.zonal.startSlider[control.Id].range_min) old_end_frame = int(t.zonal.endSlider[control.Id].range_max) diff_start_frame = int(new_start_frame - old_start_frame) # make sure we're not setting invalid times by making end frame always more than start if new_end_frame <= new_start_frame: new_end_frame += 1 # new desired slider range desired_range = new_end_frame - new_start_frame # existing range existing_range = old_end_frame - old_start_frame # new scale ratio scale_ratio = float(desired_range) / existing_range # scale all keys in fcurve for each property on constraint for property in properties: animation_node = FindPropertyAnimationNode(property, t.pathConstraint[control.Id]) Scale(animation_node, FBTime(0, 0, 0, old_start_frame), scale_ratio, diff_start_frame) ''' DEBUG for key in animation_node.FCurve.Keys: print "key.LeftTangentWeight is " + str(key.LeftTangentWeight) print "key.RightTangentWeight is " + str(key.RightTangentWeight) print "key.Interpolation is " + str(key.Interpolation) print "key.LeftDerivative is " + str(key.LeftDerivative) print "key.RightDerivative is " + str(key.RightDerivative) print "key.TangentConstantMode is " + str(key.TangentConstantMode) print "key.TangentMode is " + str(key.TangentMode) ''' # update edit boxes t.zonal.startEdit[control.Id].Value = new_start_frame t.zonal.endEdit[control.Id].Value = new_end_frame t.zonal.endSlider[control.Id].range_max = new_end_frame t.zonal.startSlider[control.Id].range_min = new_start_frame def zonalEditUpdated(control, event=''): if control.StartEdit: if t.zonal.startEdit[control.Id].Value >= t.zonal.endEdit[control.Id].Value: t.zonal.startEdit[control.Id].Value = t.zonal.endEdit[control.Id].Value - 1 t.zonal.startSlider[control.Id].Value = KeyFrameToPhase(control.Value) zonalSliderUpdated(t.zonal.startSlider[control.Id]) else: if t.zonal.endEdit[control.Id].Value <= t.zonal.startEdit[control.Id].Value: t.zonal.endEdit[control.Id].Value = t.zonal.startEdit[control.Id].Value + 1 t.zonal.endSlider[control.Id].Value = KeyFrameToPhase(control.Value) zonalSliderUpdated(t.zonal.endSlider[control.Id]) def zonalEditButtonClicked(control, event): current_keyframe = FBSystem().LocalTime.GetFrame() if control.StartEditButton: t.zonal.startSlider[control.Id].Value = KeyFrameToPhase(current_keyframe) # force slider callback zonalSliderUpdated(t.zonal.startSlider[control.Id]) t.zonal.startEdit[control.Id].Value = current_keyframe else: t.zonal.endSlider[control.Id].Value = KeyFrameToPhase(current_keyframe) # force slider callback zonalSliderUpdated(t.zonal.endSlider[control.Id]) t.zonal.endEdit[control.Id].Value = current_keyframe def fcurveButtonClicked(control, event): # select pathconstraint clearSelection() t.pathConstraint[control.Id].Selected = True # add constraint offset animation nodes yaw_prop = t.pathConstraint[control.Id].PropertyList.Find("Yaw") if yaw_prop: # select custom property yaw_prop.SetFocus(True) translation_prop = t.pathConstraint[control.Id].PropertyList.Find("Translation Offset") if translation_prop: # select custom property translation_prop.SetFocus(True) # close zonal key helper window ( to avoid sync issues ) if t.tabBox.currentIndex() == 2 or t.tabBox.currentIndex() == 3: if hasattr(t, 'zonal'): closeZonalHelperWindow() ShowToolByName("FCurve") def zonalRestoreButtonClicked(control, event): restoreZonalValues() # update UI for i in range(len(t.zonal.startSlider)): new_start_frame = t.zonal.startSlider[i].range_min new_end_frame = t.zonal.endSlider[i].range_max new_start_phase = KeyFrameToPhase(new_start_frame) new_end_phase = KeyFrameToPhase(new_end_frame) t.zonal.startEdit[i].Value = new_start_frame t.zonal.endEdit[i].Value = new_end_frame t.zonal.startSlider[i].Value = new_start_phase t.zonal.endSlider[i].Value = new_end_phase #################################################################### # Zonal UI definition #################################################################### def PopulateZonalUI(zonal): t.zonal.startSlider = [] t.zonal.startEdit = [] t.zonal.startEditButton = [] t.zonal.endSlider = [] t.zonal.endEdit = [] t.zonal.endEditButton = [] t.zonal.fcurveButton = [] ui_x_offset = 10 ui_y_offset = 10 slider_height = 15 slider_width = 275 phase_box_width = 30 border_offset = 0 key_button_width = 15 fcurve_button_width = 25 ui_x_width = slider_width + phase_box_width + key_button_width + 8 ui_y_height = 40 object_counter = 0 for object in t.selected_obj_name: start_frame = t.take_start_frame end_frame = t.take_end_frame # fetch any exisiting values in case window has been in previous use # use yaw property as start/end guide though we could use any other properties yaw_prop = t.pathConstraint[object_counter].PropertyList.Find("Yaw") if yaw_prop: start_frame = yaw_prop.GetAnimationNode().FCurve.Keys[0].Time.GetFrame() end_frame = yaw_prop.GetAnimationNode().FCurve.Keys[-1].Time.GetFrame() ########################################################################################## # Border ########################################################################################## object_counter_str = str(object_counter) x = FBAddRegionParam(ui_x_offset, FBAttachType.kFBAttachNone, "") y = FBAddRegionParam(ui_y_offset, FBAttachType.kFBAttachNone, "") w = FBAddRegionParam(ui_x_width, FBAttachType.kFBAttachNone, "") h = FBAddRegionParam(slider_height * 3 + 10, FBAttachType.kFBAttachNone, "") t.zonal.AddRegion("object_border" + object_counter_str, object, x, y, w, h) t.zonal.SetBorder("object_border" + object_counter_str, FBBorderStyle.kFBEmbossBorder, True, True, 2, 0, 90.0, 0) border_offset = ui_y_offset ########################################################################################## # Start Slider ########################################################################################## # add slider for start phase ui_y_offset += slider_height x = FBAddRegionParam(ui_x_offset + 5, FBAttachType.kFBAttachTop, "") y = FBAddRegionParam(ui_y_offset, FBAttachType.kFBAttachNone, "") w = FBAddRegionParam(slider_width, FBAttachType.kFBAttachNone, "") h = FBAddRegionParam(slider_height, FBAttachType.kFBAttachNone, "") t.zonal.AddRegion("start_slider" + object_counter_str, "start_slider" + object_counter_str, x, y, w, h) t.zonal.startSlider.append(FBSlider()) # add id to slider t.zonal.startSlider[object_counter].Id = object_counter t.zonal.startSlider[object_counter].StartSlider = True t.zonal.startSlider[object_counter].Value = KeyFrameToPhase(start_frame) t.zonal.startSlider[object_counter].range_min = start_frame t.zonal.startSlider[object_counter].ReadOnly = False t.zonal.startSlider[object_counter].Enabled = True t.zonal.startSlider[object_counter].Orientation = FBOrientation.kFBHorizontal t.zonal.startSlider[object_counter].OnTransaction.Add(zonalSliderUpdated) t.zonal.SetControl("start_slider" + object_counter_str, t.zonal.startSlider[object_counter]) ########################################################################################## # Start Edit ########################################################################################## # add edit box x = FBAddRegionParam(slider_width + 10, FBAttachType.kFBAttachTop, "") y = FBAddRegionParam(ui_y_offset, FBAttachType.kFBAttachNone, "") w = FBAddRegionParam(phase_box_width, FBAttachType.kFBAttachNone, "") h = FBAddRegionParam(slider_height, FBAttachType.kFBAttachNone, "") t.zonal.AddRegion("start_edit" + object_counter_str, "start_edit" + object_counter_str, x, y, w, h) t.zonal.startEdit.append(FBEditNumber()) t.zonal.startEdit[object_counter].Id = object_counter t.zonal.startEdit[object_counter].StartEdit = True t.zonal.startEdit[object_counter].Visible = True t.zonal.startEdit[object_counter].ReadOnly = False t.zonal.startEdit[object_counter].Enabled = True t.zonal.startEdit[object_counter].Precision = 0 t.zonal.startEdit[object_counter].Min = t.take_start_frame t.zonal.startEdit[object_counter].Max = t.take_end_frame t.zonal.startEdit[object_counter].Value = start_frame t.zonal.SetControl("start_edit" + object_counter_str, t.zonal.startEdit[object_counter]) t.zonal.startEdit[object_counter].OnChange.Add(zonalEditUpdated) ########################################################################################## # Start Edit Button ########################################################################################## # add edit box x = FBAddRegionParam(slider_width + 12 + phase_box_width, FBAttachType.kFBAttachTop, "") y = FBAddRegionParam(ui_y_offset, FBAttachType.kFBAttachNone, "") w = FBAddRegionParam(key_button_width, FBAttachType.kFBAttachNone, "") h = FBAddRegionParam(slider_height, FBAttachType.kFBAttachNone, "") t.zonal.AddRegion("start_button" + object_counter_str, "start_button" + object_counter_str, x, y, w, h) t.zonal.startEditButton.append(FBButton()) t.zonal.startEditButton[object_counter].Id = object_counter t.zonal.startEditButton[object_counter].StartEditButton = True t.zonal.startEditButton[object_counter].Visible = True t.zonal.startEditButton[object_counter].ReadOnly = False t.zonal.startEditButton[object_counter].Enabled = True t.zonal.startEditButton[object_counter].Caption = '<' t.zonal.startEditButton[object_counter].Hint = 'Set keyframe value to be the same as timeslider.' t.zonal.SetControl("start_button" + object_counter_str, t.zonal.startEditButton[object_counter]) t.zonal.startEditButton[object_counter].OnClick.Add(zonalEditButtonClicked) ########################################################################################## # End Slider ########################################################################################## # add slider for end phase ui_y_offset += slider_height x = FBAddRegionParam(ui_x_offset + 5, FBAttachType.kFBAttachTop, "") y = FBAddRegionParam(ui_y_offset, FBAttachType.kFBAttachNone, "") w = FBAddRegionParam(slider_width, FBAttachType.kFBAttachNone, "") h = FBAddRegionParam(slider_height, FBAttachType.kFBAttachNone, "") t.zonal.AddRegion("end_slider" + object_counter_str, "end_slider" + object_counter_str, x, y, w, h) t.zonal.endSlider.append(FBSlider()) t.zonal.endSlider[object_counter].Id = object_counter t.zonal.endSlider[object_counter].StartSlider = False t.zonal.endSlider[object_counter].Value = KeyFrameToPhase(end_frame) t.zonal.endSlider[object_counter].range_max = end_frame t.zonal.endSlider[object_counter].ReadOnly = False t.zonal.endSlider[object_counter].Enabled = True t.zonal.endSlider[object_counter].Orientation = FBOrientation.kFBHorizontal t.zonal.endSlider[object_counter].OnTransaction.Add(zonalSliderUpdated) t.zonal.SetControl("end_slider" + object_counter_str, t.zonal.endSlider[object_counter]) ########################################################################################## # End Edit ########################################################################################## # add edit box x = FBAddRegionParam(slider_width + 10, FBAttachType.kFBAttachTop, "") y = FBAddRegionParam(ui_y_offset, FBAttachType.kFBAttachNone, "") w = FBAddRegionParam(phase_box_width, FBAttachType.kFBAttachNone, "") h = FBAddRegionParam(slider_height, FBAttachType.kFBAttachNone, "") t.zonal.AddRegion("end_edit" + object_counter_str, "end_edit" + object_counter_str, x, y, w, h) t.zonal.endEdit.append(FBEditNumber()) t.zonal.endEdit[object_counter].Id = object_counter t.zonal.endEdit[object_counter].StartEdit = False t.zonal.endEdit[object_counter].Visible = True t.zonal.endEdit[object_counter].ReadOnly = False t.zonal.endEdit[object_counter].Enabled = True t.zonal.endEdit[object_counter].Value = end_frame t.zonal.endEdit[object_counter].Precision = 0 t.zonal.endEdit[object_counter].Min = t.take_start_frame t.zonal.endEdit[object_counter].Max = t.take_end_frame t.zonal.endEdit[object_counter].Value = PhaseToKeyFrame(t.zonal.endSlider[object_counter].Value) t.zonal.SetControl("end_edit" + object_counter_str, t.zonal.endEdit[object_counter]) t.zonal.endEdit[object_counter].OnChange.Add(zonalEditUpdated) ########################################################################################## # End Edit Button ########################################################################################## # add edit box x = FBAddRegionParam(slider_width + 12 + phase_box_width, FBAttachType.kFBAttachTop, "") y = FBAddRegionParam(ui_y_offset, FBAttachType.kFBAttachNone, "") w = FBAddRegionParam(key_button_width, FBAttachType.kFBAttachNone, "") h = FBAddRegionParam(slider_height, FBAttachType.kFBAttachNone, "") t.zonal.AddRegion("end_button" + object_counter_str, "end_button" + object_counter_str, x, y, w, h) t.zonal.endEditButton.append(FBButton()) t.zonal.endEditButton[object_counter].Id = object_counter t.zonal.endEditButton[object_counter].StartEditButton = False t.zonal.endEditButton[object_counter].Visible = True t.zonal.endEditButton[object_counter].ReadOnly = False t.zonal.endEditButton[object_counter].Enabled = True t.zonal.endEditButton[object_counter].Caption = '<' t.zonal.endEditButton[object_counter].Hint = 'Set keyframe value to be the same as timeslider.' t.zonal.SetControl("end_button" + object_counter_str, t.zonal.endEditButton[object_counter]) t.zonal.endEditButton[object_counter].OnClick.Add(zonalEditButtonClicked) ########################################################################################## # Fcurve Button ########################################################################################## # add edit box x = FBAddRegionParam(slider_width + 12 + phase_box_width + 28, FBAttachType.kFBAttachTop, "") y = FBAddRegionParam(border_offset, FBAttachType.kFBAttachNone, "") w = FBAddRegionParam(fcurve_button_width, FBAttachType.kFBAttachNone, "") h = FBAddRegionParam(50, FBAttachType.kFBAttachNone, "") t.zonal.AddRegion("fcurve_button" + object_counter_str, "fcurve_button" + object_counter_str, x, y, w, h) t.zonal.fcurveButton.append(FBButton()) t.zonal.fcurveButton[object_counter].Id = object_counter t.zonal.fcurveButton[object_counter].Visible = True t.zonal.fcurveButton[object_counter].ReadOnly = False t.zonal.fcurveButton[object_counter].Enabled = True t.zonal.fcurveButton[object_counter].Caption = '~' t.zonal.fcurveButton[ object_counter].Hint = 'Open curve editor window for finer control with relevant properties.' t.zonal.SetControl("fcurve_button" + object_counter_str, t.zonal.fcurveButton[object_counter]) t.zonal.fcurveButton[object_counter].OnClick.Add(fcurveButtonClicked) ########################################################################################## # Next object in UI loop ########################################################################################## ui_y_offset += slider_height * 2 + 10 object_counter += 1 ########################################################################################## # Restore Button ########################################################################################## ui_y_offset -= 5 x = FBAddRegionParam(ui_x_offset, FBAttachType.kFBAttachTop, "") y = FBAddRegionParam(ui_y_offset, FBAttachType.kFBAttachNone, "") w = FBAddRegionParam(ui_x_width, FBAttachType.kFBAttachNone, "") h = FBAddRegionParam(slider_height + 5, FBAttachType.kFBAttachNone, "") t.zonal.AddRegion("restore_button", "restore_button", x, y, w, h) t.zonal.restoreButton = FBButton() t.zonal.SetControl("restore_button", t.zonal.restoreButton) t.zonal.restoreButton.Visible = True save_file_exists = DoesSaveFileExist() if save_file_exists: t.zonal.restoreButton.ReadOnly = False t.zonal.restoreButton.Enabled = True t.zonal.restoreButton.Caption = 'Restore previous helper settings' else: t.zonal.restoreButton.ReadOnly = False t.zonal.restoreButton.Enabled = False t.zonal.restoreButton.Caption = 'Previous helper settings file not found.' t.zonal.restoreButton.Hint = 'Min/Max keytimes will be restored from previous tool use. Previous settings are saved in ' + tempfile.gettempdir() + '\\motionwarp_save_file.txt' t.zonal.restoreButton.Style = FBButtonStyle.kFBPushButton t.zonal.restoreButton.Look = FBButtonLook.kFBLookNormal t.zonal.restoreButton.OnClick.Add(zonalRestoreButtonClicked) ########################################################################################## # Calculate Tool Window Size and Draw ########################################################################################## t.zonal.StartSizeX = ui_x_width + ui_x_offset + fcurve_button_width + 28 t.zonal.StartSizeY = ui_y_offset + 60 ShowTool(zonal) def Run(): global t t = MotionWarp() t.setWindowTitle('Motion Warp ' + version) setupFunctionality(t) MotionWarp = Run()