Interactive/TransformCustom.py

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

__all__ = ["TransformCustom", "instantiate"]

import math

from ActionRegistry     import theActionRegistry
from awSupportApi       import Vector
from MessageInterpreter import MessageInterpreter
from MessageRegistry    import theMessageRegistry
from Modes              import SelectionMode
from RTFapi             import Event
from UserCustomization  import UserCustomBase, CustomInfo

import ModelIO


CUSTOM_TRANSFORM_ENABLE_Doc = \
[(
"""
Enable or disable custom transform feature. When enable, left mouse
button will transform the scene and right mouse button will rotate
the scene.
"""
),
[[("state"),("True to enable and False to disable")],
 ]
]

theMessageRegistry.register("CUSTOM_TRANSFORM_ENABLE",
    (bool,), 0, CUSTOM_TRANSFORM_ENABLE_Doc)


class TransformCustom(UserCustomBase):

    __kMenuId = u"Edit"


    def __init__(self):
        self.__myMenu = None
        self.__myMenuItemId = None
        self.__myInterpreter = TransformInterpreter()


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


    def appendMenuItems(self, id, menu):
        if TransformCustom.__kMenuId == id:
            self.__myMenu = menu

            menu.appendSeparator()
            self.__myMenuItemId = menu.appendCheckItem(
                _("Custom Transform"), self.OnTransform)


    def enableMenuStates(self, id, enableStates):
        if TransformCustom.__kMenuId == id:
            enableStates[self.__myMenuItemId] = True


    def resetMenuStates(self, id):
        if TransformCustom.__kMenuId == id:
            menuItemStates = {}
            menuItemStates[self.__myMenuItemId] = False
            self.__myMenu.checkMenuItems(menuItemStates)


    def OnTransform(self, event):
        isOn = bool(self.__myMenu.IsChecked(self.__myMenuItemId))
        if isOn:
            self.sendMessage("SELECT", ((), SelectionMode.kReplace))

        self.sendMessage("CUSTOM_TRANSFORM_ENABLE", (isOn,))


