Interactive/SaveBehaviorAsMovieCustom.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 2010 R1 or later.
#
# You will get a custom menu item in the context menu for the
# behaviors for saving behaviors as movies.  Adjust "The" parameters.

# --- START OF CUSTOMIZATION ------------------------------------------------

# If set to True, the resulting AVI or collection of images will be shown.
TheShowResult = True

# Size, format, FPS
TheFramesPerSecond  = 5     # Frames per second in the result
TheImageWidth       = 640       # Movie/image width
TheImageHeight      = 480       # Movie/image height

# If True, the end of the turntable image movie will not have the starting
# position in it.  If False, the start and the end of the movie will
# have the object in the same position.
TheSkipLastTurntableFrameImages = False

# As above, but for the .avi format.  Setting this to True means that
# the last frame is not repeated, which means that a loop playback
# will not have the hesitation at the end.
TheSkipLastTurntableFrameMovies = True

# Renderer choice
TheUseRaytracing       = False  # True for raytracing, False otherwise

# HW Quality
TheAntiAliasing        = 4      # 0 - 9, only used if raytracing is off

# RT Quality
TheRaytracingSampling  = 4      # 0 for 1/16
                                # 1 for 1/4
                                # 2 for 1
                                # 3 for 4
                                # 4 for 16
                                # 5 for 64
                                # 6 for 256
TheRaytracingPreset    = ('02-InteractiveShadows.xml', u'Shadows - Interactive', 1)
# One of these, from support/InteractiveRaytracingSettings
# ('01-InteractiveRT.xml', u'Ray Tracing - Interactive', 1)
# ('02-InteractiveShadows.xml', u'Shadows - Interactive', 1)
# ('03-InteractiveAmbientShadow.xml', u'Ambient Shadow - Interactive', 1)
# ('04-InteractiveGlobalIllumination.xml', u'Global Illumination - Interactive', 1)
# ('05-GoodRT.xml', u'Ray Tracing - Good', 1)
# ('06-GoodShadows.xml', u'Shadow - Good', 1)
# ('07-GoodAmbientShadow.xml', u'Ambient Shadow - Good', 1)
# ('08-GoodGlobalIllumination.xml', u'Global Illumination - Good', 1)
# ('09-UltimateRT.xml', u'Ray Tracing - Ultimate', 1)
# ('10-UltimateShadows.xml', u'Shadow - Ultimate', 1)
# ('11-UltimateAmbientShadow.xml', u'Ambient Shadow - Ultimate', 1)
# ('12-UltimateGlobalIllumination.xml', u'Global Illumination - Ultimate', 1)

# ----- END OF CUSTOMIZATION ------------------------------------------------

__all__ = ['SaveBehaviorAsMovieCustom', 'instantiate']

from MessageInterpreter  import MessageInterpreter
from ScriptRunner        import ScriptRunner
from UserCustomization   import UserCustomBase
from GenericPopupMenu    import GenericPopupMenuItem
from ConfirmDialog       import ConfirmDialog

import BehaviorIO
import wx, os, glob, base64

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

