# Copyright 2009 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:
# Blend shaped editor.
# Press Attach and it will grab all BlendShape Model in the scene and create an editor for them.
# Press Detach and it will release all BlendShape and clears all UI
#
# Topic: FBEventName, FBSlider
# 

from pyfbsdk import *
from pyfbsdk_additions import *

class SliderControl(VBoxLayout):
    def _sliderCallback(self,control,event):
        if event.Name == FBEventName.kFBEventChange:
            self.value.Text = "%3.2f" % self.slider.Value
            self._linkedproperty.Data=self.slider.Value
        if self.key.State==1:
            self._isCandidate=True
        elif event.Name ==FBEventName.kFBEventOnClick:
            self._onChange=True
        elif event.Name ==FBEventName.kFBEventExit:
            self._onChange=False

    def _valueCallback(self, control, event):
        self._onChange=True
        self.slider.Value = float(self.value.Text)
        self._linkedproperty.Data = float(self.value.Text)
        self.value.Text = "%3.2f" % float(self.value.Text)
        self._onChange=False
        if self.key.State==1:
            self._isCandidate=True

    def _nameCallback(self, control, event):
        print "NameChanged"

    def _keyCallback(self, control, event):
        self.Key()

    def _clearCallback(self, control, event):
        self.ClearAnimation()

    def _IdleCallback(self, control, event):
        if not self._onChange:
            self.updateView()

    def __init__(self, Aproperty):
        from pyfbsdk import FBAddRegionParam
        from pyfbsdk import FBAttachType

        VBoxLayout.__init__(self)
        self._linkedproperty = Aproperty
        self._onChange = False
        self._isCandidate = False
        self.slider = pyfbsdk.FBSlider()
        self.value = pyfbsdk.FBEdit()
        self.name = pyfbsdk.FBLabel()
        self.name.Justify = FBTextJustify.kFBTextJustifyRight
        self.key = pyfbsdk.FBButton()
        self.key.State=1
        self.key.Caption = "Key"
        self.clear = pyfbsdk.FBButton()
        self.clear.Caption = "Clear"

        if Aproperty.IsAnimatable() and Aproperty.GetDataTypeName().lower()=="shape": ##check that we have an animatable shape property
            self.slider.Min = Aproperty.GetMin()
            self.slider.Max = Aproperty.GetMax()
            self.slider.Value = Aproperty.Data
            self.value.Text = "%3.2f" % Aproperty.Data
            self.name.Caption = Aproperty.GetName()
        else:
            print "notAdouble"

        self.slider.OnChange.Add(self._sliderCallback)
        self.value.OnChange.Add(self._valueCallback)
        self.key.OnClick.Add(self._keyCallback)
        self.OnIdle.Add(self._IdleCallback)
        self.clear.OnClick.Add(self._clearCallback)

        ##add the slider view        
        self.Add(self.slider, 150, width = 35)

        ## add the button clear
        self.Add(self.clear, 25)

        ## add the button key
        self.Add(self.key, 25)

        ## add the name
        self.Add(self.name, 25)


        ##add the Value
        self.Add(self.value, 25)


    def add(self,name,content):
        self.tabpanel.Items.append(name)
        self.tabcontents.append(content)
        self.setContent(len(self.tabpanel.Items) - 1)

    def setContent(self,index):
        self.tabpanel.ItemIndex = index
        self.SetControl("mainlyt",self.tabcontents[index])

    def Key(self):
        self._linkedproperty.Key()
        self._isCandidate=False

    def Reset(self):
        self._isCandidate=True
        self.value.Text="0.0"
        self._valueCallback(None,FBEventChange())

    def ClearAnimation(self):
        self._isCandidate=True
        self._linkedproperty.GetAnimationNode().FCurve.EditClear()


    def Clear(self):
        self._linkedproperty.GetAnimationNode().KeyRemove()

    def updateView(self):
        if not self._onChange:
            precision = 0.005
            currentvalue = float(self.value.Text)
            if currentvalue < self._linkedproperty.Data-precision or currentvalue > self._linkedproperty.Data+precision:
                self.value.Text="%3.2f" %self._linkedproperty.Data
                self.slider.Value = self._linkedproperty.Data
            if not self._isCandidate and self._linkedproperty.GetAnimationNode().IsKey():
                    if self.key.State!=1:
                        self.key.State=1
            else:
                if self.key.State!=0:
                    self.key.State=0
            if self._linkedproperty.IsAnimatable() and self._linkedproperty.GetAnimationNode().KeyCount>0:
                self.key.Caption="Key *"
            else:
                self.key.Caption="Key"

def GetBlendShapeProp(aModel):
    myproplist = list()
    for prop in aModel.PropertyList:
        if prop != None and prop.IsAnimatable() and prop.GetDataTypeName().lower()=="shape":
            myproplist.append(prop)
    return myproplist

