Interactive/TransformCustom.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.

__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
from Application        import theApplication


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.__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 \
           theApplication.getCurrentDocument().getModels().root 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, (theApplication.getCurrentDocument().getModels().root.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 \
            theApplication.getCurrentDocument().getModels().root 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,
            (theApplication.getCurrentDocument().getModels().root.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.__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 instantiate():
    return TransformCustom()


def info():
    customInfo = CustomInfo()
    customInfo.vendor = 'Autodesk'
    customInfo.version = '2.0'
    customInfo.api = '2013'
    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