""" Script name: imageviewer.py Author: Satish 'iluvblender' Goda (satishgoda at gmail dot com) Start Date: Saturday, Apr 21, 2007 Last Updated: Monday, Apr 25, 2007 (Render image from inside viewer by hotkey or menu item) Monday, Apr 23, 2007 (Auto play mode + menu support) License: None. Use as you please Disclaimer: Please use the script at your own risk. Pre-requisite: This script works hand-in-hand with my renderscript.py script. Please check the link below http://iluvblender.googlepages.com/bp_prepostrenderscript Testing: This script was developed on Windows XP (SP2), Blender 244 cvs (Which uses Python 2.5) Purpose: Review rendered images with description stored in filename.txt Images stored in /path/to/proj/images/filename as filename_timestamp.png Descriptions stored in .blend itself as filename.txt as "imagename -> description" More features planned in near future. (Saving images w/ descriptions to html files) Todo: Implement smart caching mechanism. Usage of the script: Execute with ALT+P in the text editor. During initial loading the progress is shown in ProgressBar at top The viewer operates in three modes: List Mode (Default) Image Mode Reloading mode Once images are reloaded, the viewer switches to "Image Mode" HOTKEYS: Following keys work in both modes ESC to quit viewer ENTER/CKEY - Toggles "List Mode" and "Image Mode". The current selection is syncronized between modes. [LEFTARROWKEY | RIGHTARROWKEY] or [ZKEY | XKEY] Navigate the image files UPARROWKEY/QKEY - Move to first file DOWNARROWKEY/WKEY - Move to last file RKEY - Reload images During reload mode none of the keys work. Use the following keys in "Image Mode" AKEY - Show image alpha SKEY - Toggle image size (half, full, double) SHIFT+SKEY - Swap previous and current image scales PKEY - Autoplays images from beginning to end (Pressing ESCKEY stops playing) You will be prompted to enter an interval in seconds. The defaults are 1 second for List Mode playback and 0.5 seconds for Image Mode. Menu Support: LMB in both modes pops up the navigation menu, with following options First image Previous image Next image Last image RMB in both modes pops up a menu with following options Playback Switch mode Reload Quit MMB in Image mode pops up a menu with following options Toggle alpha Cycle scale Previous scale """ import Blender from Blender import BGL, Image, Draw, Window, Text, Scene from Blender.sys import * FULLFILENAMEEXT = Blender.Get('filename') # Viewer variables # The viewer runs in List-Mode, Image-Mode, Reload-Mode, Help-Mode LISTMODE, IMAGEMODE, RELOADMODE, HELPMODE = range(0, 4) currentmode = LISTMODE Viewer_DrawCallbacks = None Viewer_EventCallbacks = None # Imagelist data indices FILEPATH, FILENAME, DESCRIPTION = range(0,3) imagelist = None currentimageindex = 0 numberofimages = 0 # The list in the mega-tuple below is [currentscale tuple index, previousscale tuple index] CURRENT, PREVIOUS = 0, 1 imagescale = (("Half Scale", 0.5), ("Full Scale", 1.0), ("Double Scale", 2.0), [1, None]) showalpha = 0 alphastr = ("Alpha OFF", "Alpha ON") reloadingfile = "None" areaid = Window.GetAreaID() #====================================================================== # Get blend filename without extension part def GetBlendName(): return (basename(FULLFILENAMEEXT)).split('.')[0] # Determine where the images for the blend file reside def GetImageBasePath(): filedir = dirname(FULLFILENAMEEXT) imagesdir = "images" file = GetBlendName() return filedir+sep+imagesdir+sep+file+sep #====================================================================== # METADATA functions # Read the file.txt stored inside Blender def GetMetaDataOLD(): file = GetBlendName() metafile = Text.Get("%s.txt" % (file)) return metafile.asLines()[1:] # Read the file.txt stored in the image directory def GetMetaData(): file = GetBlendName() metafilename = dirname(FULLFILENAMEEXT)+sep+"images"+sep+file+sep+file+".txt" try: metafile = open(metafilename, 'r') metafiledata = [line.rstrip('\n') for line in metafile.readlines()[1:]] metafile.close() except: return None return metafiledata #====================================================================== # Loads review Images for the blend file # returns (numberofimages, imagelist) def LoadImages(): global reloadingfile, currentmode metadata = GetMetaData() if metadata == None: return (None, None) else: imagesbasepath = GetImageBasePath() imagefile, imagedesc, imagepath = None, None, None totalimages = len(metadata) imagelist, numberofimagesloaded = [], 0 for data in metadata: if data: imagefile, imagedesc = [chunk.strip() for chunk in data.split("->")] imagepath = imagesbasepath + imagefile if currentmode == RELOADMODE: reloadingfile = imagefile Draw.Draw() else: Window.DrawProgressBar((numberofimagesloaded*1.0)/totalimages, "Loading: %s" % imagefile) imagelist.append([Image.Load(imagepath), imagefile, imagedesc]) numberofimagesloaded += 1 sleep(30) if currentmode == RELOADMODE: reloadingfile = "Done" Draw.Draw() sleep(300) currentmode = IMAGEMODE else: Window.DrawProgressBar(1.0, "Loading Done!!!") return (numberofimagesloaded, imagelist) #====================================================================== # Draw the selected image on the screen def DrawImage(xpos, ypos, scale): imagedata = imagelist[currentimageindex][0] Draw.Image(imagedata, xpos, ypos, scale, scale) # Draw any status strings, mode strings etc., def DrawStatus(): BGL.glColor3f(0.0, 0.0, 1.0) BGL.glRasterPos2f(10, 40) scaleinfo = imagescale[len(imagescale)-1] scalestr = imagescale[scaleinfo[0]][0] Draw.Text(scalestr, 'large') BGL.glColor3f(1.0, 0.0, 0.0) BGL.glRasterPos2f(10, 25) Draw.Text(alphastr[showalpha], 'large') BGL.glColor3f(1.0, 1.0, 0.0) BGL.glRasterPos2f(10, 10) imagedesc = imagelist[currentimageindex][DESCRIPTION] imageline = "(%s/%s) : " % (currentimageindex+1, numberofimages) imageline += imagedesc Draw.Text(imageline, 'large') #====================================================================== def Draw_ReloadingMode(): BGL.glColor3f(1.0, 1.0, 0.0) BGL.glRasterPos2f(10, 25) Draw.Text("RELOADING: %s" % (reloadingfile), 'large') #====================================================================== # Display the image at selected scale and also print descriptions def Draw_ImageMode(): if showalpha: BGL.glEnable( BGL.GL_BLEND ) BGL.glBlendFunc(BGL.GL_SRC_ALPHA, BGL.GL_ONE_MINUS_SRC_ALPHA) BGL.glClearColor(0.0, 0.0, 0.0, 0.0) else: BGL.glClearColor(0.0, 0.0, 0.0, 1.0) try: scaleinfo = imagescale[len(imagescale)-1] scale = imagescale[scaleinfo[0]][1] DrawImage(10, 60, scale) except: imagedesc = "Something went wrong" if showalpha: BGL.glDisable(BGL.GL_BLEND) DrawStatus() #====================================================================== # Display a list of the files and a thumbnail of selected image # A thumbnail is drawn just above the first line in the list POSITION, DIRECTION = 0, 1 UP, DOWN = 1, -1 def Draw_ListMode(toptobottom=1): xpos, lineheight = 10, 15 numberofitems = len(imagelist) heightoflist = numberofitems*lineheight imgyoffset = lineheight imgypos = heightoflist + imgyoffset imagescale = 1.0/10 turtledata = ((lineheight, UP), (heightoflist, DOWN)) turtle = turtledata[toptobottom] turtleypos, direction = turtle[POSITION], turtle[DIRECTION] DrawImage(xpos, imgypos, imagescale) for index in range(0, numberofitems): image = imagelist[index] if currentimageindex == index: BGL.glColor3f(1.0, 0.0, 0.0) else: BGL.glColor3f(0.0, 0.0, 0.0) BGL.glRasterPos2f(xpos, turtleypos); turtleypos += (direction*lineheight) Draw.Text("%d. %s -> %s" % (index+1, image[FILENAME], image[DESCRIPTION]), 'large') #====================================================================== def SwitchMode(): global currentmode if currentmode == RELOADMODE: return currentmode = 1 - currentmode def SetMode(mode): global currentmode currentmode = mode #====================================================================== def SwapImageScale(): scaleinfo = imagescale[len(imagescale)-1] if scaleinfo[PREVIOUS] != None: scaleinfo[CURRENT], scaleinfo[PREVIOUS] = scaleinfo[PREVIOUS], scaleinfo[CURRENT] # I just cycle through the scales using modulo arithmetic # I am not considering the last element of the scale-tuple # as it does not contain scale information def ToggleImageScale(): scaleinfo = imagescale[len(imagescale)-1] currscale = scaleinfo[CURRENT] scaleinfo[PREVIOUS] = currscale scaleinfo[CURRENT] = (currscale+1)%(len(imagescale)-1) def HandleImageScale(): if Window.GetKeyQualifiers() & Window.Qual.SHIFT: SwapImageScale() else: ToggleImageScale() def PreviousImage(): global currentimageindex if currentimageindex > 0: currentimageindex -= 1 def NextImage(): global currentimageindex if currentimageindex < (numberofimages-1): currentimageindex += 1 def FirstImage(): global currentimageindex currentimageindex = 0 def LastImage(): global currentimageindex currentimageindex = (numberofimages - 1) def ToggleImageAlpha(): global showalpha showalpha = 1 - showalpha #====================================================================== # If any keys were pressed during reloading/autoplay mode (other than those that the mode polls), # ignore them def FlushEventQueue(): while Window.QTest() and Window.QRead(): pass def GetPlaybackInterval(): autoplaydefaults = [1, 0.5] retval = Draw.PupFloatInput("playback interval (in seconds):", 2.0, 1.0, 10.0, 10, 2) if retval != None: interval = int(retval*1000) else: interval = int(autoplaydefaults[currentmode]*1000) Draw.PupMenu("default interval %d ms" % interval) return interval # Check if the user pressed ESCKEY during playback # We do this by polling the event queue def PlaybackHalted(): if Window.QTest(): evt, val = Window.QRead() if evt == Draw.ESCKEY and val: Draw.PupMenu("Autoplay halted by user!!") return 1 return 0 # Display image at current time def DisplayImage(WhichImage, interval): WhichImage() Draw.Draw() sleep(interval) # For now, playback starts from the first image def AutoplayImages(): interval = GetPlaybackInterval() lastimageindex = len(imagelist)-1 DisplayImage(FirstImage, interval) for currentimageindex in range(1, lastimageindex+1): if PlaybackHalted(): break DisplayImage(NextImage, interval) if (currentimageindex == lastimageindex): Draw.PupMenu("Autoplay Done!!") FlushEventQueue() # Once reloading is done, the last image is displayed in Image Mode # TODO: Should code this function in a better way def ReloadImages(): global currentimageindex, numberofimages SetMode(RELOADMODE) numberofimages, images = LoadImages() del imagelist[:] imagelist.extend(images) del images[:] del images LastImage() SetMode(IMAGEMODE) FlushEventQueue() # Render an image and reload the images def RenderImage(): try: context = Scene.GetCurrent().getRenderingContext() context.render() ReloadImages() except: Draw.PupMenu("RenderImage() says ...%t|Could not render image") #====================================================================== LMB_MenuItemMap = [FirstImage, PreviousImage, NextImage, LastImage] RMB_MenuItemMap = [AutoplayImages, SwitchMode, RenderImage, ReloadImages, Draw.Exit] MMB_MenuItemMap = [ToggleImageAlpha, ToggleImageScale, SwapImageScale] def ProcessMenu(menu, WhichMenu): result = Draw.PupMenu(menu) if result > 0: WhichMenu[result-1]() # Enabled in List Mode and Image Mode def HandleLeftMouse(): menu = "Navigation%t|First image (Q/UP)|Previous image (Z/LEFT)|Next image (X/RIGHT)|Last image (W/DOWN)" ProcessMenu(menu, LMB_MenuItemMap) # Enabled in Image Mode def HandleMiddleMouse(): menu = "Image Mode%t|Toggle alpha (A)|Cycle scale (S)|Previous scale (SHIFT+S)" ProcessMenu(menu, MMB_MenuItemMap) # Enabled in List Mode and Image Mode def HandleRightMouse(): menu = "Viewer%t|Playback (P)|Switch Mode (C/RET)|Render-Reload (T)|Reload (R)|Quit (ESC)" ProcessMenu(menu, RMB_MenuItemMap) #====================================================================== KEYPRESSED = 1 KEYRELEASE = 0 EVENTTABLE_AllModes = { Draw.CKEY: {KEYPRESSED: SwitchMode}, Draw.RETKEY: {KEYPRESSED: SwitchMode}, Draw.LEFTARROWKEY: {KEYPRESSED: PreviousImage}, Draw.ZKEY: {KEYPRESSED: PreviousImage}, Draw.RIGHTARROWKEY: {KEYPRESSED: NextImage}, Draw.XKEY: {KEYPRESSED: NextImage}, Draw.QKEY: {KEYPRESSED: FirstImage}, Draw.UPARROWKEY: {KEYPRESSED: FirstImage}, Draw.WKEY: {KEYPRESSED: LastImage}, Draw.DOWNARROWKEY: {KEYPRESSED: LastImage}, Draw.RKEY: {KEYPRESSED: ReloadImages}, Draw.TKEY: {KEYPRESSED: RenderImage}, Draw.PKEY: {KEYPRESSED: AutoplayImages}, Draw.RIGHTMOUSE: {KEYPRESSED: HandleRightMouse}, Draw.LEFTMOUSE: {KEYPRESSED: HandleLeftMouse} } EVENTTABLE_ImageMode = { Draw.AKEY: {KEYPRESSED: ToggleImageAlpha}, Draw.SKEY: {KEYPRESSED: HandleImageScale}, Draw.MIDDLEMOUSE: {KEYPRESSED: HandleMiddleMouse} } EVENTTABLE_ListMode = { Draw.OKEY: {KEYPRESSED: lambda:Draw.PupMenu("Placeholder. Be gentle ")} } # Process the events registered with a particular event table # Returns 1 if event was found and 0 otherwise (try/except act as gaurd for unregistered events) def ProcessEvents(EventTable, event, value): try: EventTable[event][value]() return 1 except: return 0 # Handle events common to all modes. If this can process the event # return true to abort traversing the other modes def Event_AllModes(event, value): ProcessEvents(EVENTTABLE_AllModes, event, value) # Handle Image Mode events def Event_ImageMode(event, value): ProcessEvents(EVENTTABLE_ImageMode, event, value) # Handle List Mode events def Event_ListMode(event, value): ProcessEvents(EVENTTABLE_ListMode, event, value) #====================================================================== # Try running the global event table. If some event has run then redraw, or # else continue into the sub tables to cascade events def CALLBACK_HandleViewerEvents(event, value): if event == Draw.ESCKEY and value: Draw.PupMenu("Bye Bye") Draw.Exit() if not Event_AllModes(event, value): Viewer_EventCallbacks[currentmode](event, value) Draw.Redraw() # Call appropriate callback depending on current viewer mode def CALLBACK_DisplayViewer(): BGL.glClear(BGL.GL_COLOR_BUFFER_BIT) Viewer_DrawCallbacks[currentmode]() #====================================================================== # Load images and launch viewer numberofimages, imagelist = LoadImages() if imagelist != None: Viewer_DrawCallbacks = (Draw_ListMode, Draw_ImageMode, Draw_ReloadingMode) Viewer_EventCallbacks = (Event_ListMode, Event_ImageMode) Draw.Register(CALLBACK_DisplayViewer, CALLBACK_HandleViewerEvents, None) else: name = "ImageViewer Says - %t|No Metadata file exists. I am exiting" result = Draw.PupMenu(name)