class TransformInterpreter(MessageInterpreter):

    __kMoveStartKeyMappingId = "MoveStart"
    __kMoveKeyMappingId = "Move"
    __kRotateStartKeyMappingId = "RotateStart"
    __kRotateKeyMappingId = "Rotate"


    def __init__(self):
        MessageInterpreter.__init__(self)

        self.__myNode = None
        self.__myDisplay = None
        self.__myRotatePivot = (0.0, 0.0, 0.0)
        self.__myLastPosition = (0.0, 0.0)
        self.__myAzimuth = 0.0

        #   Move key mappings.
        #
        keyMappings = (
          (Event.BUTTON_LMB, (), True, Event.BUTTONDOWN, "MyMoveStart"),
        )
        theActionRegistry.registerList(TransformInterpreter.__kMoveStartKeyMappingId, keyMappings)

        keyMappings = (
          (Event.BUTTON_LMB, (), False, Event.BUTTONUP, "MyMoveStop")
        , (Event.BUTTON_None, (), False, Event.MOVE, "MyMove")
        )
        theActionRegistry.registerList(TransformInterpreter.__kMoveKeyMappingId, keyMappings)

        #   Rotate key mappings.
        #
        keyMappings = (
          (Event.BUTTON_RMB, (), True, Event.BUTTONDOWN, "MyRotateStart"),
        )
        theActionRegistry.registerList(TransformInterpreter.__kRotateStartKeyMappingId, keyMappings)

        keyMappings = (
          (Event.BUTTON_RMB, (), False, Event.BUTTONUP, "MyRotateStop")
        , (Event.BUTTON_None, (), False, Event.MOVE, "MyRotate")
        )
        theActionRegistry.registerList(TransformInterpreter.__kRotateKeyMappingId, keyMappings)


    def getPosition(self, event):
        x = event.getValue(Event.kNormalizedWinX)
        y = event.getValue(Event.kNormalizedWinY)
        return x, y


    def MyMoveStart(self, event):
        if self.__myDisplay is None or \
            self.__myNode is None:
            return False
        
        self.__myLastPosition = self.getPosition(event)
        theActionRegistry.addSetToStack(TransformInterpreter.__kMoveKeyMappingId)
        theActionRegistry.activateSet(TransformInterpreter.__kMoveKeyMappingId, True)

        #   Get the view direction so we can determine the azimuth of the view.
        #
        camera = self.__myDisplay.getCamera()
        position = camera.getPosition()
        lookAt = camera.getCOI()

        viewDirection = Vector(0, 0, 0)
        viewDirection.minus(lookAt, position)
        viewDirection[2] = 0.0
        viewDirection.normalize()

        self.__myAzimuth = math.atan2(viewDirection[1], viewDirection[0])

        return True


    def MyMove(self, event):
        x, y = self.getPosition(event)

        lastX, lastY = self.__myLastPosition
        dx = x - lastX
        dy = y - lastY

        cosTheta = math.cos(self.__myAzimuth)
        sinTheta = math.sin(self.__myAzimuth)

        tx = sinTheta * dx + cosTheta * dy
        ty = -cosTheta * dx + sinTheta * dy

        #   \todo: May want to take scene/object size or view distance
        #   into consideration instead of hardcoding value for transation
        #   adjustment.
        #
        tx *= 100.0
        ty *= 100.0

        #   Apply translation.
        #
        self.sendMessage("TRANSLATE_WORLDSPACE",
            (tx, ty, 0.0, True, (self.__myNode.getUniqueId(),)),
            requestUndo=False)

        #   Good idea to apply translation to rotation pivot.
        #
        x, y, z = self.__myRotatePivot
        x += tx
        y += ty
        self.__myRotatePivot = (x, y, z)

        self.__myLastPosition = self.getPosition(event)

        return True


    def MyMoveStop(self, event):
        self.__myLastPosition = self.getPosition(event)
        theActionRegistry.removeSetFromStack(TransformInterpreter.__kMoveKeyMappingId)
        theActionRegistry.activateSet(TransformInterpreter.__kMoveKeyMappingId, False)
        return True


    def MyRotateStart(self, event):
        if self.__myDisplay is None or \
            self.__myNode is None:
            return False

        self.__myLastPosition = self.getPosition(event)
        theActionRegistry.addSetToStack(TransformInterpreter.__kRotateKeyMappingId)
        theActionRegistry.activateSet(TransformInterpreter.__kRotateKeyMappingId, True)

        return True


    def MyRotate(self, event):
        x, y = self.getPosition(event)

        lastX, lastY = self.__myLastPosition

        dx = x - lastX
        dy = y - lastY

        degrees = dx * 180.0
        radians = math.radians(degrees)

        self.sendMessage("ROTATE_WORLDSPACE",
            (radians, (0.0, 0.0, 1.0), self.__myRotatePivot, True,
            (self.__myNode.getUniqueId(),)),
            requestUndo=False)

        self.__myLastPosition = self.getPosition(event)

        return True


    def MyRotateStop(self, event):
        self.__myLastPosition = self.getPosition(event)
        theActionRegistry.removeSetFromStack(TransformInterpreter.__kRotateKeyMappingId)
        theActionRegistry.activateSet(TransformInterpreter.__kRotateKeyMappingId, False)
        return True


    def APPLICATION_CLOSE_SCENE(self, message):
        self.__myNode = None
        self.__myRotatePivot = (0.0, 0.0, 0.0)
        self.__myLastPosition = (0.0, 0.0)
        theActionRegistry.removeSetFromStack(TransformInterpreter.__kMoveKeyMappingId)
        theActionRegistry.removeSetFromStack(TransformInterpreter.__kMoveStartKeyMappingId)
        theActionRegistry.removeSetFromStack(TransformInterpreter.__kRotateKeyMappingId)
        theActionRegistry.removeSetFromStack(TransformInterpreter.__kRotateStartKeyMappingId)


    def CUSTOM_TRANSFORM_ENABLE(self, message):
        (isOn,) = message.data

        if isOn:
            theActionRegistry.addSetToStack(TransformInterpreter.__kMoveStartKeyMappingId)
            theActionRegistry.addSetToStack(TransformInterpreter.__kRotateStartKeyMappingId)
        else:
            theActionRegistry.removeSetFromStack(TransformInterpreter.__kMoveStartKeyMappingId)
            theActionRegistry.removeSetFromStack(TransformInterpreter.__kRotateStartKeyMappingId)

        theActionRegistry.activateSet(TransformInterpreter.__kMoveStartKeyMappingId, isOn)
        theActionRegistry.activateSet(TransformInterpreter.__kRotateStartKeyMappingId, isOn)


    def SET_DISPLAY(self, message):
        (self.__myDisplay,) = message.data


    def SET_DOCUMENT(self, message):
        (document,) = message.data
        self.__myNode = document.get(ModelIO.id).root


def instantiate():
    return TransformCustom()


def info():
    customInfo = CustomInfo()
    customInfo.vendor = 'Autodesk'
    customInfo.version = '1.0'
    customInfo.api = '2009'
    customInfo.shortInfo = "Custom transform feature."
    customInfo.longInfo = \
"""Enable or disable custom transform feature. When enabled, left mouse button will transform the \
scene and right mouse button will rotate the scene.
"""
    return customInfo