# base64.encodestring(open("file.swf", "rb"),sys.stdout)
TheRotator = "Q1dTCQoIAAB4nH1VfVMTRxjf3Xt5chcIAcKBASRoFA1IDvGV1ipisGLSWAPKdCZ6R7Ih59wL3l0y8F+HT1Cn//WfOvWD2I8QmSn9Cp1+Cbp7Fx1pO+7knt19Xn77vGV3H8n3EBr5HaEBjB4MJxBCa+Tk5OT9WJItMao1qEtzSwi9z/wCnMFGGi3yaXugZZtBe5F2qRsGUolPSOx6VnO6Znbpfdo2u5bnrwYVr2vRtU4Qes5LZjJQMS1303Kobbl0MMZoWsGebR4osapt7RW+CLHyOYRiOeYuLVtBKK36vnkAtmc2tx1biaFdGipbT8tlxqS+4nidgDpel8o+fc2UVCZ6Sl93aBDKLd906JLCzSkHmG2H4d5KsWg2vR262PCc4mptuXhV128UdzqWHVpuMhZ1QssORuPjzEaDBoG1Y9lWePAxQ77v+UGqv9kPqe+adj/yFgOifqDGu13qOcl46dCmZfZt9nzLZcftftp6zIz6fYjAdPbYrn9YcBCE1Onjhey0RLzsWH3gyNt+cvYde/xUBVY+VSBzml9jPoQ0f5r5IJ6rO69oI1zz3JCVhfozp5UeuSxAsxFaXRorTn4BZOrznlqJeorLzbDRpr4c60DXYgm2qWxTdzdsixUzbAssQXJU3G0pCFk/qBF9bjWZzDH3Bcdyhe1KWWyaoSlFHSPyHEpR0eW4O0Re+oTZbK61Lbsp7nWCthQy12lqzXRdL8xxee7OHTI/L5V4TSUaU+5mgsO5DAy/wrbAEkv812SzLJm2teuC69Uapk2VgNOK16Rq5EPAK5BYq1aelEubpTQ7OgqZNzP73/lqhUcUsdRKdatWelmpPitd3nJNFn4u9HI+NW37IPbLj9uYNnNNr9FxmM1iigGu8wBrDd/aC4f+lc/BU6kf/k+htP+vrxz3wjkNa4ImaTChaIPjkoQklMhoY5qmjWsT2hktq01qU9q0dlab0XLarHZOO59VsvnshezF7Fz2UvZytkBmE0QRQE0OiIM4NZQelkdGM2Pa+MQZKTs5NX12Jje7ApgAEUGQQUwAUYEkgQyAnAIyBCQN8jDACJDRSazMYyALQK4AWQRSBKIDWQJyFcgykGtAris3cOomTt3CIN5WVpjyV6B+DeSOssDW94CsAtwHWAN4AFACuAWwDvAQyCMgj4FUgHwHuArkCZAVIE+BFADXQNwEcQvEZyA+l1F0ZbI7EhOMCNvwLx5YEBMSEmUJIRmxizSBFKapIpTkF68wGOljialhpoCxogo9/SFjgaAmH/f0PDo8Tg8ipBjSUSs1Zw4dvTk8PjKkVnrs75MTQzKGjeE8qo/0WqNGpjX2jkt+Wx/B6xrhy5/Xx0krddEcYpBEEJPpv3BPf1441imePXSEiIoRlSMKFH9wMMX1CQNaZzYm8I8TDqGYeeFI6Z+YG5QJbdLKMiq1Uq3JCOQUaypCrCfZVthI4gi5Pr0xjSJ0m39y9Sy3ARZOj8/VGWxI0SKH0zMI9fQj/QdU+LOgzzu4PrvwDcd6u3D3bXUWTycEftCbQ4rnHW4kxaevn0NjL3lG8igOGDd/xe/w+TxcYGlFF5GA0BxrVXSJl+EyEhEqIDKB5lnakSAIyXSbZ+YP3cgsfGsuMLphXnmh1pMLZRZFW30B9cRGArXBAAO3Fo3BapG8kOryhozakpEx9NaSoTAeE6vVqzg9zsP4wMM4/hjG95H75CEulZ+czyNRwGpyqKf3HqH6ch4ZA9VlwsokEdYFcz2dIuOablzXjRu6cVM3bunGbd0Q2W8bTcejLTD1u6NFFL/PX3w5Fz9/ORF7+tE/lPwecw=="

TheTemplate = "PGh0bWw+CjxoZWFkPgo8dGl0bGU+JXM8L3RpdGxlPgo8L2hlYWQ+Cjxib2R5Pgo8ZW1iZWQgc3JjPSIlcyIgcXVhbGl0eT0iaGlnaCIgYmdjb2xvcj0iI2ZmZmZmZiIgd2lkdGg9IjY0MCIgaGVpZ2h0PSI0ODAiIG5hbWU9IiVzIiBhbGlnbj0ibWlkZGxlIiBhbGxvd1NjcmlwdEFjY2Vzcz0ic2FtZURvbWFpbiIgYWxsb3dGdWxsU2NyZWVuPSJmYWxzZSIgdHlwZT0iYXBwbGljYXRpb24veC1zaG9ja3dhdmUtZmxhc2giIHBsdWdpbnNwYWdlPSJodHRwOi8vd3d3LmFkb2JlLmNvbS9nby9nZXRmbGFzaHBsYXllciIgLz4KPC9ib2R5Pgo8L2h0bWw+Cg=="

tempShot = 'temporaryKeyShot1'
theCreateAsMovie = False
theCreateAsTiff = True

