Commit 462773b5 authored by Christopher Gearhart's avatar Christopher Gearhart

Missing frames now displayed and frame range (also fixed a few minor bugs)

parent a60587f9
......@@ -6,22 +6,19 @@ Scripts and associated files for rendering from Blender files on CSE remote serv
* Features:
* Clean UI for sending frames to servers and viewing them within Blender
* Mid-render previews/status updates available with 'SHIFT + P'
* Abort render ('ESC')
* Abort render with 'ESC'
* NOTE: Files are auto-packed into the .blend file with each render process
* Required packages:
* Local: rsync
* Host Server: rsync, python
* Client Servers: blender
* Future improvements:
* Remove restriction from using spaces in project name
* Handle known errors
* Detect when SSH keys have not been set up
* Detect when required packages have not been installed on servers (see 'which' command)
* Detect if you've run out of disk space
* Don't pack files into the blend file?
* 'blender_task' module
* Integrate max server load functionality to set cap on how many frames will be rendered
* If servers available, re-render current jobs until one is finished, then kill the rest
* Re-render failed frames automatically
* Handle known errors (see 'Handle known errors' list above)
* Handle known errors (ssh keys, necessary packages not installed, run out of disk space, etc.)
* Send tiles to the various servers based on computer speed
......@@ -5,11 +5,12 @@ import subprocess
import os
import sys
import fnmatch
import itertools
import operator
from .setupServers import *
def getFrames(projectName, archiveFiles=False, frameRange=False):
""" rsync rendered frames from host server to local machine """
scn = bpy.context.scene
basePath = bpy.path.abspath("//")
dumpLocation = getRenderDumpFolder()
......@@ -40,7 +41,6 @@ def getFrames(projectName, archiveFiles=False, frameRange=False):
def buildFrameRangesString(frameRanges):
""" builds frame range list of lists/ints from user-entered frameRanges string """
frameRangeList = frameRanges.replace(" ", "").split(",")
newFrameRangeList = []
invalidDict = {"valid":False, "string":None}
......@@ -68,7 +68,6 @@ def buildFrameRangesString(frameRanges):
def copyProjectFile(projectName, compress):
""" copies project file from local machine to host server """
scn = bpy.context.scene
saveToPath = "{tempLocalDir}{projectName}.blend".format(tempLocalDir=scn.tempLocalDir, projectName=projectName)
......@@ -96,7 +95,6 @@ def copyFiles():
def renderFrames(frameRange, projectName, jobsPerFrame=False):
""" calls 'blender_task' on host server """
scn = bpy.context.scene
# defines the name of the output files generated by 'blender_task'
extraFlags = " -O {nameOutputFiles}".format(nameOutputFiles=getNameOutputFiles())
......@@ -130,7 +128,6 @@ def removeViewable(typeOfRender):
def expandFrames(frame_range):
""" Helper function takes frame range string and returns list with frame ranges expanded """
frames = []
for i in frame_range:
if type(i) == list:
......@@ -142,18 +139,29 @@ def expandFrames(frame_range):
return list(set(frames))
def intsToFrameRanges(intsList):
""" turns list of ints to list of frame ranges """
frameRangesS = ""
i = 0
while i < len(intsList) - 1:
s = intsList[i] # start index
e = s # end index
while i < len(intsList) - 1 and intsList[i + 1] - intsList[i] == 1:
e += 1
i += 1
frameRangesS += "{s},".format(s=s) if s == e else "{s}-{e},".format(s=s, e=e)
i += 1
return frameRangesS[:-1]
def listMissingFiles(filename, frameRange):
""" lists all missing files from local render dump directory """
dumpFolder = getRenderDumpFolder()
compList = expandFrames(json.loads(frameRange))
if not os.path.exists(dumpFolder):
errorMsg = "The folder does not exist: {dumpFolder}/".format(dumpFolder=dumpFolder)
return str(compList)[1:-1]
allFiles = os.listdir(dumpFolder)
......@@ -165,16 +173,15 @@ def listMissingFiles(filename, frameRange):
for f in allFiles:
if "_average." not in f and not fnmatch.fnmatch(f, "*_seed-*_????.???") and f[:len(filename)] == filename:
# compare lists to determine which frames are missing from imlist
missingF = [i for i in compList if i not in imList]
# convert list of ints to string with frame ranges
missingFR = intsToFrameRanges(missingF)
# return the list of missing frames as string, omitting the open and close brackets
return str(missingF)[1:-1]
return missingFR
def handleError(classObject, errorSource, i="Not Provided"):
errorMessage = False
# if error message available, print in Info window and define errorMessage string
if i == "Not Provided":
if classObject.process.stderr != None:
......@@ -210,7 +217,6 @@ def handleBTError(classObject, i="Not Provided"):
def setFrameRangesDict(classObject):
scn = bpy.context.scene
if scn.frameRanges == "":
classObject.frameRangesDict = {"string":"[[{frameStart},{frameEnd}]]".format(frameStart=str(scn.frame_start), frameEnd=str(scn.frame_end))}
......@@ -222,7 +228,6 @@ def setFrameRangesDict(classObject):
def getRenderDumpFolder():
dumpLoc = bpy.context.scene.renderDumpLoc
# setup the render dump folder based on user input
if dumpLoc.startswith("//"):
dumpLoc = os.path.join(bpy.path.abspath("//"), dumpLoc[2:])
......@@ -231,11 +236,9 @@ def getRenderDumpFolder():
# if no user input, use default render location
dumpLoc = os.path.join(bpy.path.abspath("//"), "render-dump")
# check to make sure dumpLoc exists on local machine
if not os.path.exists(dumpLoc):
return dumpLoc
def getRunningStatuses():
......@@ -269,7 +272,6 @@ def getNumRenderedFiles(jobType, frameRange=None, fileName=None):
def cleanupCancelledRender(classObject, context, killPython=True):
""" Kills running processes when render job cancelled """
wm = context.window_manager
for j in range(len(classObject.processes)):
......@@ -304,7 +306,7 @@ def updateServerPrefs():
if bpy.props.serverPrefs != oldServerPrefs:
# verify host server login, built from user entries, correspond to a responsive server
try:"ssh -T -oStrictHostKeyChecking=no -x {login} 'echo hi'".format(login=bpy.props.serverPrefs["login"]), shell=True)"ssh -T -oBatchMode=yes -oStrictHostKeyChecking=no -x {login} 'echo hi'".format(login=bpy.props.serverPrefs["login"]), shell=True)
return {"valid":False, "errorMessage":"ssh to '{login}' failed. Check your settings and ensure ssh keys are setup".format(login=bpy.props.serverPrefs["login"])}
......@@ -59,11 +59,6 @@ def setupServerPrefs():
extension = readFileFor(serverFile, "EXTENSION").replace("\"", "")
return {"valid":False, "errorMessage":invalidEntry("EXTENSION")}
email = readFileFor(serverFile, "EMAIL ADDRESS").replace("\"", "")
return {"valid":False, "errorMessage":invalidEntry("EMAIL ADDRESS")}
# build SSH login information
login = "{username}@{hostServer}{extension}".format(username=username, hostServer=hostServer, extension=extension)
......@@ -92,7 +87,7 @@ def setupServerPrefs():
return {"valid":False, "errorMessage":"Could not load dictionary. Please make sure you've entered a valid dictionary and check for syntax errors"}
return {"valid":True, "servers":servers, "login":login, "path":path, "hostConnection":hostConnection, "email":email}
return {"valid":True, "servers":servers, "login":login, "path":path, "hostConnection":hostConnection}
def writeServersFile(serverDict, serverGroups):
f = open(os.path.join(getLibraryPath(), "to_host_server", "servers.txt"), "w")
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment