Commit a60587f9 authored by Christopher Gearhart's avatar Christopher Gearhart

Now supports project names with spaces, and average frames now stays contained within Blender.

parent d11396b4
......@@ -106,6 +106,7 @@ def register():
bpy.props.lastRemotePath = None
bpy.props.needsUpdating = True
bpy.props.nameAveragedImage = None
bpy.props.imExtension = False
bpy.props.nameImOutputFiles = ""
bpy.props.animExtension = False
......@@ -142,6 +143,7 @@ def unregister():
del bpy.props.serverPrefs
del bpy.props.animFrameRange
del bpy.props.lastRemotePath
del bpy.props.nameAveragedImage
del bpy.props.imExtension
del bpy.props.animExtension
del bpy.props.needsUpdating
......
......@@ -237,31 +237,32 @@ class sendFrame(Operator):
numRenderedFiles = getNumRenderedFiles("image", [bpy.props.imFrame], None)
if numRenderedFiles > 0:
averaged = True
averageFrames(self, bpy.props.nameImOutputFiles)
bpy.props.nameAveragedImage = averageFrames(self, bpy.props.nameImOutputFiles)
else:
averaged = False
# calculate number of samples represented in averaged image
self.numSamples = self.sampleSize * self.avDict["numFrames"]
# open rendered image
if i == 0:
setRenderStatus("image", "Complete!")
if context.area.type == "IMAGE_EDITOR":
if averaged and not self.previewed:
averaged_image_filepath = os.path.join(getRenderDumpFolder(), "{fileName}_{frame}_average{extension}".format(fileName=bpy.props.nameImOutputFiles, frame=str(bpy.props.imFrame).zfill(4), extension=bpy.props.imExtension))
bpy.ops.image.open(filepath=averaged_image_filepath)
bpy.ops.image.reload()
if bpy.data.images.find(bpy.props.nameAveragedImage) >= 0:
# open rendered image in any open 'IMAGE_EDITOR' windows
for area in context.screen.areas:
if area.type == "IMAGE_EDITOR":
area.spaces.active.image = bpy.data.images[bpy.props.nameAveragedImage]
break
self.report({"INFO"}, "Render completed at {numSamples} samples! View the rendered image in your UV/Image_Editor".format(numSamples=str(self.numSamples)))
else:
# open preview image in UV/Image_Editor
changeContext(context, "IMAGE_EDITOR")
if averaged and not self.previewed:
averaged_image_filepath = os.path.join(getRenderDumpFolder(), "{fileName}_{frame}_average{extension}".format(fileName=bpy.props.nameImOutputFiles, frame=str(bpy.props.imFrame).zfill(4), extension=bpy.props.imExtension))
bpy.ops.image.open(filepath=averaged_image_filepath)
if bpy.data.images.find(bpy.props.nameAveragedImage) >= 0:
# open preview image in UV/Image_Editor
changeContext(context, "IMAGE_EDITOR")
for area in context.screen.areas:
if area.type == "IMAGE_EDITOR":
area.spaces.active.image = bpy.data.images[bpy.props.nameAveragedImage]
self.previewed = True
break
self.processes[1] = False
self.previewed = True
previewString = "Render preview loaded ({numSamples} samples)".format(numSamples=str(self.numSamples))
self.report({"INFO"}, previewString)
bpy.ops.image.reload()
appendViewable("image")
removeViewable("animation")
if i == 0:
......@@ -280,10 +281,10 @@ class sendFrame(Operator):
return{"PASS_THROUGH"}
def execute(self, context):
self.projectName = bpy.path.display_name_from_filepath(bpy.data.filepath)
self.projectName = bpy.path.display_name_from_filepath(bpy.data.filepath).replace(" ", "_")
scn = context.scene
#for testing purposes only (saves unsaved file as 'unsaved_file.blend')
# for testing purposes only (saves unsaved file as 'unsaved_file.blend')
if self.projectName == "":
self.projectName = "unsaved_file"
bpy.ops.wm.save_mainfile(filepath="{tempLocalDir}{projectName}.blend".format(tempLocalDir=scn.tempLocalDir, projectName=self.projectName))
......@@ -325,6 +326,7 @@ class sendFrame(Operator):
self.previewed = False
self.numSamples = 0
self.avDict = {"array":False, "numFrames":0}
self.averageIm = None
bpy.props.nameImOutputFiles = getNameOutputFiles()
bpy.props.imFrame = scn.frame_current
self.state = [1, 0] # initializes state for modal
......@@ -351,7 +353,7 @@ class sendFrame(Operator):
def cancel(self, context):
print("process cancelled")
cleanupCancelledRender(self, context)
cleanupCancelledRender(self, context, bpy.types.Scene.killPython)
class sendAnimation(Operator):
"""Render animation on remote servers""" # blender will use this as a tooltip for menu items and buttons.
......@@ -497,8 +499,8 @@ class sendAnimation(Operator):
return{"PASS_THROUGH"}
def execute(self, context): # ensure no other animation render processes are running
self.projectName = bpy.path.display_name_from_filepath(bpy.data.filepath)
def execute(self, context):
self.projectName = bpy.path.display_name_from_filepath(bpy.data.filepath).replace(" ", "_")
scn = context.scene
# for testing purposes only (saves unsaved file as 'unsaved_file.blend')
......@@ -558,7 +560,7 @@ class sendAnimation(Operator):
def cancel(self, context):
print("process cancelled")
cleanupCancelledRender(self, context)
cleanupCancelledRender(self, context, bpy.types.Scene.killPython)
class openRenderedImageInUI(Operator):
"""Open rendered image""" # blender will use this as a tooltip for menu items and buttons.
......@@ -567,11 +569,18 @@ class openRenderedImageInUI(Operator):
bl_options = {"REGISTER", "UNDO"} # enable undo for the operator.
def execute(self, context):
# open rendered image
changeContext(context, "IMAGE_EDITOR")
averaged_image_filepath = os.path.join(getRenderDumpFolder(), "{fileName}_{frame}_average{extension}".format(fileName=bpy.props.nameImOutputFiles, frame=str(bpy.props.imFrame).zfill(4), extension=bpy.props.imExtension))
bpy.ops.image.open(filepath=averaged_image_filepath)
bpy.ops.image.reload()
if bpy.data.images.find(bpy.props.nameAveragedImage) >= 0:
# open rendered image in UV/Image_Editor
changeContext(context, "IMAGE_EDITOR")
for area in context.screen.areas:
if area.type == "IMAGE_EDITOR":
area.spaces.active.image = bpy.data.images[bpy.props.nameAveragedImage]
elif bpy.props.nameAveragedImage and bpy.props.nameAveragedImage != "":
self.report({"ERROR"}, "Image could not be found: '{nameAveragedImage}'".format(nameAveragedImage=bpy.props.nameAveragedImage))
return{"CANCELLED"}
else:
self.report({"WARNING"}, "No rendered images could be found")
return{"CANCELLED"}
return{"FINISHED"}
......
......@@ -27,7 +27,7 @@ def getFrames(projectName, archiveFiles=False, frameRange=False):
f.close()
else:
includeDict = ""
archiveRsyncCommand = "rsync -qx --rsync-path='mkdir -p {dumpLocation}/backups/ && rsync' --remove-source-files {includeDict} --exclude='{nameOutputFiles}_????.???' --exclude='*_average.???' {dumpLocation}/* {dumpLocation}/backups/;".format(includeDict=includeDict, dumpLocation=dumpLocation, nameOutputFiles=getNameOutputFiles(), imExtension=bpy.props.imExtension)
archiveRsyncCommand = "rsync -qx --rsync-path='mkdir -p {dumpLocation}/backups/ && rsync' --remove-source-files {includeDict} --exclude='{nameOutputFiles}_????.???' {dumpLocation}/* {dumpLocation}/backups/;".format(includeDict=includeDict, dumpLocation=dumpLocation, nameOutputFiles=getNameOutputFiles(), imExtension=bpy.props.imExtension)
else:
archiveRsyncCommand = "mkdir -p {dumpLocation};".format(dumpLocation=dumpLocation)
......@@ -250,7 +250,7 @@ def getNameOutputFiles():
if scn.nameOutputFiles != "":
return scn.nameOutputFiles
else:
return bpy.path.display_name_from_filepath(bpy.data.filepath)
return bpy.path.display_name_from_filepath(bpy.data.filepath).replace(" ", "_")
def getNumRenderedFiles(jobType, frameRange=None, fileName=None):
if jobType == "image":
......@@ -267,7 +267,7 @@ def getNumRenderedFiles(jobType, frameRange=None, fileName=None):
numRenderedFiles = len(renderedFiles)
return numRenderedFiles
def cleanupCancelledRender(classObject, context):
def cleanupCancelledRender(classObject, context, killPython=True):
""" Kills running processes when render job cancelled """
wm = context.window_manager
......@@ -278,7 +278,7 @@ def cleanupCancelledRender(classObject, context):
classObject.processes[j].kill()
except:
pass
if bpy.context.scene.killPython:
if killPython:
subprocess.call("ssh -T -oStrictHostKeyChecking=no -x {login} 'killall -9 python'".format(login=bpy.props.serverPrefs["login"]), shell=True)
def changeContext(context, areaType):
......
......@@ -44,13 +44,15 @@ def averageFrames(classObject, outputFileName, verbose=0):
for image in imList:
if verbose >= 2:
print(image)
# load image
# load image # runtime per iteration:
im = bpy.data.images.load(image) # ~.0002 sec
data = list(im.pixels) # ~.10 sec
imarr = numpy.array(data, dtype=numpy.float) # ~.07 sec
arr = arr+imarr # ~.008 sec
bpy.data.images.remove(im, do_unlink=True) # ~.0002 sec
os.remove(image)
# save current info for averaged image to "self" object of class this function was called from
classObject.avDict["numFrames"] = N
classObject.avDict["array"] = arr
......@@ -60,12 +62,11 @@ def averageFrames(classObject, outputFileName, verbose=0):
if verbose >= 1:
print("Averaged successfully!")
# Generate final averaged image, add it to the main database, and save it
# Generate final averaged image and add it to the main database
imName = "{outputFileName}_{frame}_average{extension}".format(outputFileName=outputFileName, frame=str(bpy.props.imFrame).zfill(4), extension=bpy.props.imExtension)
if bpy.data.images.find(imName) < 0:
new = bpy.data.images.new(imName, w, h, alpha)
else:
new = bpy.data.images[imName]
new.pixels = arr.tolist()
new.filepath_raw = "{renderedFramesPath}{imName}".format(renderedFramesPath=renderedFramesPath, imName=imName)
new.save()
return imName
......@@ -11,10 +11,6 @@ def jobIsValid(jobType, classObject):
if classObject.projectName == "":
jobValidityDict = {"valid":False, "errorType":"WARNING", "errorMessage":"RENDER FAILED: You have not saved your project file. Please save it before attempting to render."}
# verify that project name contains no spaces
elif " " in classObject.projectName:
jobValidityDict = {"valid":False, "errorType":"ERROR", "errorMessage":"RENDER ABORTED: Please remove ' ' (spaces) from the project file name."}
# verify that a camera exists in the scene
elif bpy.context.scene.camera is None:
jobValidityDict = {"valid":False, "errorType":"ERROR", "errorMessage":"RENDER FAILED: No camera in scene."}
......
......@@ -59,6 +59,11 @@ def setupServerPrefs():
extension = readFileFor(serverFile, "EXTENSION").replace("\"", "")
except:
return {"valid":False, "errorMessage":invalidEntry("EXTENSION")}
try:
email = readFileFor(serverFile, "EMAIL ADDRESS").replace("\"", "")
except:
return {"valid":False, "errorMessage":invalidEntry("EMAIL ADDRESS")}
# build SSH login information
login = "{username}@{hostServer}{extension}".format(username=username, hostServer=hostServer, extension=extension)
......@@ -87,7 +92,7 @@ def setupServerPrefs():
except:
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}
return {"valid":True, "servers":servers, "login":login, "path":path, "hostConnection":hostConnection, "email":email}
def writeServersFile(serverDict, serverGroups):
f = open(os.path.join(getLibraryPath(), "to_host_server", "servers.txt"), "w")
......
......@@ -192,8 +192,6 @@ def main():
if verbose >= 3:
pflush("\nJob exit statuses:")
jhm.print_jobs_status()
# if args.average_results:
# averageFrames(localResultsPath, projectName, verbose)
# report on the success/failure of the tasks
endTime = time.time()
......
......@@ -234,63 +234,3 @@ def stopWatch(value):
Seconds = str(Seconds).zfill(2)
return "{Days};{Hours}:{Minutes};{Seconds}".format(Days=Days, Hours=Hours, Minutes=Minutes, Seconds=Seconds)
# def averageFrames(renderedFramesPath, projectName, verbose=0):
# """ Averages each pixel from all final rendered images to present one render result """
#
# if verbose >= 3:
# print("Averaging images...")
#
# # ensure 'renderedFramesPath' has trailing "/"
# if not renderedFramesPath.endswith("/"):
# renderedFramesPath += "/"
#
# # get image files to average from 'renderedFramesPath'
# allFiles = os.listdir(renderedFramesPath)
# supportedFileTypes = ["png", "tga", "tif", "jpg", "jp2", "bmp", "cin", "dpx", "exr", "hdr", "rgb"]
# imList = [filename for filename in allFiles if (filename[-3:] in supportedFileTypes and filename[-11:-4] != "average" and "_seed-" in filename)]
# imList = [os.path.join(renderedFramesPath, im) for im in imList]
# if not imList:
# sys.stderr.write("No valid image files to average.")
# sys.exit(1)
# extension = imList[0][-3:]
#
# # Assuming all images are the same size, get dimensions of first image
# imRef = Image.open(imList[0])
# w, h = imRef.size
# mode = imRef.mode
# N = len(imList)
#
# # Create a numpy array of floats to store the average
# if mode == "RGB":
# arr = numpy.zeros((h, w, 3), numpy.float)
# elif mode == "RGBA":
# arr = numpy.zeros((h, w, 4), numpy.float)
# elif mode == "L":
# arr = numpy.zeros((h, w), numpy.float)
# else:
# sys.stderr.write("Unsupported image type. Supported types: ['RGB', 'RGBA', 'BW']")
# sys.exit(1)
#
# # Build up average pixel intensities, casting each image as an array of floats
# if verbose >= 3:
# print("Averaging the following images:")
# for im in imList:
# # load image
# if verbose >= 3:
# print(im)
# imarr = numpy.array(Image.open(im), dtype=numpy.float)
# arr = arr+imarr/N
#
# # Round values in array and cast as 8-bit integer
# arr = numpy.array(numpy.round(arr), dtype=numpy.uint8)
#
# # Print details
# if verbose >= 2:
# print("Averaged successfully!")
#
# # Generate, save and preview final image
# out = Image.fromarray(arr, mode=mode)
# if verbose >= 3:
# pflush("saving averaged image...")
# out.save(os.path.join(renderedFramesPath, "{projectName}_average.{extension}".format(extension=extension, projectName=projectName)))
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