class SynchronizedScript( ScriptRunner ):
    def __init__( self, scriptName, behavior, label, directory,
                  filename, duration, args ):
        ScriptRunner.__init__( self, scriptName, args )
        self.myBehavior = behavior
        self.myLabel = label
        self.myDirectory = directory
        self.myFilename = filename
        self.myDuration = duration

    def Main( self ):
        if TheUseRaytracing:
            self.sendMessageSync('RT_SWITCH_REQUEST', (True,))
        self.sendMessageSync("SHOT_CREATE",
                             ("ShotKeyframed", tempShot,
                              "%s Shot" % (self.myBehavior),
                              "", False, "", None, True, 0))

        self.sendMessageSync("SHOT_SET_PARAMETERS",
                             (tempShot,
                              {'Duration' : self.myDuration,
                               'TransitionType' : u"Cut to shot",
                               'Keyframe' : None,
                               'Keyframe2' : None},
                              True))
        (root,ext) = os.path.splitext(self.myFilename)
        self.sendMessage("BEHAVIOR_METHOD_EXECUTE",(self.myBehavior,
                                                    'PlayFromStart'))
        if theCreateAsMovie:
            createHow = 0
        elif theCreateAsTiff:
            createHow = 2
        else:
            createHow = 1
        self.sendMessageSync('SAVE_SHOT_AS_MOVIE_AT_RESOLUTION',
                             (TheImageWidth, TheImageHeight,
                              TheFramesPerSecond,
                              createHow, TheAntiAliasing, False,
                              os.path.join(self.myDirectory,root),(tempShot,),
                              ('Full Frames (Uncompressed)', 7500),
                              (-1, int(TheRaytracingSampling*100.0/6.0),
                               TheRaytracingPreset)),
                             userConditions='SHOT_PLAYING_DONE',
                             userTimeout = 36000)
        self.sendMessage("BEHAVIOR_METHOD_EXECUTE",(self.myBehavior,
                                                    'StopAndResetToStart'))
        self.sendMessage("SHOT_DELETE", (tempShot,))
        if TheUseRaytracing:
            self.sendMessageSync('RT_SWITCH_REQUEST', (False,))

        # Copy the rotator, produce listing if images were made:
        showIt = None
        if not theCreateAsMovie and not theCreateAsTiff:
            imagefiles = glob.glob(os.path.join(self.myDirectory,
                                                "%s_*.jpg" % (root)))
            if imagefiles:
                swfName = "%s.swf" % (root)
                swfCopy = os.path.join(self.myDirectory, swfName )
                swf = open( swfCopy, "wb" )
                swf.write(base64.decodestring(TheRotator))

                imagesFilename = os.path.join( self.myDirectory,
                                               "images.xml" )
                images = open( imagesFilename, "w" )
                images.write( '<?xml version="1.0" encoding="utf-8"?>\n' )
                images.write( '<behavior name="%s" width="%d" height="%d" how="%d">\n' % (self.myLabel,TheImageWidth,TheImageHeight,createHow))
                cntr = 0
                for f in imagefiles:
                    fn = os.path.split(f)[1]
                    images.write( '\t<image file="%s" frame="%d"/>\n' % (fn,cntr) )
                    cntr += 1

                images.write( '</behavior>\n' );
                images.close()

                htmlId = self.myLabel
                htmlFilename = os.path.join( self.myDirectory,
                                             "%s.html" % (root) )
                html = open( htmlFilename, "w" )
                html.write( base64.decodestring(TheTemplate) % (htmlId, swfName, htmlId) )
                html.close()
                if TheShowResult:
                    showIt = swfCopy

        if TheShowResult:
            if theCreateAsMovie:
                showIt = os.path.join(self.myDirectory,self.myFilename)
            elif theCreateAsTiff:
                showIt = self.myDirectory

        if showIt:
            os.startfile(os.path.normpath(showIt))

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

class CallScriptBatch:
    def __init__(self, behAndLab, directory, filename, duration):
        (self.myBehavior,self.myLabel) = behAndLab
        self.myDirectory = directory
        self.myFilename = filename
        self.myDuration = duration

    def instantiate( self, scriptName, args ):
        return SynchronizedScript( scriptName,
                                   self.myBehavior,
                                   self.myLabel,
                                   self.myDirectory,
                                   self.myFilename,
                                   self.myDuration,
                                   args )

# --------------------------------------------------------------
class ContextMenu( UserCustomBase ):
    def __init__( self ):
        self.myInterpreter = LocalInterpreter()

    def filechoose(self):
        msg = "Please specify filename for images or movie"
        wild = "JPEG Image (*.jpg)|*.jpg|TIFF Image (*.tif)|*.tif|Movie File (*.avi)|*.avi|"
        userPath = 'c:/'
        dialog = wx.FileDialog(None, msg,
                               defaultDir="c:/",
                               defaultFile="",
                               wildcard=wild,
                               style=wx.SAVE | wx.OVERWRITE_PROMPT)
        if dialog.ShowModal() == wx.ID_OK:
            dir = dialog.GetDirectory()
            f = dialog.GetFilename()
            (r,ext) = os.path.splitext(f)
            global theCreateAsMovie
            global theCreateAsTiff
            if (".tiff" == ext) or (".tif" == ext):
                theCreateAsMovie = False
                theCreateAsTiff = True
            elif ".avi" == ext:
                theCreateAsMovie = True
                theCreateAsTiff = False
            elif (".jpeg" == ext) or (".jpg" == ext):
                theCreateAsMovie = False
                theCreateAsTiff = False
            else:
                theCreateAsMovie = False
                theCreateAsTiff = True
            return (dir, f)
        else:
            dialog.Destroy()
            return None

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

    def appendPopupMenuItems( self, id, popupMenu, item ):
        menuItem = None
        if "BehaviorSelector" == id:
            it = item.getType()
            # Covering Turntable, KeyframeAnimation, not Fbx yet
            if "Turntable" == it:
                menuItem = GenericPopupMenuItem(_( 'Save turntable as a movie ...'),
                                                self.__cbCustomBehaviorTT,
                                                item )
            elif "KeyframeAnimation" == it:
                menuItem = GenericPopupMenuItem(_( 'Save keyframe animation as a movie ...'),
                                                self.__cbCustomBehaviorKF,
                                                item )
        if menuItem:
            popupMenu.append( menuItem )


    def __cbCustomBehaviorTT( self, item ):
        dirfile = self.filechoose()
        if dirfile:
            (directory,filename) = dirfile
            self.myInterpreter.SaveMovieTurntable( item.name(),
                                                   directory, filename )

    def __cbCustomBehaviorKF( self, item ):
        dirfile = self.filechoose()
        if dirfile:
            (directory,filename) = dirfile
            self.myInterpreter.SaveMovieKeyframe( item.name(),
                                                  directory, filename )


