283 lines
11 KiB
Python
Executable File
283 lines
11 KiB
Python
Executable File
"""
|
|
Description:
|
|
Setups the Moving Cutscene hiearchy and connections
|
|
|
|
Author:
|
|
David Vega <david.vega@rockstargames.com>
|
|
"""
|
|
import pyfbsdk as mobu
|
|
|
|
import RS.Utils.Scene
|
|
|
|
from RS import Globals
|
|
from RS.Utils import Creation
|
|
from RS.Utils.Scene import Constraint
|
|
from RS.Core.Animation import Lib
|
|
|
|
|
|
def SetupMovingCutscene(driver, transferMotion=True, **drivers):
|
|
"""
|
|
Creates the moving cutscene setup based on the drivers passed
|
|
|
|
Arguments:
|
|
driver (pyfbsdk.FBModel): the model that drives the moving cutscene hiearchy, usually it is the Matrix_A model
|
|
CAMERAS: list[FBCamera, etc.]; list of cameras to parent under the CAMERA FBModelNull
|
|
transferMotion (boolean): Transfer the motion from the driver's children to the driver.
|
|
|
|
Keyword Arguments:
|
|
**drivers: {string : list[FBModel, etc],
|
|
string : {string:list[FBModel, etc.]}} ; any keyword argument is treated as the name of a
|
|
null that will be created to hold the list of models that make up the the value of that key value
|
|
pair. Key value pairs can have dictionaries to create more nulls. FBModelNulls are created based
|
|
on the lists passed and are constrained to the object they go with
|
|
|
|
Example:
|
|
SetupMovingCutscene(<FBModel instance>, CAMERAS=[<FBCamera R*Camera 1>], CHARACTERS=[<FBModelNull Character1>],
|
|
PROP={"SUITCASES" : [<FBModelNull Suitcase1>]})
|
|
|
|
#Generates
|
|
MASTER_MOVER
|
|
-CAMERAS
|
|
-R*Camera 1
|
|
-CHARACTERS
|
|
-Character 1Null
|
|
-PROP
|
|
-SUITCASES
|
|
-Suitcase1 Null
|
|
"""
|
|
frame = Globals.System.LocalTime.GetFrame()
|
|
Globals.Player.GotoStart()
|
|
|
|
constraintList = []
|
|
|
|
folderName = GenerateName(driver, "MOVING_CUTSCENE")
|
|
folder = CreateConstraintFolder(folderName)
|
|
|
|
# Create Master Mover, Zero and Cameras nulls if they aren't in the scene
|
|
|
|
zeroNull = GetNull(GenerateName(driver, "ZERO"))
|
|
masterMover = GetNull(GenerateName(driver, "MASTER_MOVER"))
|
|
|
|
# Transfer Animation from Driver to the Master Null
|
|
Lib.TransferMotion(masterMover, driver)
|
|
|
|
# Position the Master Mover and Zero Null
|
|
Lib.MatchPosition(driver, (masterMover, zeroNull))
|
|
|
|
# Clear out scaling artifacts from matching matricies
|
|
masterMover.PropertyList.Find("Lcl Scaling").Data = mobu.FBVector3d(1, 1, 1)
|
|
zeroNull.PropertyList.Find("Lcl Scaling").Data = mobu.FBVector3d(1, 1, 1)
|
|
mobu.FBSystem().Scene.Evaluate()
|
|
|
|
# Create Zero Constraint
|
|
zeroConstraintName = GenerateName(driver, "ZERO_CONSTRAINT")
|
|
zeroConstraint = filter(lambda constraint: zeroConstraintName in (constraint.Name, constraint.LongName),
|
|
Globals.Constraints)
|
|
|
|
if not zeroConstraint:
|
|
zeroConstraint = [Creation.CreateConstraint("Parent/Child", zeroConstraintName,
|
|
masterMover, zeroNull, Active=True, Lock=True)]
|
|
folder.ConnectSrc(zeroConstraint[0])
|
|
|
|
# Create Nulls
|
|
driverNulls, constraints = CreateConstraintedNulls(driver, folder=folder, transferMotion=transferMotion, **drivers)
|
|
constraintList += constraints
|
|
|
|
# Parent driver nulls
|
|
for eachNull in driverNulls:
|
|
eachNull.Parent = masterMover
|
|
eachNull.Scaling = mobu.FBVector3d(1, 1, 1)
|
|
|
|
zeroConstraint[0].Active = False
|
|
Globals.Scene.Evaluate()
|
|
|
|
# The master constraint isn't activated until after we create the other constraints
|
|
masterMoverConstraintName = GenerateName(driver, "MASTER_MOVER_CONSTRAINT").upper()
|
|
constraint = filter(lambda constraint: constraint.LongName == masterMoverConstraintName,
|
|
Globals.Constraints)
|
|
|
|
if not constraint and driver:
|
|
constraint = Creation.CreateConstraint("Parent/Child",
|
|
masterMoverConstraintName,
|
|
driver, masterMover, Active=True)
|
|
|
|
# The name of the offset attributes are the name of the source component plus Offset T/R
|
|
# However this may not always be the case, so we are just going to search if Offset T/R is in the
|
|
# name of the property to get more consistent results.
|
|
|
|
for property in constraint.PropertyList:
|
|
for tr in "TR":
|
|
if ".Offset {}".format(masterMover.Name, tr) in property.Name:
|
|
property.Data = mobu.FBVector3d(0, 0, 0)
|
|
|
|
folder.ConnectSrc(constraint)
|
|
|
|
if transferMotion:
|
|
for constraint in constraints:
|
|
constraint.Active = False
|
|
parent, child = constraint.GetSrc(0), constraint.GetDst(1)
|
|
|
|
# Ensures that the active state of the constraint is updated to False
|
|
Globals.Scene.Evaluate()
|
|
|
|
Lib.TransferMotion(parent, child)
|
|
constraint.Active = True
|
|
|
|
Globals.Player.Goto(mobu.FBTime(0, 0, 0, frame))
|
|
|
|
|
|
def GenerateName(driver, name):
|
|
"""
|
|
Generates a name with the driver's namespace ,or short name if there is no namespace, as the prefix
|
|
Arguments:
|
|
driver: FBComponent; object whose name will be used as the prefix
|
|
name: string; suffix of the new name
|
|
|
|
Return:
|
|
string
|
|
"""
|
|
return "{}_{}".format(driver.LongName.split()[0], name)
|
|
|
|
|
|
def CreateConstraintFolder(folderName):
|
|
"""
|
|
Creates a folder to hold constraints if it doesn't exist
|
|
|
|
Arguments:
|
|
folderName: string; name of the folder to create/return
|
|
|
|
Return:
|
|
FBFolder
|
|
"""
|
|
folders = filter(lambda folder: folderName in [folder.Name, folder.LongName], mobu.FBSystem().Scene.Folders)
|
|
if folders:
|
|
folder = folders[0]
|
|
else:
|
|
folder = Creation.rs_CreateFolder(folderName, "Constraints")
|
|
return folder
|
|
|
|
|
|
def GetNull(name):
|
|
"""
|
|
Find the null that matches the given name, if it doesn't exist it creates it
|
|
Arguments:
|
|
name: string; name of the null to get
|
|
|
|
Return:
|
|
FBModel or subclass of FBModel
|
|
"""
|
|
null = mobu.FBFindModelByLabelName(name)
|
|
if not null:
|
|
null = mobu.FBModelNull(name)
|
|
return null
|
|
|
|
|
|
def CreateConstraintedNulls(topParent, folder=None, transferMotion=True, **parentChildren):
|
|
"""
|
|
Constraints parentChildren to those objects
|
|
Keyword Arguments:
|
|
parentChildren: {string(Name of Parent FBModel): FBModel(), etc}; Finds the FBModel object whose name matches
|
|
the keyword argument used and parent constraints all the FBModels passed in as value. Values for the
|
|
keyword arguments can be FBModels(), a list of FBModels or a dictionary.
|
|
|
|
Example:
|
|
CreateConstraintedNulls(PARENT=FBModelNull("Child"),
|
|
LIST=[FBModelNull("Child1"), FBModelNull("Child1")],
|
|
DICTIONARY={"SUBPARENT" : FBModelNull("GranChild")})
|
|
"""
|
|
parentNulls = []
|
|
constraints = []
|
|
|
|
# Generate Top Level Folder if no folder is passed
|
|
if not folder:
|
|
folderName = GenerateName(topParent, "MOVING_CUTSCENE")
|
|
folder = CreateConstraintFolder(folderName)
|
|
|
|
# Iterate over the the driver and driven objects
|
|
for parentName, children in parentChildren.iteritems():
|
|
|
|
createConstraint = True
|
|
parentNulls.append(GetNull(parentName))
|
|
|
|
# Forces the null to be positioned on top of the top most parent
|
|
parentNulls[-1].Parent = topParent
|
|
parentNulls[-1].PropertyList.Find("Lcl Translation").Data = mobu.FBVector3d(0, 0, 0)
|
|
Globals.Scene.Evaluate()
|
|
|
|
if isinstance(children, dict):
|
|
# Get/Create folder for nested parentChildren
|
|
driverFolder = CreateConstraintFolder(parentName)
|
|
folder.ConnectSrc(driverFolder)
|
|
|
|
# Create Constraints
|
|
|
|
children, newConstraints = CreateConstraintedNulls(topParent, driverFolder, transferMotion=transferMotion,
|
|
**children)
|
|
constraints.extend(newConstraints)
|
|
createConstraint = False
|
|
|
|
elif not isinstance(children, list):
|
|
children = [children]
|
|
|
|
# Remove None values from the children list
|
|
children = filter(lambda child: child is not None, children)
|
|
|
|
for child in children:
|
|
# Parent objects instead of constraining them
|
|
if not createConstraint:
|
|
child.Parent = parentNulls[-1]
|
|
continue
|
|
|
|
# Disable constraints attached to the child object
|
|
[setattr(currentConstraint, "Active", False) for currentConstraint in
|
|
RS.Utils.Scene.GetIncomingConnections(child, [mobu.FBConstraint])]
|
|
|
|
# Ensure that the constraints active values update
|
|
Globals.Scene.Evaluate()
|
|
|
|
# Position the parent null at the position of the child component
|
|
Lib.MatchPosition(child, [parentNulls[-1]])
|
|
|
|
# Check if the constraints already exist
|
|
constraint = None
|
|
constraintName = "{}_CONSTRAINT".format(parentName)
|
|
for _constraint in Globals.Scene.Constraints:
|
|
if constraintName == _constraint.Name:
|
|
constraint = _constraint
|
|
break
|
|
|
|
# Create constraint if it is missing
|
|
if constraint is None:
|
|
constraint = Constraint.Constraint(Constraint.PARENT_CHILD, "{}_CONSTRAINT".format(parentName),
|
|
child, parentNulls[-1], Active=True, Lock=False)
|
|
constraints.append(constraint)
|
|
|
|
# Add constraint to folder
|
|
folder.ConnectSrc(constraints[-1])
|
|
|
|
return parentNulls, constraints
|
|
|
|
|
|
def QuickSetup(driver=None, masterMover=None):
|
|
""" Creates a predetermined moving cutscene setup """
|
|
|
|
if not isinstance(driver, mobu.FBModel):
|
|
driver = mobu.FBFindModelByLabelName("Matrix_A")
|
|
|
|
if not isinstance(masterMover, mobu.FBModel):
|
|
masterMover = mobu.FBFindModelByLabelName("MASTER_MOVER")
|
|
|
|
# if masterMover and driver:
|
|
drivers = {"CAMERAS": [each for each in mobu.FBSystem().Scene.Cameras[7:] if "R*" in each.Name],
|
|
"CHARACTERS": [], "PROPS": []}
|
|
|
|
# Get top modelNull for each reference
|
|
|
|
for each in mobu.FBSystem().Scene.Characters:
|
|
name = "{}_Ctrl:Reference".format(each.Name.split(":")[0])
|
|
if mobu.FBFindModelByLabelName(name):
|
|
drivers["CHARACTERS"].append(mobu.FBFindModelByLabelName(name))
|
|
|
|
# Setup Moving Cutscene
|
|
SetupMovingCutscene(driver, masterMover, **drivers)
|