Samples/PointCache/CharacterPointCache.py

# Copyright 2011 Autodesk, Inc.  All rights reserved.
# Use of this software is subject to the terms of the Autodesk license agreement
# provided at the time of installation or download, or which otherwise accompanies
# this software in either electronic or hard copy form.
#
# Script description:
# Create a tool to demonstrate the Character based point cache creation workflow.
#
# Topic: FBPointCacheManager
#
import os

from pyfbsdk import *
from pyfbsdk_additions import *


class PointCache():
    def __init__(self):
        self.lSystem = FBSystem()
        self.lScene = FBSystem().Scene
        self.lSysOnUIIdle = self.lSystem.OnUIIdle
        self.lSysStory = FBStory()
        self.lSysPlayer = FBPlayerControl()
        self.lSysPcMgr = FBPointCacheManager()

        self.SelectedList      = []
        self.CacheStopTime      = self.lSysPlayer.ZoomWindowStop
        self.DuplicatedModel    = False
        self.DisableEvaluationAfterCache = True
        self.EnableInPlaceCacheDeformer = True
        self.getDefaultPath()
        self.TabTypes = ('Characters', 'Story Tracks')


    def getDefaultPath(self):
        """
        get default path from loaded file. If path is None, use
        the current working directory
        """
        if not FBApplication().FBXFileName:
            self.DefaultPath = os.getcwd()
        else:
            self.DefaultPath = os.path.dirname(FBApplication().FBXFileName)

    def resetButtons(self):
        """
        Restore the default state of the cache and deformer buttons
        """
        self.bCache.State = 0
        self.bDeformer.State = 0


    def update_EditInfo(self, text):
        self.Edit_Info.Caption = repr(text)


    def refresh_All(self):
        """
        Refresh gui elements. Have GUI elements retreive values from variables
        """
        self.refresh_CharacterList()
        self.refresh_StoryTrackList()
        self.refresh_DefaultPath()
        self.update_EditInfo("Refresh Complete")


    def refresh_CharacterList(self):
        """
        Refresh character list
        """
        self.CharacterListWidget.Items.removeAll()
        for lCh in self.lScene.Characters:
            self.CharacterListWidget.Items.append(lCh.LongName)


    def refresh_StoryTrackList(self):
        """
        Refresh story track list, limited to character tracks for simplicity
        """
        self.StoryTrackListWidget.Items.removeAll()
        for lTrack in self.lSysStory.RootFolder.Tracks:
            if lTrack.Type == FBStoryTrackType.kFBStoryTrackCharacter:
                self.StoryTrackListWidget.Items.append("%s (%s)" %(lTrack.Name,lTrack.Character.LongName))


    def refresh_DefaultPath(self):
        """
        Refresh the default cache save to path
        """
        self.Edit_DefaultPath.Text = self.DefaultPath


    def getSelectedList(self):
        """
        collect selected from active tab
        """
        self.SelectedList = []

        currentTab = self.tab.TabPanel.Items[self.tab.TabPanel.ItemIndex]
        if currentTab == 'Characters':
            for lCharacter in self.lScene.Characters:
                lName = lCharacter.LongName
                for lItemIndex in range(len(self.CharacterListWidget.Items)):
                    if self.CharacterListWidget.IsSelected(lItemIndex) and self.CharacterListWidget.Items[lItemIndex] == lName:
                        self.SelectedList.append(lCharacter)
        else:
            for lTrack in self.lSysStory.RootFolder.Tracks:
                if lTrack.Type == FBStoryTrackType.kFBStoryTrackCharacter:
                    lName = lTrack.Character.LongName
                    for lIndex, lItem in enumerate(self.StoryTrackListWidget.Items):
                        if self.StoryTrackListWidget.IsSelected(lIndex) and lTrack.Character.LongName == lName:
                            self.SelectedList.append(lTrack.Character)


    def activeCharacterEvaluation(self, pCharacterList, pEnable):
        """
        function to enable/disable the character evaluation (control rig, or story track)
        """
        for pCharacter in pCharacterList:
            pCharacter.Active = pEnable                 # Turn on/off character solving
            for lTrack in self.lSysStory.RootFolder.Tracks:
                if lTrack.Type == FBStoryTrackType.kFBStoryTrackCharacter and lTrack.Character == pCharacter:
                    lTrack.Mute = (not pEnable)         # Turn on/off story character track


    def activeCharacterInPlaceCacheDeformer(self, pEnable):
        """
        Active Character Skin Model In place cache deformer
        """
        self.getSelectedList()
        if len(self.SelectedList) == 0:
            self.update_EditInfo( "Nothing selected for caching!")
            return False

        lSkinModelList = FBModelList()
        for lCh in self.SelectedList:
            print lCh.LongName
            lCh.GetSkinModelList(lSkinModelList)

        for lSkinModel in lSkinModelList:
            self.update_EditInfo(lSkinModel.LongName)
            lSkinModel.PointCacheDeformable = pEnable

        # In Place Cache Create with the first frame's transform as reference
        self.lSysPlayer.GotoStart()
        self.activeCharacterEvaluation(self.SelectedList, (not pEnable));


    def recordIdleCallback(self, pObject, pEventName):
        """
        Record Idle Callback to control the record period
        """
        if self.lSysPlayer.IsPlaying:
            if self.lSystem.LocalTime.GetSecondDouble() >= self.CacheStopTime.GetSecondDouble() :
                self.lSysOnUIIdle.Remove(self.recordIdleCallback)
                self.lSysPlayer.GotoStart()
                if self.DisableEvaluationAfterCache == True:
                    self.activeCharacterEvaluation(self.SelectedList, False)


    def configurePointCacheManager(self):
        """
        Configure the Point Cache Manager
        """
        self.lSysPcMgr.ApplyGlobalTransform = True
        self.lSysPcMgr.AlwaysAskForPath = False
        self.lSysPcMgr.DefaultPath = self.DefaultPath



    def list_OnChange(self, pList, event):
        """
        UI Callback
        """
        for lItemIndex in range(len(pList.Items)):
            if pList.IsSelected(lItemIndex):
                self.update_EditInfo(pList.Items[lItemIndex] + " has been selected!")


    def refreshAll_OnClick(self,control=None, event=None):
        """
        UI Callback
        """
        self.refresh_All()


    def duplicateCheckBox_OnClick(self, pCheckBox, event):
        """
        UI Callback
        """
        self.DuplicatedModel = (pCheckBox.State != 0)
        self.update_EditInfo(self.DuplicatedModel)


    def disableEvalBtn_OnClick(self, pCheckBox, event):
        """
        UI Callback
        """
        self.DisableEvaluationAfterCache = (pCheckBox.State != 0)
        self.update_EditInfo(self.DisableEvaluationAfterCache)


    def cacheStartTime_OnChange(self, pTimeCode, event):
        """
        UI Callback
        """
        self.CacheStopTime = pTimeCode.Value
        self.update_EditInfo(self.CacheStopTime)


    def defaultPathEdit_OnChange(self, pEdit, event):
        """
        UI Callback
        """
        self.DefaultPath = pEdit.Text


    def defaultPath_OnClick(self, control, event):
        """
        Path Callback
        """
        lFp = FBFolderPopup()
        lFp.Caption = "Choose save location for Point Cache files"
        lFp.Path = self.DefaultPath
        lRes = lFp.Execute()

        if lRes:
          self.DefaultPath = lFp.Path

        self.refresh_DefaultPath()


    def cache_OnClick(self, control, event):
        """
        Cache Callback
        """
        self.resetButtons()

        self.getSelectedList() #Select the Character Skin Models to reocrd.

        if len(self.SelectedList) == 0:
            self.update_EditInfo("No Character selected for caching!")
            return;

        lSkinModelList = FBModelList()
        self.update_EditInfo("Create point cache for Characters:")
        for l in self.SelectedList:
            self.update_EditInfo(l.LongName)
            l.GetSkinModelList(lSkinModelList)

        if len(lSkinModelList) == 0:
            self.update_EditInfo("No Recordable Skin Models Selected")
            return

        self.update_EditInfo("Create point cache for skin models:")
        for lSkinModel in lSkinModelList:
            self.update_EditInfo(lSkinModel.LongName)

        self.configurePointCacheManager()  # setup the point cache manager

        self.lSysPcMgr.Models.removeAll()
        for lSkinModel in lSkinModelList:
            self.update_EditInfo(lSkinModel.LongName)
            self.lSysPcMgr.Models.append(lSkinModel)  #Add model to be recorded

        self.lSysPlayer.GotoStart()
        self.lSysPlayer.Record(True, False)
        self.lScene.Evaluate()   #Important, to allow setup take effects.

        if not self.lSysPlayer.IsRecording:
            return

        if self.DuplicatedModel:
            self.lSysPcMgr.ApplyCacheOnNewModel = True
        else:
            self.update_EditInfo("Cache No Duplication")
            self.lSysPcMgr.ApplyCacheOnNewModel = False
            # Set Transformation Reference, we need to turn off the Character Evaluation first.
            self.activeCharacterEvaluation(self.SelectedList, False)
            self.lScene.Evaluate()  # Important, to allow setup take effects.
            self.lSysPcMgr.SetTransformReference()
            # Restore Character Evaluation
            self.activeCharacterEvaluation(self.SelectedList, True)
            self.lScene.Evaluate()  # Important, to allow setup take effects.

        self.lSysOnUIIdle.Add(self.recordIdleCallback)

        self.lSysPlayer.Play() # Record


    def enableCache_OnClick(self, control, event):
        """
        UI Callback
        """
        self.activeCharacterInPlaceCacheDeformer(True)


    def disableCache_OnClick(self, control, event):
        """
        UI Callback
        """
        self.activeCharacterInPlaceCacheDeformer(False)





    def populateLayout(self, mainLyt):
        """
        Layout management & callback hookup
        """
        x = FBAddRegionParam(5,FBAttachType.kFBAttachLeft,"")
        y = FBAddRegionParam(5,FBAttachType.kFBAttachTop,"")
        w = FBAddRegionParam(-5,FBAttachType.kFBAttachRight,"")
        h = FBAddRegionParam(-5,FBAttachType.kFBAttachBottom,"")
        mainLyt.AddRegion("main","main", x,y,w,h)

        grid = FBGridLayout()
        mainLyt.SetControl("main", grid)

        label = FBLabel()
        label.Caption = "Choose from a list of characters or a list of character story tracks"
        grid.AddRange(label, 0, 0, 0, 4)

        bRefresh = FBButton()
        bRefresh.Caption = "Refresh"
        bRefresh.Justify = FBTextJustify.kFBTextJustifyCenter
        grid.Add(bRefresh, 0, 4)
        bRefresh.OnClick.Add(self.refreshAll_OnClick)

        self.tab = FBTabControl()

        for name in self.TabTypes:

            l = FBVBoxLayout()
            x = FBAddRegionParam(10,FBAttachType.kFBAttachLeft,"")
            y = FBAddRegionParam(10,FBAttachType.kFBAttachTop,"")
            w = FBAddRegionParam(-10,FBAttachType.kFBAttachRight,"")
            h = FBAddRegionParam(-10,FBAttachType.kFBAttachBottom,"")
            l.AddRegion(name,'', x, y, w, h)

            # provide a scrolling list
            scroll = FBScrollBox()
            l.SetControl('test',scroll)

            lLabel = FBLabel()
            lLabel.Caption = "Choose %s(s) for operation:" % name
            l.Add(lLabel, 16)

            if name == 'Characters':
                self.CharacterListWidget = FBList()
                self.CharacterListWidget.OnChange.Add(self. list_OnChange)
                self.CharacterListWidget.Style = FBListStyle.kFBVerticalList
                self.CharacterListWidget.MultiSelect = True
                l.Add(self.CharacterListWidget, 141)
            else:
                self.StoryTrackListWidget = FBList()
                self.StoryTrackListWidget.OnChange.Add(self. list_OnChange)
                self.StoryTrackListWidget.Style = FBListStyle.kFBVerticalList
                self.StoryTrackListWidget.MultiSelect = True
                l.Add(self.StoryTrackListWidget, 141)

            self.tab.Add(name,l)

        self.tab.SetContent(0)
        self.tab.TabPanel.TabStyle = 0
        grid.AddRange(self.tab, 1, 7, 0, 4)

        lPCOptionsLayout = FBHBoxLayout()
        grid.AddRange(lPCOptionsLayout,8, 8, 0, 4)

        bDupMod = FBButton()
        bDupMod.Caption = "Duplicate Model"
        bDupMod.Style = FBButtonStyle.kFBCheckbox
        bDupMod.Justify = FBTextJustify.kFBTextJustifyCenter
        bDupMod.State = self.DuplicatedModel
        lPCOptionsLayout.Add(bDupMod,105)
        bDupMod.OnClick.Add(self.duplicateCheckBox_OnClick)

        bDeactivate = FBButton()
        bDeactivate.Caption = "Deactive Eval After Cache"
        bDeactivate.Style = FBButtonStyle.kFBCheckbox
        bDeactivate.Justify = FBTextJustify.kFBTextJustifyCenter
        bDeactivate.State = self.DisableEvaluationAfterCache
        lPCOptionsLayout.Add(bDeactivate,160)
        bDeactivate.OnClick.Add(self.disableEvalBtn_OnClick)

        lLabel = FBLabel()
        lLabel.Caption = "Cache End Time:"
        lLabel.Justify = FBTextJustify.kFBTextJustifyRight
        lPCOptionsLayout.Add(lLabel, 90)

        eCacheEndTime = FBEditTimeCode()
        eCacheEndTime.Caption = "Cache End Time"
        eCacheEndTime.Value = self.CacheStopTime
        eCacheEndTime.OnChange.Add(self.cacheStartTime_OnChange)
        lPCOptionsLayout.Add(eCacheEndTime,60)

        lPCPathLayout = FBHBoxLayout()
        grid.AddRange(lPCPathLayout,9, 9, 0, 4)
        lLabel = FBLabel()
        lLabel.Caption = "Cache file path"
        lLabel.Justify = FBTextJustify.kFBTextJustifyRight
        lPCPathLayout.Add(lLabel,75)

        self.Edit_DefaultPath = FBEdit()
        self.Edit_DefaultPath.OnChange.Add(self.defaultPathEdit_OnChange)
        lPCPathLayout.AddRelative(self.Edit_DefaultPath, 1)

        bFileDialog = FBButton()
        bFileDialog.Caption = "..."
        bFileDialog.Justify = FBTextJustify.kFBTextJustifyCenter
        lPCPathLayout.Add(bFileDialog,22)
        bFileDialog.OnClick.Add(self.defaultPath_OnClick)

        bProcessCache = FBButton()
        bProcessCache.Caption = "Process Cache"
        bProcessCache.Justify = FBTextJustify.kFBTextJustifyCenter
        grid.AddRange(bProcessCache,10, 10, 0, 4)
        bProcessCache.OnClick.Add(self.cache_OnClick)

        lLabel = FBLabel()
        lLabel.Caption = "View as:"
        lLabel.Justify = FBTextJustify.kFBTextJustifyRight
        grid.Add(lLabel,11, 0)

        # bCache is a class attribute rather than a method attribute because
        # it is accessed in another method
        self.bCache = FBButton()
        self.bCache.Caption = "Cache"
        self.bCache.Style = FBButtonStyle.kFB2States
        self.bCache.Look = FBButtonLook.kFBLookPush
        self.bCache.Justify = FBTextJustify.kFBTextJustifyCenter
        grid.AddRange (self.bCache,11, 11, 1, 2)
        self.bCache.OnClick.Add(self.enableCache_OnClick)

        # bDeformer is a class attribute rather than a method attribute because
        # it is accessed in another method
        self.bDeformer = FBButton()
        self.bDeformer.Caption = "Deformer"
        self.bDeformer.Style = FBButtonStyle.kFB2States
        self.bDeformer.Look = FBButtonLook.kFBLookPush
        self.bDeformer.Justify = FBTextJustify.kFBTextJustifyCenter
        grid.AddRange(self.bDeformer,11, 11, 3, 4)
        self.bDeformer.OnClick.Add(self.disableCache_OnClick)

        #group these buttons for better UI visuals
        group = FBButtonGroup()
        group.Add(self.bCache)
        group.Add(self.bDeformer)

        self.Edit_Info = FBLabel()
        self.Edit_Info.Caption = ''
        grid.AddRange(self.Edit_Info,12, 12, 0, 4)

        # refresh all of the gui elements in order to get relevant data
        self.refresh_All()




def createTool():
    """
    Tool creation will serve as the hub for all other controls
    """
    t = FBCreateUniqueTool("Character Based Point Cache Example")
    t.StartSizeX = 615
    t.MinSizeX = 455
    t.MinSizeY = 450
    t.MaxSizeY = 450

    pc = PointCache()

    pc.populateLayout(t)
    ShowTool(t)

createTool()