Commit c8ce9c97 authored by bblanimation's avatar bblanimation

Bug fixes

parent f6d40588
......@@ -124,8 +124,10 @@ def register():
min=100, max=9999,
default=1000)
bpy.types.Scene.renderType = []
bpy.types.Scene.renderStatus = {"animation":"None", "image":"None"}
bpy.types.Scene.imagePreviewAvailable = BoolProperty(default=False)
bpy.types.Scene.animPreviewAvailable = BoolProperty(default=False)
bpy.types.Scene.imageRenderStatus = StringProperty(name="Image Render Status", default="None")
bpy.types.Scene.animRenderStatus = StringProperty(name="Image Render Status", default="None")
# Initialize server and login variables
bpy.types.Scene.serverGroups = EnumProperty(
......@@ -134,19 +136,18 @@ def register():
description="Choose which hosts to use for render processes",
items=[("All Servers", "All Servers", "Render on all servers")],
default="All Servers")
bpy.props.lastServerGroup = "All Servers"
bpy.types.Scene.lastServerGroup = StringProperty(name="Last Server Group", default="All Servers")
bpy.props.serverPrefs = {"servers":None, "login":None, "path":None, "hostConnection":None}
bpy.types.Scene.availableServers = IntProperty(name="Available Servers", default=0)
bpy.types.Scene.offlineServers = IntProperty(name="Offline Servers", default=0)
bpy.props.lastRemotePath = None
bpy.props.needsUpdating = True
bpy.types.Scene.needsUpdating = BoolProperty(default=True)
bpy.props.nameAveragedImage = ""
bpy.props.imExtension = False
bpy.props.nameImOutputFiles = ""
bpy.props.animExtension = False
bpy.props.imFrame = -1
bpy.props.animFrameRange = []
bpy.types.Scene.nameAveragedImage = StringProperty(default="")
bpy.types.Scene.nameImOutputFiles = StringProperty(default="")
bpy.types.Scene.imExtension = StringProperty(default="")
bpy.types.Scene.animExtension = StringProperty(default="")
bpy.types.Scene.imFrame = IntProperty(default=-1)
bpy.props.animFrameRange = None
# handle the keymap
wm = bpy.context.window_manager
......@@ -175,20 +176,21 @@ def unregister():
Scn = bpy.types.Scene
del bpy.props.animFrameRange
del bpy.props.imFrame
del bpy.props.animExtension
del bpy.props.nameImOutputFiles
del bpy.props.imExtension
del bpy.props.nameAveragedImage
del bpy.props.needsUpdating
del bpy.props.lastRemotePath
del Scn.imFrame
del Scn.animExtension
del Scn.nameImOutputFiles
del Scn.imExtension
del Scn.nameAveragedImage
del bpy.props.serverPrefs
del Scn.offlineServers
del Scn.availableServers
del bpy.props.serverPrefs
del bpy.props.lastServerGroup
del Scn.needsUpdating
del Scn.lastServerGroup
del Scn.serverGroups
del Scn.renderStatus
del Scn.renderType
del Scn.animRenderStatus
del Scn.imageRenderStatus
del Scn.animPreviewAvailable
del Scn.imagePreviewAvailable
del Scn.maxSamples
del Scn.samplesPerFrame
del Scn.timeout
......
......@@ -19,4 +19,4 @@ Created by Christopher Gearhart
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
__all__ = ["editRemoteServersDict", "missingFramesActions", "openRenderInUI", "refreshNumAvailableServers", "reportError", "sendAnimation", "sendFrame"]
__all__ = ["editRemoteServersDict", "missingFramesActions", "openRenderInUI", "refreshServers", "reportError", "sendAnimation", "sendFrame"]
......@@ -40,12 +40,13 @@ class editRemoteServersDict(Operator):
bl_options = {"REGISTER", "UNDO"} # enable undo for the operator.
def execute(self, context):
scn = bpy.context.scene
changeContext(context, "TEXT_EDITOR")
try:
libraryServersPath = os.path.join(getLibraryPath(), "servers")
bpy.ops.text.open(filepath=os.path.join(libraryServersPath, "remoteServers.txt"))
self.report({"INFO"}, "Opened 'remoteServers.txt'")
bpy.props.needsUpdating = True
scn.needsUpdating = True
except:
self.report({"ERROR"}, "ERROR: Could not open 'remoteServers.txt'. If the problem persists, try reinstalling the add-on.")
return{"FINISHED"}
......@@ -40,15 +40,16 @@ class openRenderedImageInUI(Operator):
bl_options = {"REGISTER", "UNDO"} # enable undo for the operator.
def execute(self, context):
scn = bpy.context.scene
try:
if bpy.data.images.find(bpy.props.nameAveragedImage) >= 0:
if bpy.data.images.find(scn.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 != "":
self.report({"ERROR"}, "Image could not be found: '{nameAveragedImage}'".format(nameAveragedImage=bpy.props.nameAveragedImage))
area.spaces.active.image = bpy.data.images[scn.nameAveragedImage]
elif scn.nameAveragedImage != "":
self.report({"ERROR"}, "Image could not be found: '{nameAveragedImage}'".format(nameAveragedImage=scn.nameAveragedImage))
return{"CANCELLED"}
else:
self.report({"WARNING"}, "No rendered images could be found")
......@@ -68,6 +69,7 @@ class openRenderedAnimationInUI(Operator):
def execute(self, context):
scn = bpy.context.scene
try:
self.frameRangesDict = buildFrameRangesString(context.scene.frameRanges)
......@@ -79,7 +81,7 @@ class openRenderedAnimationInUI(Operator):
self.renderDumpFolder = getRenderDumpFolder()
image_sequence_filepath = "{dumpFolder}/".format(dumpFolder=self.renderDumpFolder)
for frame in bpy.props.animFrameRange:
image_filename = "{fileName}_{frame}{extension}".format(fileName=getNameOutputFiles(), frame=str(frame).zfill(4), extension=bpy.props.animExtension)
image_filename = "{fileName}_{frame}{extension}".format(fileName=getNameOutputFiles(), frame=str(frame).zfill(4), extension=scn.animExtension)
if os.path.isfile(os.path.join(image_sequence_filepath, image_filename)):
bpy.ops.clip.open(directory=image_sequence_filepath, files=[{"name":image_filename}])
openedFile = image_filename
......
......@@ -32,8 +32,9 @@ from bpy.props import *
from ..functions import *
from ..functions.averageFrames import *
from ..functions.jobIsValid import *
from ..functions.common import *
class refreshNumAvailableServers(Operator):
class refreshServers(Operator):
"""Attempt to connect to all servers through host server""" # blender will use this as a tooltip for menu items and buttons.
bl_idname = "render_farm.refresh_num_available_servers" # unique identifier for buttons and menu items to reference.
bl_label = "Refresh Available Servers" # display name in the interface.
......@@ -46,13 +47,15 @@ class refreshNumAvailableServers(Operator):
return False
return True
def checkNumAvailServers(self):
@classmethod
def checkNumAvailServers(cls):
scn = bpy.context.scene
command = "ssh -T -oStrictHostKeyChecking=no -x {login} 'python {remotePath}blender_task -Hv --connection_timeout {timeout} --hosts_file {remotePath}servers.txt'".format(login=bpy.props.serverPrefs["login"], remotePath=bpy.props.serverPrefs["path"], timeout=scn.timeout)
process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
return process
def updateAvailServerInfo(self):
@classmethod
def updateAvailServerInfo(cls, process):
scn = bpy.context.scene
available = None
......@@ -60,7 +63,7 @@ class refreshNumAvailableServers(Operator):
while available is None:
try:
rl = self.process.stdout.readline()
rl = process.stdout.readline()
line1 = rl.decode("ASCII").replace("\\n", "")
available = json.loads(line1.replace("'", "\""))
except:
......@@ -68,7 +71,7 @@ class refreshNumAvailableServers(Operator):
available = None
while offline is None:
try:
rl = self.process.stdout.readline()
rl = process.stdout.readline()
line2 = rl.decode("ASCII").replace("\\n", "")
offline = json.loads(line2.replace("'", "\""))
except:
......@@ -77,8 +80,7 @@ class refreshNumAvailableServers(Operator):
scn.availableServers = len(available)
scn.offlineServers = len(offline)
for a in bpy.context.screen.areas:
a.tag_redraw()
tag_redraw_areas()
def modal(self, context, event):
try:
......@@ -108,13 +110,13 @@ class refreshNumAvailableServers(Operator):
# check number of available servers via host server
if self.state == 1:
bpy.props.needsUpdating = False
scn.needsUpdating = False
self.state += 1
self.process = self.checkNumAvailServers()
return{"PASS_THROUGH"}
elif self.state == 2:
self.updateAvailServerInfo()
self.updateAvailServerInfo(self.process)
scn = context.scene
self.report({"INFO"}, "Refresh process completed ({num} servers available)".format(num=str(scn.availableServers)))
return{"FINISHED"}
......@@ -127,6 +129,30 @@ class refreshNumAvailableServers(Operator):
handle_exception()
return{"CANCELLED"}
@classmethod
def refreshServersBlock(cls, statusType=None):
scn = bpy.context.scene
if scn.needsUpdating or scn.lastServerGroup != scn.serverGroups:
scn.lastServerGroup = scn.serverGroups
updateStatus = updateServerPrefs()
if not updateStatus["valid"]:
return False
process = copyFiles()
while process.returncode == None:
process.poll()
if process.returncode != 0:
return False
process = cls.checkNumAvailServers()
while process.returncode == None:
process.poll()
if process.returncode != 0:
return False
cls.updateAvailServerInfo(process)
scn.needsUpdating = False
return True
def execute(self, context):
try:
print("\nRunning 'checkNumAvailServers' function...")
......@@ -134,14 +160,13 @@ class refreshNumAvailableServers(Operator):
# start initial process
self.state = 1 # initializes state for modal
if bpy.props.needsUpdating or bpy.props.lastServerGroup != scn.serverGroups:
bpy.props.lastServerGroup = scn.serverGroups
if scn.needsUpdating or scn.lastServerGroup != scn.serverGroups:
scn.lastServerGroup = scn.serverGroups
updateStatus = updateServerPrefs()
if not updateStatus["valid"]:
self.report({"ERROR"}, updateStatus["errorMessage"])
return{"CANCELLED"}
self.process = copyFiles()
bpy.props.lastRemotePath = bpy.props.serverPrefs["path"]
else:
self.process = self.checkNumAvailServers()
self.state += 1
......
......@@ -38,6 +38,7 @@ class reportError(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
scn = bpy.context.scene
try:
# set up file paths
libraryServersPath = os.path.join(getLibraryPath(), "error_log")
......@@ -49,7 +50,7 @@ class reportError(bpy.types.Operator):
bpy.ops.text.open(filepath=os.path.join(libraryServersPath, "Render_Farm_error_report.txt"))
bpy.context.space_data.show_word_wrap = True
self.report({"INFO"}, "Opened 'Render_Farm_error_report.txt'")
bpy.props.needsUpdating = True
scn.needsUpdating = True
except:
self.report({"ERROR"}, "ERROR: Could not open 'Render_Farm_error_report.txt'. If the problem persists, try reinstalling the add-on.")
except:
......
......@@ -29,6 +29,7 @@ import time
from bpy.types import Operator
from bpy.props import *
from .refreshServers import *
from ..functions import *
from ..functions.averageFrames import *
from ..functions.jobIsValid import *
......@@ -137,7 +138,7 @@ class sendAnimation(Operator):
# start render process from the defined start and end frames
elif self.state[i] == 2:
bpy.props.needsUpdating = False
scn.needsUpdating = False
self.processes[i] = renderFrames(str(self.expandedFrameRange), self.projectName)
setRenderStatus("animation", "Rendering...")
self.state[i] += 1
......@@ -160,7 +161,7 @@ class sendAnimation(Operator):
else:
viewString = ""
self.report({"INFO"}, "Render completed for {numCompleted}/{numSent} frames{viewString}".format(numCompleted=numCompleted, numSent=len(bpy.props.animFrameRange), viewString=viewString))
appendViewable("animation")
scn.animPreviewAvailable = True
if i == 1:
self.processes[1] = False
self.statusChecked = True
......@@ -192,13 +193,14 @@ class sendAnimation(Operator):
self.report({"WARNING"}, "Render in progress...")
return{"CANCELLED"}
elif scn.availableServers == 0:
self.report({"WARNING"}, "No servers available. Try refreshing.")
return{"CANCELLED"}
serversRefreshed = refreshServers.refreshServersBlock(statusType="animation")
if not serversRefreshed:
self.report({"WARNING"}, "Servers could not be auto-refreshed. Try manual refreshing (Ctrl R).")
return{"CANCELLED"}
# for testing purposes only (saves unsaved file as 'unsaved_file.blend')
# for testing purposes only (saves unsaved file)
if self.projectName == "":
self.projectName = "unsaved_file"
bpy.ops.wm.save_mainfile(filepath="{tempLocalDir}{projectName}.blend".format(tempLocalDir=scn.tempLocalDir, projectName=self.projectName))
self.projectName = "rf_unsaved_file"
print("\nRunning sendAnimation function...")
......@@ -218,7 +220,7 @@ class sendAnimation(Operator):
return{"CANCELLED"}
# set the file extension and frame range for use with 'open animation' button
bpy.props.animExtension = bpy.context.scene.render.file_extension
scn.animExtension = bpy.context.scene.render.file_extension
bpy.props.animFrameRange = self.expandedFrameRange
# start initial render process
......@@ -232,8 +234,8 @@ class sendAnimation(Operator):
self.numFrames = str(int(scn.frame_end) - int(scn.frame_start))
self.statusChecked = False
self.state = [1, 0] # initializes state for modal
if bpy.props.needsUpdating or bpy.props.lastServerGroup != scn.serverGroups:
bpy.props.lastServerGroup = scn.serverGroups
if scn.needsUpdating or scn.lastServerGroup != scn.serverGroups:
scn.lastServerGroup = scn.serverGroups
updateStatus = updateServerPrefs()
if not updateStatus["valid"]:
self.report({"ERROR"}, updateStatus["errorMessage"])
......
......@@ -29,6 +29,7 @@ import time
from bpy.types import Operator
from bpy.props import *
from .refreshServers import *
from ..functions import *
from ..functions.averageFrames import *
from ..functions.jobIsValid import *
......@@ -137,9 +138,9 @@ class sendFrame(Operator):
# start render process at current frame
elif self.state[i] == 2:
bpy.props.needsUpdating = False
scn.needsUpdating = False
jobsPerFrame = scn.maxSamples // self.sampleSize
self.processes[i] = renderFrames(str([bpy.props.imFrame]), self.projectName, jobsPerFrame)
self.processes[i] = renderFrames(str([scn.imFrame]), self.projectName, jobsPerFrame)
self.state[i] += 1
setRenderStatus("image", "Rendering...")
return{"PASS_THROUGH"}
......@@ -157,12 +158,12 @@ class sendFrame(Operator):
# average the rendered frames if there are new frames to average
elif self.state[i] == 4:
# only average if there are new frames to average
numRenderedFiles = getNumRenderedFiles("image", [bpy.props.imFrame], None)
numRenderedFiles = getNumRenderedFiles("image", [scn.imFrame], None)
if numRenderedFiles > 0:
averaged = True
aveName = averageFrames(self, bpy.props.nameImOutputFiles)
aveName = averageFrames(self, scn.nameImOutputFiles)
if aveName != None:
bpy.props.nameAveragedImage = aveName
scn.nameAveragedImage = aveName
else:
averaged = False
else:
......@@ -171,11 +172,12 @@ class sendFrame(Operator):
self.numSamples = self.sampleSize * self.avDict["numFrames"]
if i == 0:
setRenderStatus("image", "Complete!")
if bpy.data.images.find(bpy.props.nameAveragedImage) >= 0:
print(scn.nameAveragedImage)
if bpy.data.images.find(scn.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]
area.spaces.active.image = bpy.data.images[scn.nameAveragedImage]
break
self.report({"INFO"}, "Render completed at {num} samples! View the rendered image in your UV/Image_Editor".format(num=str(self.numSamples)))
elif self.numSamples == 0:
......@@ -183,19 +185,19 @@ class sendFrame(Operator):
self.previewed = True
self.processes[1] = False
else:
if bpy.data.images.find(bpy.props.nameAveragedImage) >= 0:
if bpy.data.images.find(scn.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]
area.spaces.active.image = bpy.data.images[scn.nameAveragedImage]
self.previewed = True
break
self.processes[1] = False
previewString = "Render preview loaded ({num} samples)".format(num=str(self.numSamples))
self.report({"INFO"}, previewString)
appendViewable("image")
removeViewable("animation")
scn.imagePreviewAvailable = True
scn.animPreviewAvailable = False
if i == 0:
if self.renderCancelled:
setRenderStatus("image", "Cancelled")
......@@ -231,13 +233,14 @@ class sendFrame(Operator):
self.report({"WARNING"}, "Render in progress...")
return{"CANCELLED"}
elif scn.availableServers == 0:
self.report({"WARNING"}, "No servers available. Try refreshing.")
return{"CANCELLED"}
serversRefreshed = refreshServers.refreshServersBlock(statusType="image")
if not serversRefreshed:
self.report({"WARNING"}, "Servers could not be auto-refreshed. Try manual refreshing (Ctrl R).")
return{"CANCELLED"}
# for testing purposes only (saves unsaved file as 'unsaved_file.blend')
# for testing purposes only (saves unsaved file)
if self.projectName == "":
self.projectName = "unsaved_file"
bpy.ops.wm.save_mainfile(filepath="{tempLocalDir}{projectName}.blend".format(tempLocalDir=scn.tempLocalDir, projectName=self.projectName))
self.projectName = "rf_unsaved_file"
# ensure the job won't break the script
if not jobIsValid("image", self):
......@@ -246,7 +249,7 @@ class sendFrame(Operator):
print("\nRunning sendFrame function...")
# set the file extension for use with 'open image' button
bpy.props.imExtension = scn.render.file_extension
scn.imExtension = scn.render.file_extension
# Store current sample size for use in computing render results
self.sampleSize = scn.samplesPerFrame
......@@ -262,16 +265,15 @@ class sendFrame(Operator):
self.numSamples = 0
self.avDict = {"array":False, "numFrames":0}
self.averageIm = None
bpy.props.nameImOutputFiles = getNameOutputFiles()
bpy.props.imFrame = scn.frame_current
scn.nameImOutputFiles = getNameOutputFiles()
scn.imFrame = scn.frame_current
self.state = [1, 0] # initializes state for modal
if bpy.props.needsUpdating or bpy.props.lastServerGroup != scn.serverGroups:
bpy.props.lastServerGroup = scn.serverGroups
if scn.needsUpdating or scn.lastServerGroup != scn.serverGroups:
scn.lastServerGroup = scn.serverGroups
updateStatus = updateServerPrefs()
if not updateStatus["valid"]:
self.report({"ERROR"}, updateStatus["errorMessage"])
return{"CANCELLED"}
bpy.props.lastRemotePath = bpy.props.serverPrefs["path"]
else:
self.state[0] += 1
self.processes = [copyProjectFile(self.projectName, scn.compress), False]
......
......@@ -65,7 +65,7 @@ def getFrames(projectName, archiveFiles=False, frameRange=False):
if frameRange:
fileStrings = ""
for frame in frameRange:
fileStrings += "{nameOutputFiles}_{frameNum}{animExtension}\n".format(nameOutputFiles=getNameOutputFiles(), frameNum=str(frame).zfill(4), animExtension=bpy.props.animExtension)
fileStrings += "{nameOutputFiles}_{frameNum}{animExtension}\n".format(nameOutputFiles=getNameOutputFiles(), frameNum=str(frame).zfill(4), animExtension=scn.animExtension)
outFilePath = os.path.join(dumpLocation, "includeList.txt")
f = open(outFilePath, "w")
f.write(fileStrings)
......@@ -73,7 +73,7 @@ def getFrames(projectName, archiveFiles=False, frameRange=False):
f.close()
else:
includeDict = ""
archiveRsyncCommand = "rsync -aqx --rsync-path='mkdir -p {dumpLocation}/backups/ && rsync' --remove-source-files {includeDict} --exclude='{nameOutputFiles}_????.???' --exclude='backups/' '{dumpLocation}/' '{dumpLocation}/backups/';".format(includeDict=includeDict, dumpLocation=dumpLocation.replace(" ", "\\ "), nameOutputFiles=getNameOutputFiles(), imExtension=bpy.props.imExtension)
archiveRsyncCommand = "rsync -aqx --rsync-path='mkdir -p {dumpLocation}/backups/ && rsync' --remove-source-files {includeDict} --exclude='{nameOutputFiles}_????.???' --exclude='backups/' '{dumpLocation}/' '{dumpLocation}/backups/';".format(includeDict=includeDict, dumpLocation=dumpLocation.replace(" ", "\\ "), nameOutputFiles=getNameOutputFiles(), imExtension=scn.imExtension)
else:
archiveRsyncCommand = "mkdir -p {dumpLocation};".format(dumpLocation=dumpLocation.replace(" ", "\\ "))
print(archiveRsyncCommand)
......@@ -120,25 +120,25 @@ def copyProjectFile(projectName, compress):
scn = bpy.context.scene
bpy.ops.file.pack_all()
saveToPath = "{tempLocalDir}{projectName}.blend".format(tempLocalDir=scn.tempLocalDir, projectName=projectName)
if compress:
bpy.ops.wm.save_as_mainfile(filepath=saveToPath, compress=True, copy=True)
if projectName == "rf_unsaved_file":
# saves unsaved file as 'rf_unsaved_file.blend'
bpy.ops.wm.save_mainfile(filepath="{tempLocalDir}rf_unsaved_file.blend".format(tempLocalDir=scn.tempLocalDir), compress=compress)
else:
bpy.ops.wm.save_as_mainfile(filepath=saveToPath, copy=True)
bpy.ops.wm.save_as_mainfile(filepath=saveToPath, compress=compress, copy=True)
# copies blender project file to host server
rsyncCommand = "rsync --copy-links --progress --rsync-path='mkdir -p {remotePath}{projectName}/toRemote/ && rsync' -qazx --include={projectName}.blend --exclude='*' -e 'ssh -T -oCompression=no -oStrictHostKeyChecking=no -x' '{tempLocalDir}' '{login}:{remotePath}{projectName}/toRemote/'".format(remotePath=bpy.props.serverPrefs["path"], projectName=projectName, tempLocalDir=scn.tempLocalDir, login=bpy.props.serverPrefs["login"])
rsyncCommand = "rsync --copy-links --progress --rsync-path='mkdir -p {remotePath}{projectName}/toRemote/ && rsync' -qazx --include={projectName}.blend --exclude='*' -e 'ssh -T -oCompression=no -oStrictHostKeyChecking=no -x' '{tempLocalDir}' '{login}:{remotePath}{projectName}/toRemote/'".format(remotePath=bpy.props.serverPrefs["path"].replace(" ", "\\ "), projectName=projectName, tempLocalDir=scn.tempLocalDir, login=bpy.props.serverPrefs["login"])
process = subprocess.Popen(rsyncCommand, shell=True)
return process
def copyFiles():
""" copies necessary files to host server """
scn = bpy.context.scene
# write out the servers file for remote servers
writeServersFile(bpy.props.serverPrefs["servers"], scn.serverGroups)
# rsync setup files to host server ('servers.txt', 'blender_p.py', 'blender_task' module)
rsyncCommand = "rsync -qax -e 'ssh -T -oCompression=no -oStrictHostKeyChecking=no -x' --exclude='*.zip' --rsync-path='mkdir -p {remotePath} && rsync' '{to_host_server}/' '{login}:{remotePath}'".format(remotePath=bpy.props.serverPrefs["path"], to_host_server=os.path.join(getLibraryPath(), "to_host_server"), login=bpy.props.serverPrefs["login"])
rsyncCommand = "rsync -qax -e 'ssh -T -oCompression=no -oStrictHostKeyChecking=no -x' --exclude='*.zip' --rsync-path='mkdir -p {remotePath} && rsync' '{to_host_server}/' '{login}:{remotePath}'".format(remotePath=bpy.props.serverPrefs["path"].replace(" ", "\\ "), to_host_server=os.path.join(getLibraryPath(), "to_host_server"), login=bpy.props.serverPrefs["login"])
process = subprocess.Popen(rsyncCommand, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
return process
......@@ -159,22 +159,21 @@ def renderFrames(frameRange, projectName, jobsPerFrame=False):
return process
def setRenderStatus(key, status):
bpy.context.scene.renderStatus[key] = status
for a in bpy.context.screen.areas:
a.tag_redraw()
scn = bpy.context.scene
if key == "image":
scn.imageRenderStatus = status
elif key == "animation":
scn.animRenderStatus = status
tag_redraw_areas()
def getRenderStatus(key):
return bpy.context.scene.renderStatus[key]
def appendViewable(typeOfRender):
if typeOfRender not in bpy.context.scene.renderType:
bpy.context.scene.renderType.append(typeOfRender)
def removeViewable(typeOfRender):
try:
bpy.context.scene.renderType.remove(typeOfRender)
except:
return
scn = bpy.context.scene
if key == "image":
return scn.imageRenderStatus
elif key == "animation":
return scn.animRenderStatus
else:
return ""
def expandFrames(frame_range):
""" Helper function takes frame range string and returns list with frame ranges expanded """
......@@ -310,8 +309,9 @@ def getNameOutputFiles():
return bashSafeName(bpy.path.display_name_from_filepath(bpy.data.filepath))
def getNumRenderedFiles(jobType, frameRange=None, fileName=None):
scn = bpy.context.scene
if jobType == "image":
numRenderedFiles = len([f for f in os.listdir(getRenderDumpFolder()) if "_seed-" in f and f.endswith(str(frameRange[0]) + bpy.props.imExtension)])
numRenderedFiles = len([f for f in os.listdir(getRenderDumpFolder()) if "_seed-" in f and f.endswith(str(frameRange[0]) + scn.imExtension)])
else:
renderedFiles = []
for f in os.listdir(getRenderDumpFolder()):
......@@ -319,7 +319,7 @@ def getNumRenderedFiles(jobType, frameRange=None, fileName=None):
frameNum = int(f[-8:-4])
except:
continue
if frameNum in frameRange and fnmatch.fnmatch(f, "{fileName}_????{extension}".format(fileName=fileName, extension=bpy.props.animExtension)):
if frameNum in frameRange and fnmatch.fnmatch(f, "{fileName}_????{extension}".format(fileName=fileName, extension=scn.animExtension)):
renderedFiles.append(f)
numRenderedFiles = len(renderedFiles)
return numRenderedFiles
......@@ -344,6 +344,7 @@ def changeContext(context, areaType):
return lastAreaType
def updateServerPrefs():
scn = bpy.context.scene
# verify rsync is installed on local machine
localVerify = subprocess.call("rsync --version", stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
if localVerify > 0:
......@@ -358,7 +359,7 @@ def updateServerPrefs():
return newServerPrefs
# verify host server login, built from user entries, correspond to a responsive server, and that defined renderFarm path is writable
rc = subprocess.call("ssh -T -oBatchMode=yes -oStrictHostKeyChecking=no -oConnectTimeout=10 -x {login} 'mkdir -p {remotePath}; touch {remotePath}test'".format(login=bpy.props.serverPrefs["login"], remotePath=bpy.props.serverPrefs["path"]), shell=True)
rc = subprocess.call("ssh -T -oBatchMode=yes -oStrictHostKeyChecking=no -oConnectTimeout=10 -x {login} 'mkdir -p {remotePath}; touch {remotePath}test'".format(login=bpy.props.serverPrefs["login"], remotePath=bpy.props.serverPrefs["path"].replace(" ", "\\ ")), shell=True)
if rc != 0:
return {"valid":False, "errorMessage":"ssh to '{login}' failed (return code: {rc}). Check your settings, ensure ssh keys are setup, and verify your write permissions for '{remotePath}' (see error in terminal)".format(login=bpy.props.serverPrefs["login"], rc=rc, remotePath=bpy.props.serverPrefs["path"])}
......
......@@ -28,7 +28,7 @@ from . import getRenderDumpFolder
def averageFrames(classObject, outputFileName, verbose=0):
""" Averages final rendered images in blender to present one render result """
scn = bpy.context.scene
if verbose >= 1:
print("Averaging images...")
......@@ -39,7 +39,7 @@ def averageFrames(classObject, outputFileName, verbose=0):
# get image files to average from 'renderedFramesPath'
allFiles = os.listdir(renderedFramesPath)
inFileName = "{outputFileName}_seed-*_{frame}{extension}".format(outputFileName=outputFileName, frame=str(bpy.props.imFrame).zfill(4), extension=bpy.props.imExtension)
inFileName = "{outputFileName}_seed-*_{frame}{extension}".format(outputFileName=outputFileName, frame=str(scn.imFrame).zfill(4), extension=scn.imExtension)
imListNames = [filename for filename in allFiles if fnmatch.fnmatch(filename, inFileName)]
imList = [os.path.join(renderedFramesPath, im) for im in imListNames]
if not imList:
......@@ -87,7 +87,7 @@ def averageFrames(classObject, outputFileName, verbose=0):
print("Averaged successfully!")
# 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)
imName = "{outputFileName}_{frame}_average{extension}".format(outputFileName=outputFileName, frame=str(scn.imFrame).zfill(4), extension=scn.imExtension)
if bpy.data.images.find(imName) < 0:
new = bpy.data.images.new(imName, w, h, alpha)
else:
......
......@@ -29,7 +29,7 @@ scn = bpy.context.scene
""" BEGIN SUPPORT FOR REBRICKR """
@persistent
def handle_legoizer_animation(scene):
def handle_rebrickr_animation(scene):
print("Adjusting frame")
groupsToAdjust = {}
for group in bpy.data.groups:
......@@ -53,9 +53,9 @@ def handle_legoizer_animation(scene):
brick.hide = not displayOnCurF
brick.hide_render = not displayOnCurF
handle_legoizer_animation(scn)
bpy.app.handlers.render_pre.append(handle_legoizer_animation)
bpy.app.handlers.frame_change_pre.append(handle_legoizer_animation)
handle_rebrickr_animation(scn)
bpy.app.handlers.render_pre.append(handle_rebrickr_animation)
bpy.app.handlers.frame_change_pre.append(handle_rebrickr_animation)
""" END SUPPORT FOR REBRICKR """
......
......@@ -72,15 +72,13 @@ def ssh_string(username, hostname, verbose=0):
return tmpStr
def rsync_files_to_node_string(remoteResultsPath, projectSyncPath, username, hostname, projectPath, verbose=0):
tmpStr = "rsync -e 'ssh -oStrictHostKeyChecking=no' --rsync-path='mkdir -p {remoteResultsPath} && rsync' -a {projectSyncPath} {username}@{hostname}:{projectPath}/".format(remoteResultsPath=remoteResultsPath, projectSyncPath=projectSyncPath, username=username, hostname=hostname, projectPath=projectPath)
tmpStr = "rsync -e 'ssh -oStrictHostKeyChecking=no' --rsync-path='mkdir -p {remoteResultsPath} && rsync' -a {projectSyncPath} {username}@{hostname}:{projectPath}/".format(remoteResultsPath=remoteResultsPath.replace(" ", "\\ "), projectSyncPath=projectSyncPath, username=username, hostname=hostname, projectPath=projectPath)
if verbose >= 3:
pflush(tmpStr)
return tmpStr
def rsync_files_from_node_string(username, hostname, remoteResultsPath, localResultsPath, outputName="", frameString="", verbose=0):
tmpStr = "rsync -atu -e 'ssh -oStrictHostKeyChecking=no' --include='{outputName}{frameString}.???' --exclude='*' --remove-source-files --rsync-path='mkdir -p {localResultsPath} && rsync' {username}@{hostname}:{remoteResultsPath} {localResultsPath}".format(outputName=outputName, username=username, hostname=hostname, remoteResultsPath=remoteResultsPath, frameString=frameString, localResultsPath=localResultsPath)
tmpStr = "rsync -atu -e 'ssh -oStrictHostKeyChecking=no' --include='{outputName}{frameString}.???' --exclude='*' --remove-source-files --rsync-path='mkdir -p {localResultsPath} && rsync' {username}@{hostname}:{remoteResultsPath} {localResultsPath}".format(outputName=outputName, username=username, hostname=hostname, remoteResultsPath=remoteResultsPath, frameString=frameString, localResultsPath=localResultsPath.replace(" ", "\\ "))
if verbose >= 3:
pflush(tmpStr)
return tmpStr
......
......@@ -26,6 +26,7 @@ from bpy.types import Panel
from bpy.props import *
from ..functions import getRenderStatus, have_internet
from ..functions.setupServers import *
from .app_handlers import *
class renderOnServersPanel(Panel):
bl_space_type = "VIEW_3D"
......@@ -57,7 +58,6 @@ class renderOnServersPanel(Panel):
# Render Buttons
row = col.row(align=True)
row.alignment = "EXPAND"
row.active = scn.availableServers > 0 or not scn.render.engine == "CYCLES"
row.operator("render_farm.render_frame_on_servers", text="Render", icon="RENDER_STILL")
row.operator("render_farm.render_animation_on_servers", text="Animation", icon="RENDER_ANIMATION")
col = layout.column(align=True)
......@@ -76,9 +76,9 @@ class renderOnServersPanel(Panel):
# display buttons to view render(s)
row = layout.row(align=True)
if "image" in scn.renderType:
if scn.imagePreviewAvailable:
row.operator("render_farm.open_rendered_image", text="View Image", icon="FILE_IMAGE")
if "animation" in scn.renderType:
if scn.animPreviewAvailable:
row.operator("render_farm.open_rendered_animation", text="View Animation", icon="FILE_MOVIE")
if bpy.data.texts.find('Render_Farm_log') >= 0:
......
......@@ -22,21 +22,21 @@ Created by Christopher Gearhart
# system imports
import bpy
import math
from bpy.app.handlers import persistent
from bpy.types import Panel
from bpy.props import *
from ..functions import getRenderStatus, have_internet
from ..buttons.refreshServers import refreshServers
from ..functions import *