# --------------------------------------------------------------
class LocalInterpreter( MessageInterpreter ):

    def __init__( self ):
        MessageInterpreter.__init__( self )
        self.myDocument = None

    def SET_DOCUMENT( self, message ):
        (self.myDocument,) = message.data

    def APPLICATION_CLOSE_SCENE( self, message ):
        self.myDocument = None

    def document(self):
        return self.myDocument

    def SaveMovieTurntable(self, behavior, directory, filename):
        # Find the behavior in question, see what parameters
        # it has, and choose the duration from it.  Make sure
        # that it is a float...
        (duration,label) = self._GetTurntableDurationAndLabel(behavior)
        self.SaveMovieBehavior((behavior,label), directory, filename, duration)

    def SaveMovieKeyframe(self, behavior, directory, filename):
        # Find the behavior in question, see what parameters
        # it has, and choose the duration from it.  Make sure
        # that it is a float...
        (duration,label) = self._GetKeyframeDurationAndLabel(behavior)
        self.SaveMovieBehavior((behavior,label), directory, filename, duration)

    def SaveMovieBehavior(self, behAndLab, directory, filename, duration):
        (behavior,label) = behAndLab
        # Show the global parameters:
        m = "Save movie for behavior (%s) with these parameters:\n" % (label)
        m += "    Duration: %f seconds\n" % (float(duration))
        m += "    Frames per second: %d\n" % (int(TheFramesPerSecond))
        m += "    Width: %d\n" % (int(TheImageWidth))
        m += "    Height: %d\n" % (int(TheImageHeight))
        if theCreateAsMovie:
            m += "    Create as AVI %s\n" % (filename)
        elif theCreateAsTiff:
            m += "    Create as TIFF sequence %s\n" % (filename)
        else:
            m += "    Create as JPEG sequence %s\n" % (filename)
        m += "    Directory %s\n" % (directory)

        if TheUseRaytracing:
            m += "    Using raytracing with parameters\n"
            m += "      Sampling: %d" % (int(TheRaytracingSampling))
            m += "      Preset: %s" % TheRaytracingPreset
        else:
            m += "    Using hardware with anti-aliasing %d" % (int(TheAntiAliasing))
        print m

        cd = ConfirmDialog()
        cd.show( None, "Save behavior as movie", m, ('OK','Cancel') )
        if 2 == cd.returnValue():
            # This is cancel...
            duration = 0.0

        if duration > 0.0:
            module = CallScriptBatch(behAndLab, directory, filename, duration)
            self.sendMessage( "EXECUTE_MODULE", ("BehaviorMovieSave", module))

    def _GetTurntableDurationAndLabel(self, turntable):
        # Return the time for a single spin
        duration = 0.0
        if self.myDocument:
            behaviors = self.myDocument.get(BehaviorIO.id)
            tt = behaviors[turntable]
            rpm = float(tt.getParameterValue('RPM'))
            if rpm > 0:
                # Single spin in seconds will take 60.0 / rpm
                duration = 60.0 / rpm
                if theCreateAsMovie:
                    if TheSkipLastTurntableFrameMovies:
                        duration -= 1.0/TheFramesPerSecond
                else:
                    if TheSkipLastTurntableFrameImages:
                        duration -= 1.0/TheFramesPerSecond
            label = tt.getLabel()
        return (duration,label)

    def _GetKeyframeDurationAndLabel(self, keyframe):
        # Return the time for a single spin
        duration = 0.0
        label = ''
        if self.myDocument:
            behaviors = self.myDocument.get(BehaviorIO.id)
            tt = behaviors[keyframe]
            # This is the ugly way of getting it!
            tt.updateDuration()
            duration = float(tt._FbxBehavior__myDuration)
            label = tt.getLabel()
        return (duration,label)

def instantiate():
    return ContextMenu()