""" Description: Integrates IVR Tracks into Motion Builder Authors: Kat Bodey David Vega """ import os import struct import pyfbsdk as mobu from RS import Globals from RS.Utils import Math from RS.Utils import Creation class IVRFile(object): """ Generates an IVR Track in the scene based on the contents of an ivr file generated from RAGE """ def __init__(self, filepath=""): """ Constructor Arguments: filepath (string): path to ivr file """ self.FilePath = filepath self.TimeInRecordingArray = [] self.SpeedArray = [] self.MatrixAArray = [] self.MatrixBArray = [] self.SteerAngleArray = [] self.GasArray = [] self.BrakeArray = [] self.HandBrakeArray = [] self.CoordsArray = [] self.ValidData = False if os.path.exists(filepath): self.ValidData = self.ParseIVRFile() def ParseIVRFile(self): """ Read the contents of the IVR File """ lIvrFile = open(self.FilePath, 'rb') lIvrData = lIvrFile.read() lFrames = len(lIvrData) / 32 for frame in xrange(lFrames): # Convert the binary data to usable strings bits = frame * 32 self.TimeInRecordingArray.append(struct.unpack('L', lIvrData[0 + bits:4 + bits])[0]) self.SpeedArray.append(struct.unpack('hhh', lIvrData[4 + bits:10 + bits])) self.MatrixAArray.append(struct.unpack('bbb', lIvrData[10 + bits:13 + bits])) self.MatrixBArray.append(struct.unpack('bbb', lIvrData[13 + bits:16 + bits])) self.SteerAngleArray.append(struct.unpack('b', lIvrData[16 + bits])[0]) self.GasArray.append(struct.unpack('b', lIvrData[17 + bits])[0]) self.BrakeArray.append(struct.unpack('b', lIvrData[18 + bits])[0]) self.HandBrakeArray.append(struct.unpack('b', lIvrData[19 + bits])[0]) self.CoordsArray.append(struct.unpack('fff', lIvrData[20 + bits:32 + bits])) return True def GenerateComponents(self): """ Create the mobu.FBComponents that make up the IVR Track and set them up properly""" mobu.FBSystem().CurrentTake.SetCurrentLayer(0) lNullA = mobu.FBModelNull("Matrix_A") lNullB = mobu.FBModelNull("Translate_Null") rootNull = mobu.FBModelNull("IVR_Null") for vectorA, vectorB, position, time in \ zip(self.MatrixAArray, self.MatrixBArray, self.CoordsArray, self.TimeInRecordingArray): # Get vectors that represent the rotation # We add 0 to the vectors because MB uses 4x4 matricies and the vectors that the # GetOrthonormalMatrixVectors method return only contain x,y, and z rotationVectors = [vector + [0] for vector in Math.GetOrthonormalMatrixVectors(vectorA, vectorB)] transformVector = list(position) + [1] lMatrix = mobu.FBMatrix() lMatrix.Set(rotationVectors + [transformVector]) lNullA.SetMatrix(lMatrix) time = mobu.FBTime(0, 0, 0, int(time / 33.0)) for translationRotation in ["Translation", "Rotation"]: translationRotationNode = getattr(lNullA, translationRotation) translationRotationNode.SetAnimated(True) animationNode = translationRotationNode.GetAnimationNode() for node, data in zip(animationNode.Nodes, translationRotationNode.Data): node.FCurve.KeyAdd(time, data * (100 ** (translationRotation == "Translation"))) mobu.FBSystem().Scene.Evaluate() path = self.MakeSpline() path.Parent = lNullB self.AnimateVehicleAttributes().Parent = lNullB lNullB.Parent = rootNull lNullA.Parent = lNullB lNullB.Rotation.Data = mobu.FBVector3d(-90, 0, 0) self.SetupMatrixNull(lNullA) return lNullB def MakeSpline(self): """ Generate the 3D Curve Path """ PathName = os.path.basename(self.FilePath) return Creation.CreatePath(PathName, *[[coordinate[0] * 100, coordinate[1] * 100, coordinate[2] * 100, 0] for coordinate in self.CoordsArray]) def AnimateVehicleAttributes(self): """ Creates Vehicle Animation Properties """ null = mobu.FBCreateObject( "Browsing/Templates/Elements", "Null", "CarProperties" ) for propertyName in ['Speed', 'SteerAngle', 'Gas', 'Brake', 'HandBrake']: # Create Property and set a Key on it isSpeed = propertyName == 'Speed' newProperty = null.PropertyCreate(propertyName, [mobu.FBPropertyType.kFBPT_double, mobu.FBPropertyType.kFBPT_Vector3D] [isSpeed], ['Double', 'Vector3D'][isSpeed], True, True, None) newProperty.SetAnimated(True) newProperty.Key() # In the older method, this loop would iterate over a matrix but not use the values to add keys # Instead we just use the hard value of 16 as all Motion Builder Matricies are 4 x 4 Matricies for index in xrange(16): time = mobu.FBTime(0, 0, 0, index) animationNode = newProperty.GetAnimationNode() # The Speed property takes a Vector3D as a value , So we iterate over the xyz values to set them. # We also switch the y & z values around so the data is represented correctly in Motion Builder if isSpeed: for nullIndex, speedIndex in enumerate([0, 2, 1]): animationNode.Nodes[nullIndex].FCurve.KeyAdd(time, self.SpeedArray[nullIndex][speedIndex]) continue animationNode.FCurve.KeyAdd(time, getattr(self, "{}Array".format(propertyName))[index]) return null @staticmethod def SetupMatrixNull(null): """ Setup the Matrix Null so it follows the 3D Curve Path Arguments: null (pyfbsdk.FBModelNull): null that travels along the 3D Curve path Return: null """ translation = null.Translation.GetAnimationNode() rotation = null.Rotation.GetAnimationNode() currentTake = mobu.FBSystem().CurrentTake # push farplane back for iCamera in Globals.gCameras: if iCamera.Name == "Producer Perspective": iCamera.PropertyList.Find("Far Plane").Data = 400000 break # set end range lEndRange = translation.Nodes[0].FCurve.Keys[-1].Time.GetFrame() mobu.FBPlayerControl().LoopStop = mobu.FBTime(0, 0, 0, lEndRange) # apply gimblekill & resample filters for each in ['Gimbal Killer', 'Resample']: lGimbleFilter = mobu.FBFilterManager().CreateFilter('Gimbal Killer') if each == 'Resample': lGimbleFilter.Apply(translation, False) lGimbleFilter.Apply(rotation, False) lGimbleFilter.FBDelete() # re-orientate matrix null lCurrentLayer = currentTake.GetCurrentLayer() lLayer1 = lCurrentLayer + 1 currentTake.SetCurrentLayer(lLayer1) [rotation.Nodes[1].FCurve.KeyAdd(mobu.FBTime(0, 0, 0), value) for value in [0.0, 180.0]] rotation.Nodes[1].Selected = True currentTake.PlotTakeOnSelectedProperties(mobu.FBTime(0, 0, 0, 1)) mobu.FBSystem().Scene.Evaluate() return null @staticmethod def SceneAdjustment(null, pX, pY, pZ, propertyType="Translation"): """ Move scene to user coordinates or orientation Arguments: null (pyfbsdk.FBModelNull): null that travels along the 3D Curve path pX (int/float): translate or rotate x value pY (int/float): translate or rotate y value pZ (int/float): translate or rotate z value type (string): whether the values should be applied as translation or rotation """ if null: setattr(null, propertyType, mobu.FBVector3d(pX, pY, pZ)) else: mobu.FBMessageBox("Warning", "No Null Selected", "OK")