class ModelBlendShapeUI (HBoxLayout):
    def _KeyAllCallBack(self,control, event):
        for each in self._mySliderList:
            each.Key()

    def _ResetAllCallBack(self,control, event):
        for each in self._mySliderList:
            each.Reset()

    def _ClearAllCallBack(self,control, event):
        for each in self._mySliderList:
            each.ClearAnimation()

    def __init__(self, Amodel):
        from pyfbsdk import FBAddRegionParam
        from pyfbsdk import FBAttachType

        HBoxLayout.__init__(self)
        self._linkedModel = Amodel
        self._mySliderList=list()
        ## populate the propertylist
        self.bkeyall=FBButton()
        self.bkeyall.Caption="Key All"
        self.brstall=FBButton()
        self.brstall.Caption="Reset All"
        self.bclrall=FBButton()
        self.bclrall.Caption="Clear All"

        ## Creation of the buttonLayout 
        self._myButtonsLay = VBoxLayout()
        self.Add(self._myButtonsLay, 75)

        self.bkeyall.OnClick.Add(self._KeyAllCallBack)
        self._myButtonsLay.Add(self.bkeyall, 25)

        ##brstall
        self.brstall.OnClick.Add(self._ResetAllCallBack)
        self._myButtonsLay.Add(self.brstall, 25)

        ##bclrall
        self.bclrall.OnClick.Add(self._ClearAllCallBack)
        self._myButtonsLay.Add(self.bclrall, 25)

        # add all the slider ctrl
        for prop in GetBlendShapeProp(self._linkedModel):
            slider = SliderControl(prop)
            self._mySliderList.append(slider)
            self.Add(slider, 75, space = 10)



def GetBlendShapeModel():
    modelslist = list()
    for component in FBSystem().Scene.Components:
        if str(type(component)).lower().find("fbmodel")>-1:
            bblend=False
            for prop in component.PropertyList:
                if not prop==None and  prop.IsAnimatable() and prop.GetDataTypeName().lower()=="shape":
                    bblend=True
                    break
            if bblend:
                modelslist.append(component)
    return modelslist


def DetachFromBlendShape(control, event):
    global regions
    global models

    for region in regions.itervalues():
        lyt.RemoveRegion(region)
    regions = {}
    models = []

def AttachToBlendShape(control, event):
    DetachFromBlendShape(None, None)

    global models
    models = GetBlendShapeModel()

    anchor = FBAttachType.kFBAttachTop
    anchorRegion = ""
    for i, model in enumerate(models):
        lytName = model.Name
        blendShapeEditor = ModelBlendShapeUI(model)

        arrowName = "ArrowName" + str( i )
        x = FBAddRegionParam(0,FBAttachType.kFBAttachLeft,"")
        y = FBAddRegionParam(0,anchor,anchorRegion)
        w = FBAddRegionParam(2000,FBAttachType.kFBAttachNone,"")
        h = FBAddRegionParam(0,FBAttachType.kFBAttachNone,"")
        lyt.AddRegion(arrowName ,arrowName , x, y, w, h)

        b = FBArrowButton()
        regions[model] = arrowName
        lyt.SetControl(arrowName ,b)

        # important : we set the content AFTER having added the button arrow
        # to its parent layout
        b.SetContent( model.Name, blendShapeEditor, 2000, 300 )
        blendShapeEditor.Restructure(True)

        anchor = FBAttachType.kFBAttachBottom
        anchorRegion = arrowName


def SceneChanged(scene, event):
    if event.Type == FBSceneChangeType.kFBSceneChangeDetach  and \
        event.ChildComponent in models:
        lyt.RemoveRegion(regions[event.ChildComponent])
        models.remove(event.ChildComponent)


def OnToolDestroy(control,event):
    # Important: each time we run this script we need to remove
    # the SceneChanged from the Scene else they will accumulate
    FBSystem().Scene.OnChange.Remove(SceneChanged)


models = []
regions = {}

# Tool creation will serve as the hub for all other controls
tool = CreateUniqueTool("Blend Shape Editor")

tool.StartSizeX = 400
tool.StartSizeY = 200

scroll = FBScrollBox()
scroll.SetContentSize(2000,10000)

mainLyt = VBoxLayout()
x = FBAddRegionParam(0,FBAttachType.kFBAttachLeft,"")
y = FBAddRegionParam(0,FBAttachType.kFBAttachTop,"")
w = FBAddRegionParam(0,FBAttachType.kFBAttachRight,"")
h = FBAddRegionParam(0,FBAttachType.kFBAttachBottom,"")
tool.AddRegion("main","main", x, y, w, h)
tool.SetControl("main",mainLyt)

btnbar = HBoxLayout()
btn = FBButton()
btn.Caption = "Attach"
btn.OnClick.Add(AttachToBlendShape)
btnbar.Add(btn, 75)

btn = FBButton()
btn.Caption = "Detach"
btn.OnClick.Add(DetachFromBlendShape)
btnbar.Add(btn, 75)

mainLyt.Add(btnbar, 30)

mainLyt.AddRelative(scroll)

lyt = scroll.Content

# Register for scene event
FBSystem().Scene.OnChange.Add(SceneChanged)

# register when this tool is destroyed.
tool.OnUnbind.Add(OnToolDestroy)

ShowTool(tool)