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

#
# You will need Autodesk Showcase 2008 or later.
#
# Drop this file, along with GridArrangeDialog.py in the directory 
# where the user preferences are.
#
# You will get a "Tools" menu added to the Showcase menu bar containing the
# "Arrange items in a grid..." menu item. This menu item opens a dialog box
# which allows the user to arrange selected items in grid patterns.
#

from awSupportApi           import Vector, AffineMatrix, Point
from DialogInterpreter      import DialogFactory
from GenericMenu            import GenericMenu
from GridArrangeDialog      import GridArrangeDialog
from Message                import Message
from MessageInterpreter     import MessageInterpreter
from MessageRegistry        import theMessageRegistry
from UserCustomization      import UserCustomBase, CustomInfo
from RunInUiThread          import RunInUiThread
from RTFapi                 import Node
from SceneGraphUtilities    import ComputeBoundingBox

def instantiate():
    return PositionCustomization()


class PositionCustomization(UserCustomBase):
    def __init__(self):
        self.__myInterpreter = PositionCustomInterpreter()

    def appendMenu(self, menuInterpreter):
        menu = menuInterpreter.appendMenu(ToolsMenu)
        
    def getInterpreter(self, isInteractive):
        return self.__myInterpreter

        
class ToolsMenu(GenericMenu):
    def __init__(self, parentWindow):
        GenericMenu.__init__(self, parentWindow, menuId=u'CarCustom')
        
        self.__myDocumentLoaded   = False
        self.__mySpectating       = False
        
        self.__myGridId = self.appendItem('Arrange items in a grid...', self.__onShowGridDialog)
        self.__updateMenu()
        
    def __onShowGridDialog(self, event):
        self.sendMessage('SHOW_GRID_DIALOG', ())

    def getLabel(self):
        return 'Tools'
                
    def __updateMenu(self):
        enableStates = {}
        enableStates[self.__myGridId] = self.__myDocumentLoaded and not self.__mySpectating
        
        self.enableMenuItems(enableStates)
        
    def CLIENT_CONNECTION_STATUS(self, message):
        (self.__mySpectating, statusMessage) = message.data
        RunInUiThread(self.__updateMenu)
        
    def APPLICATION_CLOSE_SCENE(self, message):
        self.__myDocumentLoaded = False
        RunInUiThread(self.__updateMenu)
        
    def DOCUMENT_LOADED( self, message ):
        self.__myDocumentLoaded = True
        RunInUiThread(self.__updateMenu)
        
                
class PositionCustomInterpreter(MessageInterpreter):
    def __init__(self):
        MessageInterpreter.__init__(self)

    def SHOW_GRID_DIALOG(self, message):
        if DialogFactory.ready():
            DialogFactory.instance().show(GridArrangeDialog)
    
    def __calculateBoundingBoxOffset(self, nodeId):
        """
        Calculates the vector from the location of the object in worldspace
        to the center of it's bounding box
        """
        node = Node.getNodeById(nodeId)
        
        # The translate is 4th column rows 1-3 of the AffineMatrix
        matrix = AffineMatrix()
        node.getWorldTransform(matrix, True)
        translate = Point(matrix.m[9], matrix.m[10], matrix.m[11])
        bbox = ComputeBoundingBox([node])
        
        if not bbox.isBounded():
            return Vector(0.0, 0.0, 0.0)
            
        boxCenter = bbox.mid()
        
        return Vector(translate[0] - boxCenter[0], 
                    translate[1] - boxCenter[1], 
                    translate[2] - boxCenter[2])
            
    def GRID_ARRANGE(self, message):
        (objects, rows, columns, layout) = message.data
        (rowVector, columnVector, gridCenter) = layout
        
        # Go to the starting corner of the grid:
        currentRow = Vector(gridCenter)
        rowStart = Vector(rowVector)
        rowStart *= (rows - 1)/2.0
        columnStart = Vector(columnVector)
        columnStart *= (columns - 1)/2.0
        
        currentRow -= rowStart
        currentRow -= columnStart
        currentColumn = Vector(currentRow)
        
        # start at this point and lay out our objects in a grid pattern
        for (i, objectID) in enumerate(objects):
            # Center the object's bounding box on the current grid point
            boxOffset = self.__calculateBoundingBoxOffset(objectID)
            boxOffset += currentColumn
            
            self.sendMessage("TRANSLATE_WORLDSPACE", 
                (boxOffset[0], boxOffset[1], boxOffset[2], False, (objectID, )) )
                        
            currentColumn += columnVector
            
            # start a new row every columns number of objects
            if (i + 1) % columns == 0:
                currentRow += rowVector
                currentColumn = Vector(currentRow)
            

SHOW_GRID_DIALOG_Doc = \
[(
"""
Show's the "Arrange Items in a Grid" dialog box.
"""
),
[], 
]

GRID_ARRANGE_DIALOG_Doc = \
[(
"""
Arranges the specified nodes into the specified 2D grid layout.

The correct translation offset will be calculated so that each node's
bounding box will be at the center of its grid point.
"""
),
[[("objects"),("A list of node ids to arrange in the grid.")], 
 [("rows"), ("The number of rows in the grid.")],
 [("columns"), ("The number of columns in the grid.")],
 [("layout"), (
 """
 Contains a tuple consisting of: the vector to add between rows, the vector to 
 add between columns, and a vector representing worldspace location of the 
 center of the grid.
 """)]
 ]
]

messages = (
    ('SHOW_GRID_DIALOG',    0, (), SHOW_GRID_DIALOG_Doc ),
    ('GRID_ARRANGE',        Message.kUndoable, (tuple, int, int, tuple), GRID_ARRANGE_DIALOG_Doc ),
)

# These messages will now appear in the message reference window
for message in messages:
    theMessageRegistry.register( message[0], message[2], message[1], message[3] )



def info():
    customInfo = CustomInfo()
    customInfo.vendor = 'Autodesk'
    customInfo.version = '1.0'
    customInfo.api = '2013'
    customInfo.shortInfo = "Arrange selected items in grid patterns."
    customInfo.longInfo = \
"""You will get a "Tools" menu added to the Showcase menu bar containing the "Arrange items in a \
grid..." menu item. This menu item opens a dialog box which allows the user to arrange selected \
items in grid patterns.
"""
    return customInfo