Interactive/ClickBehaviorCustom.py

# (C) Copyright 2012 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.

#           module = __import__(custom)
#           instance = module.instantiate()
#           self.__myCustoms.append(instance)

# Use Ctrl-P to get into a mode where the left mouse button click on an
# object with an assigned behavior starts the behavior, the middle
# mouse button click pauses and the right mouse button click resets it.

__all__ = ['ClickBehaviorCustom', 'instantiate']

from ActionRegistry      import theActionRegistry
from MessageInterpreter  import MessageInterpreter
from RTFapi              import Event, Node, kScene
from SceneGraphUtilities import GetIntersectionData
from UserCustomization   import UserCustomBase, CustomInfo

import BehaviorIO, ModelIO

kClickKeyMappingId = 'ClickBehavior'

class ClickBehaviorCustom (UserCustomBase):
    def __init__(self):
        self.__myClickMenu = None
        self.__myClickId = None
        self.__myInterpreter = ClickBehaviorInterpreter()

    def getInterpreter(self,isInteractive):
        if isInteractive:
            return self.__myInterpreter
        return None

    def appendMenuItems(self,id,menu):
        if "Help" == id:
            self.__myClickMenu = menu
            self.__myClickId = \
                        menu.appendCheckItem(_( 'Click behaviors\tCtrl+P' ),
                                             self.__onClickedObject )
            theActionRegistry.register( 'Global',
                                        Event.BUTTON_p,
                                        ( Event.BUTTON_Control, ),
                                        True,
                                        Event.BUTTONUP,
                                        'ClickBehaviorObjectMode' )

    def enableMenuStates(self,id,enableStates):
        if "Help" == id:
            enableStates[self.__myClickId] = True

    def resetMenuStates(self, id):
        if "Help" == id:
            menuItemStates = {}
            menuItemStates[self.__myClickId] = False
            self.__myClickMenu.checkMenuItems(menuItemStates)

    # ------------------------------------------------
    def __onClickedObject( self, event ):
        isOn = bool(self.__myClickMenu.IsChecked(self.__myClickId))
        self.__myInterpreter.ClickBehaviorObjectMode( isOn )


# -----------------------------------------------------------------------

class ClickBehaviorInterpreter( MessageInterpreter ):

    def __init__( self ):
        MessageInterpreter.__init__( self )
        self.__myDisplay = None
        self.__myModels = None
        self.__myBehaviors = None
        self.__myIsClicking = False

        #   Click key mappings.
        #
        keyMappings = (
          (Event.BUTTON_LMB,  (Event.BUTTON_Control,),
           True,  Event.BUTTONDOWN, "MyClickBehaviorObjectStart"),
          (Event.BUTTON_MMB,  (Event.BUTTON_Control,),
           True,  Event.BUTTONDOWN, "MyClickBehaviorObjectStop"),
          (Event.BUTTON_RMB,  (Event.BUTTON_Control,),
           True,  Event.BUTTONDOWN, "MyClickBehaviorObjectReset"),
        )
        theActionRegistry.registerList(kClickKeyMappingId, keyMappings)


    def __getHitPath( self, event, modelRoot, display ) :
        '''
        Returns a list of nodes from the node that was clicked on,
        to the root node of the model.  Any one of these nodes
        could be associated with a light.
        '''
        x = event.getValue( Event.kNormalizedWinX )
        y = event.getValue( Event.kNormalizedWinY )

        intersector = display.createIntersector( kScene )
        intersector.intersect( x, y )
        (path,node,isp,norm,patch,dist) = GetIntersectionData( intersector,
                                                modelRoot,
                                                conditions = [lambda node: node.hasProperty( Node.kIsSelectable )],
                                                needPatchInfo=False,
                                                filterCut=False)
        return path


    def MyClickBehaviorObjectStart(self, event ):
        self.MyClickBehaviorObjectDo(event, 1)

    def MyClickBehaviorObjectStop(self, event ):
        self.MyClickBehaviorObjectDo(event, 0)

    def MyClickBehaviorObjectReset(self, event ):
        self.MyClickBehaviorObjectDo(event, -1)


    def MyClickBehaviorObjectDo(self, event, startOrStopOrReset):
        '''
        Shows all lights that are illuminating the clicked-on
        object.  If all applicable lights are already on, then
        nothing is done.
        '''
        if self.__myDisplay is None or \
            self.__myModels is None or \
            self.__myBehaviors is None:
            return False

        # Find out which node (and its parents) was clicked on
        #
        pathToClickedNode = self.__getHitPath( event, self.__myModels.root, 
                                                self.__myDisplay )
        if pathToClickedNode is not None:
            """
            print "Hit: %d" % (len(pathToClickedNode))
            for each in pathToClickedNode :
                print "  hit %s" % each.getUniqueId()
            """
            for each in pathToClickedNode:
                if self.ProcessNode(each, startOrStopOrReset):
                    break
        return True


    def ProcessNode( self, node, startOrStopOrReset ):
        id = node.getUniqueId()
        for name, behavior in self.__myBehaviors.iteritems():
            nodes = behavior.getParameterValue( 'Nodes' )
            nodesList = behavior.getParameterValue( 'NodesList' )
            if (nodes is not None and id in nodes) or \
               (nodesList is not None and id in nodesList):
                """
                print "Start behavior %s" % name
                """
                if 1 == startOrStopOrReset:
                    self.sendMessage( "BEHAVIOR_METHOD_EXECUTE",
                                      ( name, "Continue", ) )
                elif 0 == startOrStopOrReset:
                    self.sendMessage( "BEHAVIOR_METHOD_EXECUTE",
                                      ( name, "Pause", ) )
                else:
                    self.sendMessage( "BEHAVIOR_METHOD_EXECUTE",
                                      ( name, "StopAndResetToStart", ) )


    def ClickBehaviorObjectMode( self, isOn ):
        if self.__myDisplay and self.__myModels and self.__myBehaviors :
            if isOn != self.__myIsClicking:
                self.__myIsClicking = isOn
                if isOn :
                    theActionRegistry.addSetToStack(kClickKeyMappingId)
                else:
                    theActionRegistry.removeSetFromStack(kClickKeyMappingId)
            # Push key mappings to take over click
            theActionRegistry.activateSet( kClickKeyMappingId,
                                           self.__myIsClicking )


    def APPLICATION_CLOSE_SCENE( self, message ) :
        self.ClickBehaviorObjectMode( False )
        self.__myModels = None
        self.__myBehaviors = None

    def SET_DOCUMENT( self, message ) :
        self.ClickBehaviorObjectMode( False )
        ( document, ) = message.data
        self.__myModels = document.get( ModelIO.id )
        self.__myBehaviors = document.get( BehaviorIO.id )
    
    def SET_DISPLAY( self, message ):
        ( self.__myDisplay, ) = message.data


# --------------------------------------------------------------------

def instantiate():
    return ClickBehaviorCustom()


def info():
    customInfo = CustomInfo()
    customInfo.vendor = 'Autodesk'
    customInfo.version = '1.0'
    customInfo.api = '2013'
    customInfo.shortInfo = "A sample add-in to switch the mouse click's behavior."
    customInfo.longInfo = \
"""Use Ctrl-P to get into a mode where the left mouse button click on an \
object with an assigned behavior starts the behavior, the middle \
mouse button click pauses and the right mouse button click resets it.
"""
    return customInfo