diff options
author | Campbell Barton <ideasman42@gmail.com> | 2009-12-29 03:04:57 +0300 |
---|---|---|
committer | Campbell Barton <ideasman42@gmail.com> | 2009-12-29 03:04:57 +0300 |
commit | 58b1591ceb7d05a185536b99bb69e62d1ebdaef0 (patch) | |
tree | 1149ae6b6996cbcc6f4b61be4e9cf880856b3f53 /release/scripts | |
parent | 42154ad226d7f5be0bb5ca80250b0dcc395db9cc (diff) |
netrender tab to spaces
Diffstat (limited to 'release/scripts')
-rw-r--r-- | release/scripts/io/netrender/__init__.py | 4 | ||||
-rw-r--r-- | release/scripts/io/netrender/balancing.py | 222 | ||||
-rw-r--r-- | release/scripts/io/netrender/client.py | 478 | ||||
-rw-r--r-- | release/scripts/io/netrender/master.py | 1618 | ||||
-rw-r--r-- | release/scripts/io/netrender/master_html.py | 446 | ||||
-rw-r--r-- | release/scripts/io/netrender/model.py | 484 | ||||
-rw-r--r-- | release/scripts/io/netrender/operators.py | 762 | ||||
-rw-r--r-- | release/scripts/io/netrender/slave.py | 410 | ||||
-rw-r--r-- | release/scripts/io/netrender/ui.py | 504 | ||||
-rw-r--r-- | release/scripts/io/netrender/utils.py | 98 | ||||
-rw-r--r-- | release/scripts/presets/render/HDTV_1080p.py | 2 | ||||
-rw-r--r-- | release/scripts/presets/render/HDTV_720p.py | 2 | ||||
-rw-r--r-- | release/scripts/presets/render/TV_PAL.py | 2 | ||||
-rw-r--r-- | release/scripts/presets/render/TV_PAL_16_colon_9.py | 2 | ||||
-rw-r--r-- | release/scripts/ui/space_userpref.py | 12 | ||||
-rw-r--r-- | release/scripts/ui/space_view3d_toolbar.py | 8 |
16 files changed, 2527 insertions, 2527 deletions
diff --git a/release/scripts/io/netrender/__init__.py b/release/scripts/io/netrender/__init__.py index 9e48164a5eb..77ab95535aa 100644 --- a/release/scripts/io/netrender/__init__.py +++ b/release/scripts/io/netrender/__init__.py @@ -4,12 +4,12 @@ # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. -# +# # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. -# +# # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software Foundation, # Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. diff --git a/release/scripts/io/netrender/balancing.py b/release/scripts/io/netrender/balancing.py index d8951b1e2e8..e09b19f956c 100644 --- a/release/scripts/io/netrender/balancing.py +++ b/release/scripts/io/netrender/balancing.py @@ -4,12 +4,12 @@ # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. -# +# # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. -# +# # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software Foundation, # Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. @@ -22,129 +22,129 @@ from netrender.utils import * import netrender.model class RatingRule: - def rate(self, job): - return 0 + def rate(self, job): + return 0 class ExclusionRule: - def test(self, job): - return False + def test(self, job): + return False class PriorityRule: - def test(self, job): - return False + def test(self, job): + return False class Balancer: - def __init__(self): - self.rules = [] - self.priorities = [] - self.exceptions = [] - - def addRule(self, rule): - self.rules.append(rule) - - def addPriority(self, priority): - self.priorities.append(priority) - - def addException(self, exception): - self.exceptions.append(exception) - - def applyRules(self, job): - return sum((rule.rate(job) for rule in self.rules)) - - def applyPriorities(self, job): - for priority in self.priorities: - if priority.test(job): - return True # priorities are first - - return False - - def applyExceptions(self, job): - for exception in self.exceptions: - if exception.test(job): - return True # exceptions are last - - return False - - def sortKey(self, job): - return (1 if self.applyExceptions(job) else 0, # exceptions after - 0 if self.applyPriorities(job) else 1, # priorities first - self.applyRules(job)) - - def balance(self, jobs): - if jobs: - # use inline copy to make sure the list is still accessible while sorting - jobs[:] = sorted(jobs, key=self.sortKey) - return jobs[0] - else: - return None - + def __init__(self): + self.rules = [] + self.priorities = [] + self.exceptions = [] + + def addRule(self, rule): + self.rules.append(rule) + + def addPriority(self, priority): + self.priorities.append(priority) + + def addException(self, exception): + self.exceptions.append(exception) + + def applyRules(self, job): + return sum((rule.rate(job) for rule in self.rules)) + + def applyPriorities(self, job): + for priority in self.priorities: + if priority.test(job): + return True # priorities are first + + return False + + def applyExceptions(self, job): + for exception in self.exceptions: + if exception.test(job): + return True # exceptions are last + + return False + + def sortKey(self, job): + return (1 if self.applyExceptions(job) else 0, # exceptions after + 0 if self.applyPriorities(job) else 1, # priorities first + self.applyRules(job)) + + def balance(self, jobs): + if jobs: + # use inline copy to make sure the list is still accessible while sorting + jobs[:] = sorted(jobs, key=self.sortKey) + return jobs[0] + else: + return None + # ========================== class RatingUsage(RatingRule): - def __str__(self): - return "Usage rating" - - def rate(self, job): - # less usage is better - return job.usage / job.priority + def __str__(self): + return "Usage rating" + + def rate(self, job): + # less usage is better + return job.usage / job.priority class RatingUsageByCategory(RatingRule): - def __str__(self): - return "Usage per category rating" - - def __init__(self, get_jobs): - self.getJobs = get_jobs - def rate(self, job): - total_category_usage = sum([j.usage for j in self.getJobs() if j.category == job.category]) - maximum_priority = max([j.priority for j in self.getJobs() if j.category == job.category]) - - # less usage is better - return total_category_usage / maximum_priority - + def __str__(self): + return "Usage per category rating" + + def __init__(self, get_jobs): + self.getJobs = get_jobs + def rate(self, job): + total_category_usage = sum([j.usage for j in self.getJobs() if j.category == job.category]) + maximum_priority = max([j.priority for j in self.getJobs() if j.category == job.category]) + + # less usage is better + return total_category_usage / maximum_priority + class NewJobPriority(PriorityRule): - def str_limit(self): - return "less than %i frame%s done" % (self.limit, "s" if self.limit > 1 else "") - - def __str__(self): - return "Priority to new jobs" - - def __init__(self, limit = 1): - self.limit = limit - - def test(self, job): - return job.countFrames(status = DONE) < self.limit + def str_limit(self): + return "less than %i frame%s done" % (self.limit, "s" if self.limit > 1 else "") + + def __str__(self): + return "Priority to new jobs" + + def __init__(self, limit = 1): + self.limit = limit + + def test(self, job): + return job.countFrames(status = DONE) < self.limit class MinimumTimeBetweenDispatchPriority(PriorityRule): - def str_limit(self): - return "more than %i minute%s since last" % (self.limit, "s" if self.limit > 1 else "") - - def __str__(self): - return "Priority to jobs that haven't been dispatched recently" - - def __init__(self, limit = 10): - self.limit = limit - - def test(self, job): - return job.countFrames(status = DISPATCHED) == 0 and (time.time() - job.last_dispatched) / 60 > self.limit + def str_limit(self): + return "more than %i minute%s since last" % (self.limit, "s" if self.limit > 1 else "") + + def __str__(self): + return "Priority to jobs that haven't been dispatched recently" + + def __init__(self, limit = 10): + self.limit = limit + + def test(self, job): + return job.countFrames(status = DISPATCHED) == 0 and (time.time() - job.last_dispatched) / 60 > self.limit class ExcludeQueuedEmptyJob(ExclusionRule): - def __str__(self): - return "Exclude queued and empty jobs" - - def test(self, job): - return job.status != JOB_QUEUED or job.countFrames(status = QUEUED) == 0 - + def __str__(self): + return "Exclude queued and empty jobs" + + def test(self, job): + return job.status != JOB_QUEUED or job.countFrames(status = QUEUED) == 0 + class ExcludeSlavesLimit(ExclusionRule): - def str_limit(self): - return "more than %.0f%% of all slaves" % (self.limit * 100) - - def __str__(self): - return "Exclude jobs that would use too many slaves" - - def __init__(self, count_jobs, count_slaves, limit = 0.75): - self.count_jobs = count_jobs - self.count_slaves = count_slaves - self.limit = limit - - def test(self, job): - return not ( self.count_jobs() == 1 or self.count_slaves() <= 1 or float(job.countSlaves() + 1) / self.count_slaves() <= self.limit ) + def str_limit(self): + return "more than %.0f%% of all slaves" % (self.limit * 100) + + def __str__(self): + return "Exclude jobs that would use too many slaves" + + def __init__(self, count_jobs, count_slaves, limit = 0.75): + self.count_jobs = count_jobs + self.count_slaves = count_slaves + self.limit = limit + + def test(self, job): + return not ( self.count_jobs() == 1 or self.count_slaves() <= 1 or float(job.countSlaves() + 1) / self.count_slaves() <= self.limit ) diff --git a/release/scripts/io/netrender/client.py b/release/scripts/io/netrender/client.py index bbbb9f26051..d71d6a072ba 100644 --- a/release/scripts/io/netrender/client.py +++ b/release/scripts/io/netrender/client.py @@ -4,12 +4,12 @@ # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. -# +# # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. -# +# # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software Foundation, # Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. @@ -28,253 +28,253 @@ import netrender.master as master from netrender.utils import * def addFluidFiles(job, path): - if os.path.exists(path): - pattern = re.compile("fluidsurface_(final|preview)_([0-9]+)\.(bobj|bvel)\.gz") - - for fluid_file in sorted(os.listdir(path)): - match = pattern.match(fluid_file) - - if match: - # fluid frames starts at 0, which explains the +1 - # This is stupid - current_frame = int(match.groups()[1]) + 1 - job.addFile(path + fluid_file, current_frame, current_frame) + if os.path.exists(path): + pattern = re.compile("fluidsurface_(final|preview)_([0-9]+)\.(bobj|bvel)\.gz") + + for fluid_file in sorted(os.listdir(path)): + match = pattern.match(fluid_file) + + if match: + # fluid frames starts at 0, which explains the +1 + # This is stupid + current_frame = int(match.groups()[1]) + 1 + job.addFile(path + fluid_file, current_frame, current_frame) def addPointCache(job, ob, point_cache, default_path): - if not point_cache.disk_cache: - return - - - name = point_cache.name - if name == "": - name = "".join(["%02X" % ord(c) for c in ob.name]) - - cache_path = bpy.utils.expandpath(point_cache.filepath) if point_cache.external else default_path - - index = "%02i" % point_cache.index - - if os.path.exists(cache_path): - pattern = re.compile(name + "_([0-9]+)_" + index + "\.bphys") - - cache_files = [] - - for cache_file in sorted(os.listdir(cache_path)): - match = pattern.match(cache_file) - - if match: - cache_frame = int(match.groups()[0]) - cache_files.append((cache_frame, cache_file)) - - cache_files.sort() - - if len(cache_files) == 1: - cache_frame, cache_file = cache_files[0] - job.addFile(cache_path + cache_file, cache_frame, cache_frame) - else: - for i in range(len(cache_files)): - current_item = cache_files[i] - next_item = cache_files[i+1] if i + 1 < len(cache_files) else None - previous_item = cache_files[i - 1] if i > 0 else None - - current_frame, current_file = current_item - - if not next_item and not previous_item: - job.addFile(cache_path + current_file, current_frame, current_frame) - elif next_item and not previous_item: - next_frame = next_item[0] - job.addFile(cache_path + current_file, current_frame, next_frame - 1) - elif not next_item and previous_item: - previous_frame = previous_item[0] - job.addFile(cache_path + current_file, previous_frame + 1, current_frame) - else: - next_frame = next_item[0] - previous_frame = previous_item[0] - job.addFile(cache_path + current_file, previous_frame + 1, next_frame - 1) - + if not point_cache.disk_cache: + return + + + name = point_cache.name + if name == "": + name = "".join(["%02X" % ord(c) for c in ob.name]) + + cache_path = bpy.utils.expandpath(point_cache.filepath) if point_cache.external else default_path + + index = "%02i" % point_cache.index + + if os.path.exists(cache_path): + pattern = re.compile(name + "_([0-9]+)_" + index + "\.bphys") + + cache_files = [] + + for cache_file in sorted(os.listdir(cache_path)): + match = pattern.match(cache_file) + + if match: + cache_frame = int(match.groups()[0]) + cache_files.append((cache_frame, cache_file)) + + cache_files.sort() + + if len(cache_files) == 1: + cache_frame, cache_file = cache_files[0] + job.addFile(cache_path + cache_file, cache_frame, cache_frame) + else: + for i in range(len(cache_files)): + current_item = cache_files[i] + next_item = cache_files[i+1] if i + 1 < len(cache_files) else None + previous_item = cache_files[i - 1] if i > 0 else None + + current_frame, current_file = current_item + + if not next_item and not previous_item: + job.addFile(cache_path + current_file, current_frame, current_frame) + elif next_item and not previous_item: + next_frame = next_item[0] + job.addFile(cache_path + current_file, current_frame, next_frame - 1) + elif not next_item and previous_item: + previous_frame = previous_item[0] + job.addFile(cache_path + current_file, previous_frame + 1, current_frame) + else: + next_frame = next_item[0] + previous_frame = previous_item[0] + job.addFile(cache_path + current_file, previous_frame + 1, next_frame - 1) + def clientSendJob(conn, scene, anim = False): - netsettings = scene.network_render - job = netrender.model.RenderJob() - - if anim: - for f in range(scene.start_frame, scene.end_frame + 1): - job.addFrame(f) - else: - job.addFrame(scene.current_frame) - - filename = bpy.data.filename - job.addFile(filename) - - job_name = netsettings.job_name - path, name = os.path.split(filename) - if job_name == "[default]": - job_name = name - - ########################### - # LIBRARIES - ########################### - for lib in bpy.data.libraries: - job.addFile(bpy.utils.expandpath(lib.filename)) - - ########################### - # IMAGES - ########################### - for image in bpy.data.images: - if image.source == "FILE" and not image.packed_file: - job.addFile(bpy.utils.expandpath(image.filename)) - - ########################### - # FLUID + POINT CACHE - ########################### - root, ext = os.path.splitext(name) - default_path = path + os.sep + "blendcache_" + root + os.sep # need an API call for that - - for object in bpy.data.objects: - for modifier in object.modifiers: - if modifier.type == 'FLUID_SIMULATION' and modifier.settings.type == "DOMAIN": - addFluidFiles(job, bpy.utils.expandpath(modifier.settings.path)) - elif modifier.type == "CLOTH": - addPointCache(job, object, modifier.point_cache, default_path) - elif modifier.type == "SOFT_BODY": - addPointCache(job, object, modifier.point_cache, default_path) - elif modifier.type == "SMOKE" and modifier.smoke_type == "TYPE_DOMAIN": - addPointCache(job, object, modifier.domain_settings.point_cache_low, default_path) - if modifier.domain_settings.highres: - addPointCache(job, object, modifier.domain_settings.point_cache_high, default_path) - - # particles modifier are stupid and don't contain data - # we have to go through the object property - for psys in object.particle_systems: - addPointCache(job, object, psys.point_cache, default_path) - - #print(job.files) - - job.name = job_name - job.category = netsettings.job_category - - for slave in netrender.blacklist: - job.blacklist.append(slave.id) - - job.chunks = netsettings.chunks - job.priority = netsettings.priority - - # try to send path first - conn.request("POST", "/job", repr(job.serialize())) - response = conn.getresponse() - - job_id = response.getheader("job-id") - - # if not ACCEPTED (but not processed), send files - if response.status == http.client.ACCEPTED: - for rfile in job.files: - f = open(rfile.filepath, "rb") - conn.request("PUT", fileURL(job_id, rfile.index), f) - f.close() - response = conn.getresponse() - - # server will reply with ACCEPTED until all files are found - - return job_id + netsettings = scene.network_render + job = netrender.model.RenderJob() + + if anim: + for f in range(scene.start_frame, scene.end_frame + 1): + job.addFrame(f) + else: + job.addFrame(scene.current_frame) + + filename = bpy.data.filename + job.addFile(filename) + + job_name = netsettings.job_name + path, name = os.path.split(filename) + if job_name == "[default]": + job_name = name + + ########################### + # LIBRARIES + ########################### + for lib in bpy.data.libraries: + job.addFile(bpy.utils.expandpath(lib.filename)) + + ########################### + # IMAGES + ########################### + for image in bpy.data.images: + if image.source == "FILE" and not image.packed_file: + job.addFile(bpy.utils.expandpath(image.filename)) + + ########################### + # FLUID + POINT CACHE + ########################### + root, ext = os.path.splitext(name) + default_path = path + os.sep + "blendcache_" + root + os.sep # need an API call for that + + for object in bpy.data.objects: + for modifier in object.modifiers: + if modifier.type == 'FLUID_SIMULATION' and modifier.settings.type == "DOMAIN": + addFluidFiles(job, bpy.utils.expandpath(modifier.settings.path)) + elif modifier.type == "CLOTH": + addPointCache(job, object, modifier.point_cache, default_path) + elif modifier.type == "SOFT_BODY": + addPointCache(job, object, modifier.point_cache, default_path) + elif modifier.type == "SMOKE" and modifier.smoke_type == "TYPE_DOMAIN": + addPointCache(job, object, modifier.domain_settings.point_cache_low, default_path) + if modifier.domain_settings.highres: + addPointCache(job, object, modifier.domain_settings.point_cache_high, default_path) + + # particles modifier are stupid and don't contain data + # we have to go through the object property + for psys in object.particle_systems: + addPointCache(job, object, psys.point_cache, default_path) + + #print(job.files) + + job.name = job_name + job.category = netsettings.job_category + + for slave in netrender.blacklist: + job.blacklist.append(slave.id) + + job.chunks = netsettings.chunks + job.priority = netsettings.priority + + # try to send path first + conn.request("POST", "/job", repr(job.serialize())) + response = conn.getresponse() + + job_id = response.getheader("job-id") + + # if not ACCEPTED (but not processed), send files + if response.status == http.client.ACCEPTED: + for rfile in job.files: + f = open(rfile.filepath, "rb") + conn.request("PUT", fileURL(job_id, rfile.index), f) + f.close() + response = conn.getresponse() + + # server will reply with ACCEPTED until all files are found + + return job_id def requestResult(conn, job_id, frame): - conn.request("GET", renderURL(job_id, frame)) + conn.request("GET", renderURL(job_id, frame)) @rnaType class NetworkRenderEngine(bpy.types.RenderEngine): - bl_idname = 'NET_RENDER' - bl_label = "Network Render" - def render(self, scene): - if scene.network_render.mode == "RENDER_CLIENT": - self.render_client(scene) - elif scene.network_render.mode == "RENDER_SLAVE": - self.render_slave(scene) - elif scene.network_render.mode == "RENDER_MASTER": - self.render_master(scene) - else: - print("UNKNOWN OPERATION MODE") - - def render_master(self, scene): - netsettings = scene.network_render - - address = "" if netsettings.server_address == "[default]" else netsettings.server_address - - master.runMaster((address, netsettings.server_port), netsettings.server_broadcast, netsettings.path, self.update_stats, self.test_break) - - - def render_slave(self, scene): - slave.render_slave(self, scene.network_render) - - def render_client(self, scene): - netsettings = scene.network_render - self.update_stats("", "Network render client initiation") - - - conn = clientConnection(netsettings.server_address, netsettings.server_port) - - if conn: - # Sending file - - self.update_stats("", "Network render exporting") - - new_job = False - - job_id = netsettings.job_id - - # reading back result - - self.update_stats("", "Network render waiting for results") - - requestResult(conn, job_id, scene.current_frame) - response = conn.getresponse() - - if response.status == http.client.NO_CONTENT: - new_job = True - netsettings.job_id = clientSendJob(conn, scene) - job_id = netsettings.job_id - - requestResult(conn, job_id, scene.current_frame) - response = conn.getresponse() - - while response.status == http.client.ACCEPTED and not self.test_break(): - time.sleep(1) - requestResult(conn, job_id, scene.current_frame) - response = conn.getresponse() - - # cancel new jobs (animate on network) on break - if self.test_break() and new_job: - conn.request("POST", cancelURL(job_id)) - response = conn.getresponse() - print( response.status, response.reason ) - netsettings.job_id = 0 - - if response.status != http.client.OK: - conn.close() - return - - r = scene.render_data - x= int(r.resolution_x*r.resolution_percentage*0.01) - y= int(r.resolution_y*r.resolution_percentage*0.01) - - f = open(netsettings.path + "output.exr", "wb") - buf = response.read(1024) - - while buf: - f.write(buf) - buf = response.read(1024) - - f.close() - - result = self.begin_result(0, 0, x, y) - result.load_from_file(netsettings.path + "output.exr", 0, 0) - self.end_result(result) - - conn.close() + bl_idname = 'NET_RENDER' + bl_label = "Network Render" + def render(self, scene): + if scene.network_render.mode == "RENDER_CLIENT": + self.render_client(scene) + elif scene.network_render.mode == "RENDER_SLAVE": + self.render_slave(scene) + elif scene.network_render.mode == "RENDER_MASTER": + self.render_master(scene) + else: + print("UNKNOWN OPERATION MODE") + + def render_master(self, scene): + netsettings = scene.network_render + + address = "" if netsettings.server_address == "[default]" else netsettings.server_address + + master.runMaster((address, netsettings.server_port), netsettings.server_broadcast, netsettings.path, self.update_stats, self.test_break) + + + def render_slave(self, scene): + slave.render_slave(self, scene.network_render) + + def render_client(self, scene): + netsettings = scene.network_render + self.update_stats("", "Network render client initiation") + + + conn = clientConnection(netsettings.server_address, netsettings.server_port) + + if conn: + # Sending file + + self.update_stats("", "Network render exporting") + + new_job = False + + job_id = netsettings.job_id + + # reading back result + + self.update_stats("", "Network render waiting for results") + + requestResult(conn, job_id, scene.current_frame) + response = conn.getresponse() + + if response.status == http.client.NO_CONTENT: + new_job = True + netsettings.job_id = clientSendJob(conn, scene) + job_id = netsettings.job_id + + requestResult(conn, job_id, scene.current_frame) + response = conn.getresponse() + + while response.status == http.client.ACCEPTED and not self.test_break(): + time.sleep(1) + requestResult(conn, job_id, scene.current_frame) + response = conn.getresponse() + + # cancel new jobs (animate on network) on break + if self.test_break() and new_job: + conn.request("POST", cancelURL(job_id)) + response = conn.getresponse() + print( response.status, response.reason ) + netsettings.job_id = 0 + + if response.status != http.client.OK: + conn.close() + return + + r = scene.render_data + x= int(r.resolution_x*r.resolution_percentage*0.01) + y= int(r.resolution_y*r.resolution_percentage*0.01) + + f = open(netsettings.path + "output.exr", "wb") + buf = response.read(1024) + + while buf: + f.write(buf) + buf = response.read(1024) + + f.close() + + result = self.begin_result(0, 0, x, y) + result.load_from_file(netsettings.path + "output.exr", 0, 0) + self.end_result(result) + + conn.close() def compatible(module): - module = __import__(module) - for subclass in module.__dict__.values(): - try: subclass.COMPAT_ENGINES.add('NET_RENDER') - except: pass - del module + module = __import__(module) + for subclass in module.__dict__.values(): + try: subclass.COMPAT_ENGINES.add('NET_RENDER') + except: pass + del module compatible("properties_render") compatible("properties_world") diff --git a/release/scripts/io/netrender/master.py b/release/scripts/io/netrender/master.py index 83980c95da7..a33606d19a0 100644 --- a/release/scripts/io/netrender/master.py +++ b/release/scripts/io/netrender/master.py @@ -4,12 +4,12 @@ # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. -# +# # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. -# +# # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software Foundation, # Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. @@ -26,135 +26,135 @@ import netrender.balancing import netrender.master_html class MRenderFile(netrender.model.RenderFile): - def __init__(self, filepath, index, start, end): - super().__init__(filepath, index, start, end) - self.found = False - - def test(self): - self.found = os.path.exists(self.filepath) - return self.found + def __init__(self, filepath, index, start, end): + super().__init__(filepath, index, start, end) + self.found = False + + def test(self): + self.found = os.path.exists(self.filepath) + return self.found class MRenderSlave(netrender.model.RenderSlave): - def __init__(self, name, address, stats): - super().__init__() - self.id = hashlib.md5(bytes(repr(name) + repr(address), encoding='utf8')).hexdigest() - self.name = name - self.address = address - self.stats = stats - self.last_seen = time.time() - - self.job = None - self.job_frames = [] - - netrender.model.RenderSlave._slave_map[self.id] = self - - def seen(self): - self.last_seen = time.time() - - def finishedFrame(self, frame_number): - self.job_frames.remove(frame_number) - if not self.job_frames: - self.job = None + def __init__(self, name, address, stats): + super().__init__() + self.id = hashlib.md5(bytes(repr(name) + repr(address), encoding='utf8')).hexdigest() + self.name = name + self.address = address + self.stats = stats + self.last_seen = time.time() + + self.job = None + self.job_frames = [] + + netrender.model.RenderSlave._slave_map[self.id] = self + + def seen(self): + self.last_seen = time.time() + + def finishedFrame(self, frame_number): + self.job_frames.remove(frame_number) + if not self.job_frames: + self.job = None class MRenderJob(netrender.model.RenderJob): - def __init__(self, job_id, job_info): - super().__init__(job_info) - self.id = job_id - self.last_dispatched = time.time() - - # force one chunk for process jobs - if self.type == netrender.model.JOB_PROCESS: - self.chunks = 1 - - # Force WAITING status on creation - self.status = JOB_WAITING - - # special server properties - self.last_update = 0 - self.save_path = "" - self.files = [MRenderFile(rfile.filepath, rfile.index, rfile.start, rfile.end) for rfile in job_info.files] - - def save(self): - if self.save_path: - f = open(self.save_path + "job.txt", "w") - f.write(repr(self.serialize())) - f.close() - - def edit(self, info_map): - if "status" in info_map: - self.status = info_map["status"] - - if "priority" in info_map: - self.priority = info_map["priority"] - - if "chunks" in info_map: - self.chunks = info_map["chunks"] - - def testStart(self): - for f in self.files: - if not f.test(): - return False - - self.start() - return True - - def testFinished(self): - for f in self.frames: - if f.status == QUEUED or f.status == DISPATCHED: - break - else: - self.status = JOB_FINISHED - - def start(self): - self.status = JOB_QUEUED - - def addLog(self, frames): - log_name = "_".join(("%04d" % f for f in frames)) + ".log" - log_path = self.save_path + log_name - - for number in frames: - frame = self[number] - if frame: - frame.log_path = log_path - - def addFrame(self, frame_number, command): - frame = MRenderFrame(frame_number, command) - self.frames.append(frame) - return frame - - def reset(self, all): - for f in self.frames: - f.reset(all) - - def getFrames(self): - frames = [] - for f in self.frames: - if f.status == QUEUED: - self.last_dispatched = time.time() - frames.append(f) - if len(frames) >= self.chunks: - break - - return frames + def __init__(self, job_id, job_info): + super().__init__(job_info) + self.id = job_id + self.last_dispatched = time.time() + + # force one chunk for process jobs + if self.type == netrender.model.JOB_PROCESS: + self.chunks = 1 + + # Force WAITING status on creation + self.status = JOB_WAITING + + # special server properties + self.last_update = 0 + self.save_path = "" + self.files = [MRenderFile(rfile.filepath, rfile.index, rfile.start, rfile.end) for rfile in job_info.files] + + def save(self): + if self.save_path: + f = open(self.save_path + "job.txt", "w") + f.write(repr(self.serialize())) + f.close() + + def edit(self, info_map): + if "status" in info_map: + self.status = info_map["status"] + + if "priority" in info_map: + self.priority = info_map["priority"] + + if "chunks" in info_map: + self.chunks = info_map["chunks"] + + def testStart(self): + for f in self.files: + if not f.test(): + return False + + self.start() + return True + + def testFinished(self): + for f in self.frames: + if f.status == QUEUED or f.status == DISPATCHED: + break + else: + self.status = JOB_FINISHED + + def start(self): + self.status = JOB_QUEUED + + def addLog(self, frames): + log_name = "_".join(("%04d" % f for f in frames)) + ".log" + log_path = self.save_path + log_name + + for number in frames: + frame = self[number] + if frame: + frame.log_path = log_path + + def addFrame(self, frame_number, command): + frame = MRenderFrame(frame_number, command) + self.frames.append(frame) + return frame + + def reset(self, all): + for f in self.frames: + f.reset(all) + + def getFrames(self): + frames = [] + for f in self.frames: + if f.status == QUEUED: + self.last_dispatched = time.time() + frames.append(f) + if len(frames) >= self.chunks: + break + + return frames class MRenderFrame(netrender.model.RenderFrame): - def __init__(self, frame, command): - super().__init__() - self.number = frame - self.slave = None - self.time = 0 - self.status = QUEUED - self.command = command - - self.log_path = None - - def reset(self, all): - if all or self.status == ERROR: - self.log_path = None - self.slave = None - self.time = 0 - self.status = QUEUED + def __init__(self, frame, command): + super().__init__() + self.number = frame + self.slave = None + self.time = 0 + self.status = QUEUED + self.command = command + + self.log_path = None + + def reset(self, all): + if all or self.status == ERROR: + self.log_path = None + self.slave = None + self.time = 0 + self.status = QUEUED # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= @@ -169,692 +169,692 @@ cancel_pattern = re.compile("/cancel_([a-zA-Z0-9]+)") edit_pattern = re.compile("/edit_([a-zA-Z0-9]+)") class RenderHandler(http.server.BaseHTTPRequestHandler): - def send_head(self, code = http.client.OK, headers = {}, content = "application/octet-stream"): - self.send_response(code) - self.send_header("Content-type", content) - - for key, value in headers.items(): - self.send_header(key, value) - - self.end_headers() - - def do_HEAD(self): - - if self.path == "/status": - job_id = self.headers.get('job-id', "") - job_frame = int(self.headers.get('job-frame', -1)) - - job = self.server.getJobID(job_id) - if job: - frame = job[job_frame] - - - if frame: - self.send_head(http.client.OK) - else: - # no such frame - self.send_head(http.client.NO_CONTENT) - else: - # no such job id - self.send_head(http.client.NO_CONTENT) - - # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= - # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= - # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - - def do_GET(self): - - if self.path == "/version": - self.send_head() - self.server.stats("", "Version check") - self.wfile.write(VERSION) - # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - elif self.path.startswith("/render"): - match = render_pattern.match(self.path) - - if match: - job_id = match.groups()[0] - frame_number = int(match.groups()[1]) - - job = self.server.getJobID(job_id) - - if job: - frame = job[frame_number] - - if frame: - if frame.status in (QUEUED, DISPATCHED): - self.send_head(http.client.ACCEPTED) - elif frame.status == DONE: - self.server.stats("", "Sending result to client") - f = open(job.save_path + "%04d" % frame_number + ".exr", 'rb') - - self.send_head() - - shutil.copyfileobj(f, self.wfile) - - f.close() - elif frame.status == ERROR: - self.send_head(http.client.PARTIAL_CONTENT) - else: - # no such frame - self.send_head(http.client.NO_CONTENT) - else: - # no such job id - self.send_head(http.client.NO_CONTENT) - else: - # invalid url - self.send_head(http.client.NO_CONTENT) - # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - elif self.path.startswith("/log"): - match = log_pattern.match(self.path) - - if match: - job_id = match.groups()[0] - frame_number = int(match.groups()[1]) - - job = self.server.getJobID(job_id) - - if job: - frame = job[frame_number] - - if frame: - if not frame.log_path or frame.status in (QUEUED, DISPATCHED): - self.send_head(http.client.PROCESSING) - else: - self.server.stats("", "Sending log to client") - f = open(frame.log_path, 'rb') - - self.send_head(content = "text/plain") - - shutil.copyfileobj(f, self.wfile) - - f.close() - else: - # no such frame - self.send_head(http.client.NO_CONTENT) - else: - # no such job id - self.send_head(http.client.NO_CONTENT) - else: - # invalid URL - self.send_head(http.client.NO_CONTENT) - # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - elif self.path == "/status": - job_id = self.headers.get('job-id', "") - job_frame = int(self.headers.get('job-frame', -1)) - - if job_id: - - job = self.server.getJobID(job_id) - if job: - if job_frame != -1: - frame = job[frame] - - if frame: - message = frame.serialize() - else: - # no such frame - self.send_heat(http.client.NO_CONTENT) - return - else: - message = job.serialize() - else: - # no such job id - self.send_head(http.client.NO_CONTENT) - return - else: # status of all jobs - message = [] - - for job in self.server: - message.append(job.serialize()) - - - self.server.stats("", "Sending status") - self.send_head() - self.wfile.write(bytes(repr(message), encoding='utf8')) - - # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - elif self.path == "/job": - self.server.balance() - - slave_id = self.headers['slave-id'] - - slave = self.server.getSeenSlave(slave_id) - - if slave: # only if slave id is valid - job, frames = self.server.newDispatch(slave_id) - - if job and frames: - for f in frames: - print("dispatch", f.number) - f.status = DISPATCHED - f.slave = slave - - slave.job = job - slave.job_frames = [f.number for f in frames] - - self.send_head(headers={"job-id": job.id}) - - message = job.serialize(frames) - - self.wfile.write(bytes(repr(message), encoding='utf8')) - - self.server.stats("", "Sending job to slave") - else: - # no job available, return error code - slave.job = None - slave.job_frames = [] - - self.send_head(http.client.ACCEPTED) - else: # invalid slave id - self.send_head(http.client.NO_CONTENT) - # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - elif self.path.startswith("/file"): - match = file_pattern.match(self.path) - - if match: - slave_id = self.headers['slave-id'] - slave = self.server.getSeenSlave(slave_id) - - if not slave: - # invalid slave id - print("invalid slave id") - - job_id = match.groups()[0] - file_index = int(match.groups()[1]) - - job = self.server.getJobID(job_id) - - if job: - render_file = job.files[file_index] - - if render_file: - self.server.stats("", "Sending file to slave") - f = open(render_file.filepath, 'rb') - - self.send_head() - shutil.copyfileobj(f, self.wfile) - - f.close() - else: - # no such file - self.send_head(http.client.NO_CONTENT) - else: - # no such job id - self.send_head(http.client.NO_CONTENT) - else: # invalid url - self.send_head(http.client.NO_CONTENT) - # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - elif self.path == "/slaves": - message = [] - - self.server.stats("", "Sending slaves status") - - for slave in self.server.slaves: - message.append(slave.serialize()) - - self.send_head() - - self.wfile.write(bytes(repr(message), encoding='utf8')) - # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - else: - # hand over the rest to the html section - netrender.master_html.get(self) - - # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= - # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= - # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - def do_POST(self): - - # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - if self.path == "/job": - - length = int(self.headers['content-length']) - - job_info = netrender.model.RenderJob.materialize(eval(str(self.rfile.read(length), encoding='utf8'))) - - job_id = self.server.nextJobID() - - job = MRenderJob(job_id, job_info) - - for frame in job_info.frames: - frame = job.addFrame(frame.number, frame.command) - - self.server.addJob(job) - - headers={"job-id": job_id} - - if job.testStart(): - self.server.stats("", "New job, started") - self.send_head(headers=headers) - else: - self.server.stats("", "New job, missing files (%i total)" % len(job.files)) - self.send_head(http.client.ACCEPTED, headers=headers) - # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - elif self.path.startswith("/edit"): - match = edit_pattern.match(self.path) - - if match: - job_id = match.groups()[0] - - job = self.server.getJobID(job_id) - - if job: - length = int(self.headers['content-length']) - info_map = eval(str(self.rfile.read(length), encoding='utf8')) - - job.edit(info_map) - self.send_head() - else: - # no such job id - self.send_head(http.client.NO_CONTENT) - else: - # invalid url - self.send_head(http.client.NO_CONTENT) - # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - elif self.path.startswith("/cancel"): - match = cancel_pattern.match(self.path) - - if match: - job_id = match.groups()[0] - - job = self.server.getJobID(job_id) - - if job: - self.server.stats("", "Cancelling job") - self.server.removeJob(job) - self.send_head() - else: - # no such job id - self.send_head(http.client.NO_CONTENT) - else: - # invalid url - self.send_head(http.client.NO_CONTENT) - - # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - elif self.path == "/clear": - # cancel all jobs - self.server.stats("", "Clearing jobs") - self.server.clear() - - self.send_head() - # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - elif self.path.startswith("/reset"): - match = reset_pattern.match(self.path) - - if match: - all = match.groups()[0] == 'all' - job_id = match.groups()[1] - job_frame = int(match.groups()[2]) - - job = self.server.getJobID(job_id) - - if job: - if job_frame != 0: - - frame = job[job_frame] - if frame: - self.server.stats("", "Reset job frame") - frame.reset(all) - self.send_head() - else: - # no such frame - self.send_head(http.client.NO_CONTENT) - - else: - self.server.stats("", "Reset job") - job.reset(all) - self.send_head() - - else: # job not found - self.send_head(http.client.NO_CONTENT) - else: # invalid url - self.send_head(http.client.NO_CONTENT) - # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - elif self.path == "/slave": - length = int(self.headers['content-length']) - job_frame_string = self.headers['job-frame'] - - self.server.stats("", "New slave connected") - - slave_info = netrender.model.RenderSlave.materialize(eval(str(self.rfile.read(length), encoding='utf8')), cache = False) - - slave_id = self.server.addSlave(slave_info.name, self.client_address, slave_info.stats) - - self.send_head(headers = {"slave-id": slave_id}) - # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - elif self.path == "/log": - length = int(self.headers['content-length']) - - log_info = netrender.model.LogFile.materialize(eval(str(self.rfile.read(length), encoding='utf8'))) - - slave_id = log_info.slave_id - - slave = self.server.getSeenSlave(slave_id) - - if slave: # only if slave id is valid - job = self.server.getJobID(log_info.job_id) - - if job: - self.server.stats("", "Log announcement") - job.addLog(log_info.frames) - self.send_head(http.client.OK) - else: - # no such job id - self.send_head(http.client.NO_CONTENT) - else: # invalid slave id - self.send_head(http.client.NO_CONTENT) - # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= - # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= - # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - def do_PUT(self): - - # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - if self.path.startswith("/file"): - match = file_pattern.match(self.path) - - if match: - self.server.stats("", "Receiving job") - - length = int(self.headers['content-length']) - job_id = match.groups()[0] - file_index = int(match.groups()[1]) - - job = self.server.getJobID(job_id) - - if job: - - render_file = job.files[file_index] - - if render_file: - main_file = job.files[0].filepath # filename of the first file - - main_path, main_name = os.path.split(main_file) - - if file_index > 0: - file_path = prefixPath(job.save_path, render_file.filepath, main_path) - else: - file_path = job.save_path + main_name - - buf = self.rfile.read(length) - - # add same temp file + renames as slave - - f = open(file_path, "wb") - f.write(buf) - f.close() - del buf - - render_file.filepath = file_path # set the new path - - if job.testStart(): - self.server.stats("", "File upload, starting job") - self.send_head(http.client.OK) - else: - self.server.stats("", "File upload, file missings") - self.send_head(http.client.ACCEPTED) - else: # invalid file - print("file not found", job_id, file_index) - self.send_head(http.client.NO_CONTENT) - else: # job not found - print("job not found", job_id, file_index) - self.send_head(http.client.NO_CONTENT) - else: # invalid url - print("no match") - self.send_head(http.client.NO_CONTENT) - # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - elif self.path == "/render": - self.server.stats("", "Receiving render result") - - # need some message content here or the slave doesn't like it - self.wfile.write(bytes("foo", encoding='utf8')) - - slave_id = self.headers['slave-id'] - - slave = self.server.getSeenSlave(slave_id) - - if slave: # only if slave id is valid - job_id = self.headers['job-id'] - - job = self.server.getJobID(job_id) - - if job: - job_frame = int(self.headers['job-frame']) - job_result = int(self.headers['job-result']) - job_time = float(self.headers['job-time']) - - frame = job[job_frame] - - if frame: - if job.type == netrender.model.JOB_BLENDER: - if job_result == DONE: - length = int(self.headers['content-length']) - buf = self.rfile.read(length) - f = open(job.save_path + "%04d" % job_frame + ".exr", 'wb') - f.write(buf) - f.close() - - del buf - elif job_result == ERROR: - # blacklist slave on this job on error - # slaves might already be in blacklist if errors on the whole chunk - if not slave.id in job.blacklist: - job.blacklist.append(slave.id) - - self.server.stats("", "Receiving result") - - slave.finishedFrame(job_frame) - - frame.status = job_result - frame.time = job_time - - job.testFinished() - - self.send_head() - else: # frame not found - self.send_head(http.client.NO_CONTENT) - else: # job not found - self.send_head(http.client.NO_CONTENT) - else: # invalid slave id - self.send_head(http.client.NO_CONTENT) - # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - elif self.path.startswith("/log"): - self.server.stats("", "Receiving log file") - - match = log_pattern.match(self.path) - - if match: - job_id = match.groups()[0] - - job = self.server.getJobID(job_id) - - if job: - job_frame = int(match.groups()[1]) - - frame = job[job_frame] - - if frame and frame.log_path: - length = int(self.headers['content-length']) - buf = self.rfile.read(length) - f = open(frame.log_path, 'ab') - f.write(buf) - f.close() - - del buf - - self.server.getSeenSlave(self.headers['slave-id']) - - self.send_head() - else: # frame not found - self.send_head(http.client.NO_CONTENT) - else: # job not found - self.send_head(http.client.NO_CONTENT) - else: # invalid url - self.send_head(http.client.NO_CONTENT) + def send_head(self, code = http.client.OK, headers = {}, content = "application/octet-stream"): + self.send_response(code) + self.send_header("Content-type", content) + + for key, value in headers.items(): + self.send_header(key, value) + + self.end_headers() + + def do_HEAD(self): + + if self.path == "/status": + job_id = self.headers.get('job-id', "") + job_frame = int(self.headers.get('job-frame', -1)) + + job = self.server.getJobID(job_id) + if job: + frame = job[job_frame] + + + if frame: + self.send_head(http.client.OK) + else: + # no such frame + self.send_head(http.client.NO_CONTENT) + else: + # no such job id + self.send_head(http.client.NO_CONTENT) + + # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + + def do_GET(self): + + if self.path == "/version": + self.send_head() + self.server.stats("", "Version check") + self.wfile.write(VERSION) + # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + elif self.path.startswith("/render"): + match = render_pattern.match(self.path) + + if match: + job_id = match.groups()[0] + frame_number = int(match.groups()[1]) + + job = self.server.getJobID(job_id) + + if job: + frame = job[frame_number] + + if frame: + if frame.status in (QUEUED, DISPATCHED): + self.send_head(http.client.ACCEPTED) + elif frame.status == DONE: + self.server.stats("", "Sending result to client") + f = open(job.save_path + "%04d" % frame_number + ".exr", 'rb') + + self.send_head() + + shutil.copyfileobj(f, self.wfile) + + f.close() + elif frame.status == ERROR: + self.send_head(http.client.PARTIAL_CONTENT) + else: + # no such frame + self.send_head(http.client.NO_CONTENT) + else: + # no such job id + self.send_head(http.client.NO_CONTENT) + else: + # invalid url + self.send_head(http.client.NO_CONTENT) + # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + elif self.path.startswith("/log"): + match = log_pattern.match(self.path) + + if match: + job_id = match.groups()[0] + frame_number = int(match.groups()[1]) + + job = self.server.getJobID(job_id) + + if job: + frame = job[frame_number] + + if frame: + if not frame.log_path or frame.status in (QUEUED, DISPATCHED): + self.send_head(http.client.PROCESSING) + else: + self.server.stats("", "Sending log to client") + f = open(frame.log_path, 'rb') + + self.send_head(content = "text/plain") + + shutil.copyfileobj(f, self.wfile) + + f.close() + else: + # no such frame + self.send_head(http.client.NO_CONTENT) + else: + # no such job id + self.send_head(http.client.NO_CONTENT) + else: + # invalid URL + self.send_head(http.client.NO_CONTENT) + # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + elif self.path == "/status": + job_id = self.headers.get('job-id', "") + job_frame = int(self.headers.get('job-frame', -1)) + + if job_id: + + job = self.server.getJobID(job_id) + if job: + if job_frame != -1: + frame = job[frame] + + if frame: + message = frame.serialize() + else: + # no such frame + self.send_heat(http.client.NO_CONTENT) + return + else: + message = job.serialize() + else: + # no such job id + self.send_head(http.client.NO_CONTENT) + return + else: # status of all jobs + message = [] + + for job in self.server: + message.append(job.serialize()) + + + self.server.stats("", "Sending status") + self.send_head() + self.wfile.write(bytes(repr(message), encoding='utf8')) + + # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + elif self.path == "/job": + self.server.balance() + + slave_id = self.headers['slave-id'] + + slave = self.server.getSeenSlave(slave_id) + + if slave: # only if slave id is valid + job, frames = self.server.newDispatch(slave_id) + + if job and frames: + for f in frames: + print("dispatch", f.number) + f.status = DISPATCHED + f.slave = slave + + slave.job = job + slave.job_frames = [f.number for f in frames] + + self.send_head(headers={"job-id": job.id}) + + message = job.serialize(frames) + + self.wfile.write(bytes(repr(message), encoding='utf8')) + + self.server.stats("", "Sending job to slave") + else: + # no job available, return error code + slave.job = None + slave.job_frames = [] + + self.send_head(http.client.ACCEPTED) + else: # invalid slave id + self.send_head(http.client.NO_CONTENT) + # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + elif self.path.startswith("/file"): + match = file_pattern.match(self.path) + + if match: + slave_id = self.headers['slave-id'] + slave = self.server.getSeenSlave(slave_id) + + if not slave: + # invalid slave id + print("invalid slave id") + + job_id = match.groups()[0] + file_index = int(match.groups()[1]) + + job = self.server.getJobID(job_id) + + if job: + render_file = job.files[file_index] + + if render_file: + self.server.stats("", "Sending file to slave") + f = open(render_file.filepath, 'rb') + + self.send_head() + shutil.copyfileobj(f, self.wfile) + + f.close() + else: + # no such file + self.send_head(http.client.NO_CONTENT) + else: + # no such job id + self.send_head(http.client.NO_CONTENT) + else: # invalid url + self.send_head(http.client.NO_CONTENT) + # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + elif self.path == "/slaves": + message = [] + + self.server.stats("", "Sending slaves status") + + for slave in self.server.slaves: + message.append(slave.serialize()) + + self.send_head() + + self.wfile.write(bytes(repr(message), encoding='utf8')) + # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + else: + # hand over the rest to the html section + netrender.master_html.get(self) + + # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + def do_POST(self): + + # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + if self.path == "/job": + + length = int(self.headers['content-length']) + + job_info = netrender.model.RenderJob.materialize(eval(str(self.rfile.read(length), encoding='utf8'))) + + job_id = self.server.nextJobID() + + job = MRenderJob(job_id, job_info) + + for frame in job_info.frames: + frame = job.addFrame(frame.number, frame.command) + + self.server.addJob(job) + + headers={"job-id": job_id} + + if job.testStart(): + self.server.stats("", "New job, started") + self.send_head(headers=headers) + else: + self.server.stats("", "New job, missing files (%i total)" % len(job.files)) + self.send_head(http.client.ACCEPTED, headers=headers) + # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + elif self.path.startswith("/edit"): + match = edit_pattern.match(self.path) + + if match: + job_id = match.groups()[0] + + job = self.server.getJobID(job_id) + + if job: + length = int(self.headers['content-length']) + info_map = eval(str(self.rfile.read(length), encoding='utf8')) + + job.edit(info_map) + self.send_head() + else: + # no such job id + self.send_head(http.client.NO_CONTENT) + else: + # invalid url + self.send_head(http.client.NO_CONTENT) + # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + elif self.path.startswith("/cancel"): + match = cancel_pattern.match(self.path) + + if match: + job_id = match.groups()[0] + + job = self.server.getJobID(job_id) + + if job: + self.server.stats("", "Cancelling job") + self.server.removeJob(job) + self.send_head() + else: + # no such job id + self.send_head(http.client.NO_CONTENT) + else: + # invalid url + self.send_head(http.client.NO_CONTENT) + + # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + elif self.path == "/clear": + # cancel all jobs + self.server.stats("", "Clearing jobs") + self.server.clear() + + self.send_head() + # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + elif self.path.startswith("/reset"): + match = reset_pattern.match(self.path) + + if match: + all = match.groups()[0] == 'all' + job_id = match.groups()[1] + job_frame = int(match.groups()[2]) + + job = self.server.getJobID(job_id) + + if job: + if job_frame != 0: + + frame = job[job_frame] + if frame: + self.server.stats("", "Reset job frame") + frame.reset(all) + self.send_head() + else: + # no such frame + self.send_head(http.client.NO_CONTENT) + + else: + self.server.stats("", "Reset job") + job.reset(all) + self.send_head() + + else: # job not found + self.send_head(http.client.NO_CONTENT) + else: # invalid url + self.send_head(http.client.NO_CONTENT) + # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + elif self.path == "/slave": + length = int(self.headers['content-length']) + job_frame_string = self.headers['job-frame'] + + self.server.stats("", "New slave connected") + + slave_info = netrender.model.RenderSlave.materialize(eval(str(self.rfile.read(length), encoding='utf8')), cache = False) + + slave_id = self.server.addSlave(slave_info.name, self.client_address, slave_info.stats) + + self.send_head(headers = {"slave-id": slave_id}) + # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + elif self.path == "/log": + length = int(self.headers['content-length']) + + log_info = netrender.model.LogFile.materialize(eval(str(self.rfile.read(length), encoding='utf8'))) + + slave_id = log_info.slave_id + + slave = self.server.getSeenSlave(slave_id) + + if slave: # only if slave id is valid + job = self.server.getJobID(log_info.job_id) + + if job: + self.server.stats("", "Log announcement") + job.addLog(log_info.frames) + self.send_head(http.client.OK) + else: + # no such job id + self.send_head(http.client.NO_CONTENT) + else: # invalid slave id + self.send_head(http.client.NO_CONTENT) + # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + def do_PUT(self): + + # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + if self.path.startswith("/file"): + match = file_pattern.match(self.path) + + if match: + self.server.stats("", "Receiving job") + + length = int(self.headers['content-length']) + job_id = match.groups()[0] + file_index = int(match.groups()[1]) + + job = self.server.getJobID(job_id) + + if job: + + render_file = job.files[file_index] + + if render_file: + main_file = job.files[0].filepath # filename of the first file + + main_path, main_name = os.path.split(main_file) + + if file_index > 0: + file_path = prefixPath(job.save_path, render_file.filepath, main_path) + else: + file_path = job.save_path + main_name + + buf = self.rfile.read(length) + + # add same temp file + renames as slave + + f = open(file_path, "wb") + f.write(buf) + f.close() + del buf + + render_file.filepath = file_path # set the new path + + if job.testStart(): + self.server.stats("", "File upload, starting job") + self.send_head(http.client.OK) + else: + self.server.stats("", "File upload, file missings") + self.send_head(http.client.ACCEPTED) + else: # invalid file + print("file not found", job_id, file_index) + self.send_head(http.client.NO_CONTENT) + else: # job not found + print("job not found", job_id, file_index) + self.send_head(http.client.NO_CONTENT) + else: # invalid url + print("no match") + self.send_head(http.client.NO_CONTENT) + # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + elif self.path == "/render": + self.server.stats("", "Receiving render result") + + # need some message content here or the slave doesn't like it + self.wfile.write(bytes("foo", encoding='utf8')) + + slave_id = self.headers['slave-id'] + + slave = self.server.getSeenSlave(slave_id) + + if slave: # only if slave id is valid + job_id = self.headers['job-id'] + + job = self.server.getJobID(job_id) + + if job: + job_frame = int(self.headers['job-frame']) + job_result = int(self.headers['job-result']) + job_time = float(self.headers['job-time']) + + frame = job[job_frame] + + if frame: + if job.type == netrender.model.JOB_BLENDER: + if job_result == DONE: + length = int(self.headers['content-length']) + buf = self.rfile.read(length) + f = open(job.save_path + "%04d" % job_frame + ".exr", 'wb') + f.write(buf) + f.close() + + del buf + elif job_result == ERROR: + # blacklist slave on this job on error + # slaves might already be in blacklist if errors on the whole chunk + if not slave.id in job.blacklist: + job.blacklist.append(slave.id) + + self.server.stats("", "Receiving result") + + slave.finishedFrame(job_frame) + + frame.status = job_result + frame.time = job_time + + job.testFinished() + + self.send_head() + else: # frame not found + self.send_head(http.client.NO_CONTENT) + else: # job not found + self.send_head(http.client.NO_CONTENT) + else: # invalid slave id + self.send_head(http.client.NO_CONTENT) + # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + elif self.path.startswith("/log"): + self.server.stats("", "Receiving log file") + + match = log_pattern.match(self.path) + + if match: + job_id = match.groups()[0] + + job = self.server.getJobID(job_id) + + if job: + job_frame = int(match.groups()[1]) + + frame = job[job_frame] + + if frame and frame.log_path: + length = int(self.headers['content-length']) + buf = self.rfile.read(length) + f = open(frame.log_path, 'ab') + f.write(buf) + f.close() + + del buf + + self.server.getSeenSlave(self.headers['slave-id']) + + self.send_head() + else: # frame not found + self.send_head(http.client.NO_CONTENT) + else: # job not found + self.send_head(http.client.NO_CONTENT) + else: # invalid url + self.send_head(http.client.NO_CONTENT) class RenderMasterServer(http.server.HTTPServer): - def __init__(self, address, handler_class, path): - super().__init__(address, handler_class) - self.jobs = [] - self.jobs_map = {} - self.slaves = [] - self.slaves_map = {} - self.job_id = 0 - self.path = path + "master_" + str(os.getpid()) + os.sep - - self.slave_timeout = 30 # 30 mins: need a parameter for that - - self.balancer = netrender.balancing.Balancer() - self.balancer.addRule(netrender.balancing.RatingUsageByCategory(self.getJobs)) - self.balancer.addRule(netrender.balancing.RatingUsage()) - self.balancer.addException(netrender.balancing.ExcludeQueuedEmptyJob()) - self.balancer.addException(netrender.balancing.ExcludeSlavesLimit(self.countJobs, self.countSlaves, limit = 0.9)) - self.balancer.addPriority(netrender.balancing.NewJobPriority()) - self.balancer.addPriority(netrender.balancing.MinimumTimeBetweenDispatchPriority(limit = 2)) - - if not os.path.exists(self.path): - os.mkdir(self.path) - - def nextJobID(self): - self.job_id += 1 - return str(self.job_id) - - def addSlave(self, name, address, stats): - slave = MRenderSlave(name, address, stats) - self.slaves.append(slave) - self.slaves_map[slave.id] = slave - - return slave.id - - def removeSlave(self, slave): - self.slaves.remove(slave) - self.slaves_map.pop(slave.id) - - def getSlave(self, slave_id): - return self.slaves_map.get(slave_id, None) - - def getSeenSlave(self, slave_id): - slave = self.getSlave(slave_id) - if slave: - slave.seen() - - return slave - - def timeoutSlaves(self): - removed = [] - - t = time.time() - - for slave in self.slaves: - if (t - slave.last_seen) / 60 > self.slave_timeout: - removed.append(slave) - - if slave.job: - for f in slave.job_frames: - slave.job[f].status = ERROR - - for slave in removed: - self.removeSlave(slave) - - def updateUsage(self): - blend = 0.5 - for job in self.jobs: - job.usage *= (1 - blend) - - if self.slaves: - slave_usage = blend / self.countSlaves() - - for slave in self.slaves: - if slave.job: - slave.job.usage += slave_usage - - - def clear(self): - removed = self.jobs[:] - - for job in removed: - self.removeJob(job) - - def balance(self): - self.balancer.balance(self.jobs) - - def getJobs(self): - return self.jobs - - def countJobs(self, status = JOB_QUEUED): - total = 0 - for j in self.jobs: - if j.status == status: - total += 1 - - return total - - def countSlaves(self): - return len(self.slaves) - - def removeJob(self, job): - self.jobs.remove(job) - self.jobs_map.pop(job.id) - - for slave in self.slaves: - if slave.job == job: - slave.job = None - slave.job_frames = [] - - def addJob(self, job): - self.jobs.append(job) - self.jobs_map[job.id] = job - - # create job directory - job.save_path = self.path + "job_" + job.id + os.sep - if not os.path.exists(job.save_path): - os.mkdir(job.save_path) - - job.save() - - def getJobID(self, id): - return self.jobs_map.get(id, None) - - def __iter__(self): - for job in self.jobs: - yield job - - def newDispatch(self, slave_id): - if self.jobs: - for job in self.jobs: - if not self.balancer.applyExceptions(job) and slave_id not in job.blacklist: - return job, job.getFrames() - - return None, None + def __init__(self, address, handler_class, path): + super().__init__(address, handler_class) + self.jobs = [] + self.jobs_map = {} + self.slaves = [] + self.slaves_map = {} + self.job_id = 0 + self.path = path + "master_" + str(os.getpid()) + os.sep + + self.slave_timeout = 30 # 30 mins: need a parameter for that + + self.balancer = netrender.balancing.Balancer() + self.balancer.addRule(netrender.balancing.RatingUsageByCategory(self.getJobs)) + self.balancer.addRule(netrender.balancing.RatingUsage()) + self.balancer.addException(netrender.balancing.ExcludeQueuedEmptyJob()) + self.balancer.addException(netrender.balancing.ExcludeSlavesLimit(self.countJobs, self.countSlaves, limit = 0.9)) + self.balancer.addPriority(netrender.balancing.NewJobPriority()) + self.balancer.addPriority(netrender.balancing.MinimumTimeBetweenDispatchPriority(limit = 2)) + + if not os.path.exists(self.path): + os.mkdir(self.path) + + def nextJobID(self): + self.job_id += 1 + return str(self.job_id) + + def addSlave(self, name, address, stats): + slave = MRenderSlave(name, address, stats) + self.slaves.append(slave) + self.slaves_map[slave.id] = slave + + return slave.id + + def removeSlave(self, slave): + self.slaves.remove(slave) + self.slaves_map.pop(slave.id) + + def getSlave(self, slave_id): + return self.slaves_map.get(slave_id, None) + + def getSeenSlave(self, slave_id): + slave = self.getSlave(slave_id) + if slave: + slave.seen() + + return slave + + def timeoutSlaves(self): + removed = [] + + t = time.time() + + for slave in self.slaves: + if (t - slave.last_seen) / 60 > self.slave_timeout: + removed.append(slave) + + if slave.job: + for f in slave.job_frames: + slave.job[f].status = ERROR + + for slave in removed: + self.removeSlave(slave) + + def updateUsage(self): + blend = 0.5 + for job in self.jobs: + job.usage *= (1 - blend) + + if self.slaves: + slave_usage = blend / self.countSlaves() + + for slave in self.slaves: + if slave.job: + slave.job.usage += slave_usage + + + def clear(self): + removed = self.jobs[:] + + for job in removed: + self.removeJob(job) + + def balance(self): + self.balancer.balance(self.jobs) + + def getJobs(self): + return self.jobs + + def countJobs(self, status = JOB_QUEUED): + total = 0 + for j in self.jobs: + if j.status == status: + total += 1 + + return total + + def countSlaves(self): + return len(self.slaves) + + def removeJob(self, job): + self.jobs.remove(job) + self.jobs_map.pop(job.id) + + for slave in self.slaves: + if slave.job == job: + slave.job = None + slave.job_frames = [] + + def addJob(self, job): + self.jobs.append(job) + self.jobs_map[job.id] = job + + # create job directory + job.save_path = self.path + "job_" + job.id + os.sep + if not os.path.exists(job.save_path): + os.mkdir(job.save_path) + + job.save() + + def getJobID(self, id): + return self.jobs_map.get(id, None) + + def __iter__(self): + for job in self.jobs: + yield job + + def newDispatch(self, slave_id): + if self.jobs: + for job in self.jobs: + if not self.balancer.applyExceptions(job) and slave_id not in job.blacklist: + return job, job.getFrames() + + return None, None def runMaster(address, broadcast, path, update_stats, test_break): - httpd = RenderMasterServer(address, RenderHandler, path) - httpd.timeout = 1 - httpd.stats = update_stats - - if broadcast: - s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) - - start_time = time.time() - - while not test_break(): - httpd.handle_request() - - if time.time() - start_time >= 10: # need constant here - httpd.timeoutSlaves() - - httpd.updateUsage() - - if broadcast: - print("broadcasting address") - s.sendto(bytes("%i" % address[1], encoding='utf8'), 0, ('<broadcast>', 8000)) - start_time = time.time() - - httpd.server_close() + httpd = RenderMasterServer(address, RenderHandler, path) + httpd.timeout = 1 + httpd.stats = update_stats + + if broadcast: + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) + + start_time = time.time() + + while not test_break(): + httpd.handle_request() + + if time.time() - start_time >= 10: # need constant here + httpd.timeoutSlaves() + + httpd.updateUsage() + + if broadcast: + print("broadcasting address") + s.sendto(bytes("%i" % address[1], encoding='utf8'), 0, ('<broadcast>', 8000)) + start_time = time.time() + + httpd.server_close() diff --git a/release/scripts/io/netrender/master_html.py b/release/scripts/io/netrender/master_html.py index 1f530efa7c6..af4db43ae9d 100644 --- a/release/scripts/io/netrender/master_html.py +++ b/release/scripts/io/netrender/master_html.py @@ -4,12 +4,12 @@ # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. -# +# # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. -# +# # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software Foundation, # Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. @@ -24,226 +24,226 @@ from netrender.utils import * src_folder = os.path.split(__file__)[0] def get(handler): - def output(text): - handler.wfile.write(bytes(text, encoding='utf8')) - - def head(title): - output("<html><head>") - output("<script src='/html/netrender.js' type='text/javascript'></script>") + def output(text): + handler.wfile.write(bytes(text, encoding='utf8')) + + def head(title): + output("<html><head>") + output("<script src='/html/netrender.js' type='text/javascript'></script>") # output("<script src='/html/json2.js' type='text/javascript'></script>") - output("<title>") - output(title) - output("</title></head><body>") - output("<link rel='stylesheet' href='/html/netrender.css' type='text/css'>") - - - def link(text, url): - return "<a href='%s'>%s</a>" % (url, text) - - def startTable(border=1, class_style = None, caption = None): - output("<table border='%i'" % border) - - if class_style: - output(" class='%s'" % class_style) - - output(">") - - if caption: - output("<caption>%s</caption>" % caption) - - def headerTable(*headers): - output("<thead><tr>") - - for c in headers: - output("<td>" + c + "</td>") - - output("</tr></thead>") - - def rowTable(*data, id = None, class_style = None, extra = None): - output("<tr") - - if id: - output(" id='%s'" % id) - - if class_style: - output(" class='%s'" % class_style) - - if extra: - output(" %s" % extra) - - output(">") - - for c in data: - output("<td>" + str(c) + "</td>") - - output("</tr>") - - def endTable(): - output("</table>") - - if handler.path == "/html/netrender.js": - f = open(os.path.join(src_folder, "netrender.js"), 'rb') - - handler.send_head(content = "text/javascript") - shutil.copyfileobj(f, handler.wfile) - - f.close() - elif handler.path == "/html/netrender.css": - f = open(os.path.join(src_folder, "netrender.css"), 'rb') - - handler.send_head(content = "text/css") - shutil.copyfileobj(f, handler.wfile) - - f.close() - elif handler.path == "/html" or handler.path == "/": - handler.send_head(content = "text/html") - head("NetRender") - - output("<h2>Master</h2>") - - output("""<button title="remove all jobs" onclick="request('/clear', null);">CLEAR JOB LIST</button>""") - - startTable(caption = "Rules", class_style = "rules") - - headerTable("type", "description", "limit") - - for rule in handler.server.balancer.rules: - rowTable("rating", rule, rule.str_limit() if hasattr(rule, "limit") else " ") - - for rule in handler.server.balancer.priorities: - rowTable("priority", rule, rule.str_limit() if hasattr(rule, "limit") else " ") - - for rule in handler.server.balancer.exceptions: - rowTable("exception", rule, rule.str_limit() if hasattr(rule, "limit") else " ") - - endTable() - - output("<h2>Slaves</h2>") - - startTable() - headerTable("name", "address", "last seen", "stats", "job") - - for slave in handler.server.slaves: - rowTable(slave.name, slave.address[0], time.ctime(slave.last_seen), slave.stats, link(slave.job.name, "/html/job" + slave.job.id) if slave.job else "None") - - endTable() - - output("<h2>Jobs</h2>") - - startTable() - headerTable( - " ", - "id", - "name", - "category", - "chunks", - "priority", - "usage", - "wait", - "status", - "length", - "done", - "dispatched", - "error", - "first", - "exception" - ) - - handler.server.balance() - - for job in handler.server.jobs: - results = job.framesStatus() - rowTable( - """<button title="cancel job" onclick="request('/cancel_%s', null);">X</button>""" % job.id + - """<button title="reset all frames" onclick="request('/resetall_%s_0', null);">R</button>""" % job.id, - job.id, - link(job.name, "/html/job" + job.id), - job.category if job.category else "<i>None</i>", - str(job.chunks) + - """<button title="increase priority" onclick="request('/edit_%s', "{'chunks': %i}");">+</button>""" % (job.id, job.chunks + 1) + - """<button title="decrease priority" onclick="request('/edit_%s', "{'chunks': %i}");" %s>-</button>""" % (job.id, job.chunks - 1, "disabled=True" if job.chunks == 1 else ""), - str(job.priority) + - """<button title="increase chunks size" onclick="request('/edit_%s', "{'priority': %i}");">+</button>""" % (job.id, job.priority + 1) + - """<button title="decrease chunks size" onclick="request('/edit_%s', "{'priority': %i}");" %s>-</button>""" % (job.id, job.priority - 1, "disabled=True" if job.priority == 1 else ""), - "%0.1f%%" % (job.usage * 100), - "%is" % int(time.time() - job.last_dispatched), - job.statusText(), - len(job), - results[DONE], - results[DISPATCHED], - str(results[ERROR]) + - """<button title="reset error frames" onclick="request('/reset_%s_0', null);" %s>R</button>""" % (job.id, "disabled=True" if not results[ERROR] else ""), - handler.server.balancer.applyPriorities(job), handler.server.balancer.applyExceptions(job) - ) - - endTable() - - output("</body></html>") - - elif handler.path.startswith("/html/job"): - handler.send_head(content = "text/html") - job_id = handler.path[9:] - - head("NetRender") - - job = handler.server.getJobID(job_id) - - if job: - output("<h2>Files</h2>") - - startTable() - headerTable("path") - - tot_cache = 0 - tot_fluid = 0 - - for file in job.files: - if file.filepath.endswith(".bphys"): - tot_cache += 1 - elif file.filepath.endswith(".bobj.gz") or file.filepath.endswith(".bvel.gz"): - tot_fluid += 1 - else: - rowTable(file.filepath) - - if tot_cache > 0: - rowTable("%i physic cache files" % tot_cache, class_style = "toggle", extra = "onclick='toggleDisplay(".cache", "none", "table-row")'") - for file in job.files: - if file.filepath.endswith(".bphys"): - rowTable(os.path.split(file.filepath)[1], class_style = "cache") - - if tot_fluid > 0: - rowTable("%i fluid bake files" % tot_fluid, class_style = "toggle", extra = "onclick='toggleDisplay(".fluid", "none", "table-row")'") - for file in job.files: - if file.filepath.endswith(".bobj.gz") or file.filepath.endswith(".bvel.gz"): - rowTable(os.path.split(file.filepath)[1], class_style = "fluid") - - endTable() - - output("<h2>Blacklist</h2>") - - if job.blacklist: - startTable() - headerTable("name", "address") - - for slave_id in job.blacklist: - slave = handler.server.slaves_map[slave_id] - rowTable(slave.name, slave.address[0]) - - endTable() - else: - output("<i>Empty</i>") - - output("<h2>Frames</h2>") - - startTable() - headerTable("no", "status", "render time", "slave", "log", "result") - - for frame in job.frames: - rowTable(frame.number, frame.statusText(), "%.1fs" % frame.time, frame.slave.name if frame.slave else " ", link("view log", logURL(job_id, frame.number)) if frame.log_path else " ", link("view result", renderURL(job_id, frame.number)) if frame.status == DONE else " ") - - endTable() - else: - output("no such job") - - output("</body></html>") + output("<title>") + output(title) + output("</title></head><body>") + output("<link rel='stylesheet' href='/html/netrender.css' type='text/css'>") + + + def link(text, url): + return "<a href='%s'>%s</a>" % (url, text) + + def startTable(border=1, class_style = None, caption = None): + output("<table border='%i'" % border) + + if class_style: + output(" class='%s'" % class_style) + + output(">") + + if caption: + output("<caption>%s</caption>" % caption) + + def headerTable(*headers): + output("<thead><tr>") + + for c in headers: + output("<td>" + c + "</td>") + + output("</tr></thead>") + + def rowTable(*data, id = None, class_style = None, extra = None): + output("<tr") + + if id: + output(" id='%s'" % id) + + if class_style: + output(" class='%s'" % class_style) + + if extra: + output(" %s" % extra) + + output(">") + + for c in data: + output("<td>" + str(c) + "</td>") + + output("</tr>") + + def endTable(): + output("</table>") + + if handler.path == "/html/netrender.js": + f = open(os.path.join(src_folder, "netrender.js"), 'rb') + + handler.send_head(content = "text/javascript") + shutil.copyfileobj(f, handler.wfile) + + f.close() + elif handler.path == "/html/netrender.css": + f = open(os.path.join(src_folder, "netrender.css"), 'rb') + + handler.send_head(content = "text/css") + shutil.copyfileobj(f, handler.wfile) + + f.close() + elif handler.path == "/html" or handler.path == "/": + handler.send_head(content = "text/html") + head("NetRender") + + output("<h2>Master</h2>") + + output("""<button title="remove all jobs" onclick="request('/clear', null);">CLEAR JOB LIST</button>""") + + startTable(caption = "Rules", class_style = "rules") + + headerTable("type", "description", "limit") + + for rule in handler.server.balancer.rules: + rowTable("rating", rule, rule.str_limit() if hasattr(rule, "limit") else " ") + + for rule in handler.server.balancer.priorities: + rowTable("priority", rule, rule.str_limit() if hasattr(rule, "limit") else " ") + + for rule in handler.server.balancer.exceptions: + rowTable("exception", rule, rule.str_limit() if hasattr(rule, "limit") else " ") + + endTable() + + output("<h2>Slaves</h2>") + + startTable() + headerTable("name", "address", "last seen", "stats", "job") + + for slave in handler.server.slaves: + rowTable(slave.name, slave.address[0], time.ctime(slave.last_seen), slave.stats, link(slave.job.name, "/html/job" + slave.job.id) if slave.job else "None") + + endTable() + + output("<h2>Jobs</h2>") + + startTable() + headerTable( + " ", + "id", + "name", + "category", + "chunks", + "priority", + "usage", + "wait", + "status", + "length", + "done", + "dispatched", + "error", + "first", + "exception" + ) + + handler.server.balance() + + for job in handler.server.jobs: + results = job.framesStatus() + rowTable( + """<button title="cancel job" onclick="request('/cancel_%s', null);">X</button>""" % job.id + + """<button title="reset all frames" onclick="request('/resetall_%s_0', null);">R</button>""" % job.id, + job.id, + link(job.name, "/html/job" + job.id), + job.category if job.category else "<i>None</i>", + str(job.chunks) + + """<button title="increase priority" onclick="request('/edit_%s', "{'chunks': %i}");">+</button>""" % (job.id, job.chunks + 1) + + """<button title="decrease priority" onclick="request('/edit_%s', "{'chunks': %i}");" %s>-</button>""" % (job.id, job.chunks - 1, "disabled=True" if job.chunks == 1 else ""), + str(job.priority) + + """<button title="increase chunks size" onclick="request('/edit_%s', "{'priority': %i}");">+</button>""" % (job.id, job.priority + 1) + + """<button title="decrease chunks size" onclick="request('/edit_%s', "{'priority': %i}");" %s>-</button>""" % (job.id, job.priority - 1, "disabled=True" if job.priority == 1 else ""), + "%0.1f%%" % (job.usage * 100), + "%is" % int(time.time() - job.last_dispatched), + job.statusText(), + len(job), + results[DONE], + results[DISPATCHED], + str(results[ERROR]) + + """<button title="reset error frames" onclick="request('/reset_%s_0', null);" %s>R</button>""" % (job.id, "disabled=True" if not results[ERROR] else ""), + handler.server.balancer.applyPriorities(job), handler.server.balancer.applyExceptions(job) + ) + + endTable() + + output("</body></html>") + + elif handler.path.startswith("/html/job"): + handler.send_head(content = "text/html") + job_id = handler.path[9:] + + head("NetRender") + + job = handler.server.getJobID(job_id) + + if job: + output("<h2>Files</h2>") + + startTable() + headerTable("path") + + tot_cache = 0 + tot_fluid = 0 + + for file in job.files: + if file.filepath.endswith(".bphys"): + tot_cache += 1 + elif file.filepath.endswith(".bobj.gz") or file.filepath.endswith(".bvel.gz"): + tot_fluid += 1 + else: + rowTable(file.filepath) + + if tot_cache > 0: + rowTable("%i physic cache files" % tot_cache, class_style = "toggle", extra = "onclick='toggleDisplay(".cache", "none", "table-row")'") + for file in job.files: + if file.filepath.endswith(".bphys"): + rowTable(os.path.split(file.filepath)[1], class_style = "cache") + + if tot_fluid > 0: + rowTable("%i fluid bake files" % tot_fluid, class_style = "toggle", extra = "onclick='toggleDisplay(".fluid", "none", "table-row")'") + for file in job.files: + if file.filepath.endswith(".bobj.gz") or file.filepath.endswith(".bvel.gz"): + rowTable(os.path.split(file.filepath)[1], class_style = "fluid") + + endTable() + + output("<h2>Blacklist</h2>") + + if job.blacklist: + startTable() + headerTable("name", "address") + + for slave_id in job.blacklist: + slave = handler.server.slaves_map[slave_id] + rowTable(slave.name, slave.address[0]) + + endTable() + else: + output("<i>Empty</i>") + + output("<h2>Frames</h2>") + + startTable() + headerTable("no", "status", "render time", "slave", "log", "result") + + for frame in job.frames: + rowTable(frame.number, frame.statusText(), "%.1fs" % frame.time, frame.slave.name if frame.slave else " ", link("view log", logURL(job_id, frame.number)) if frame.log_path else " ", link("view result", renderURL(job_id, frame.number)) if frame.status == DONE else " ") + + endTable() + else: + output("no such job") + + output("</body></html>") diff --git a/release/scripts/io/netrender/model.py b/release/scripts/io/netrender/model.py index f541b0c5e1a..c5b4af47aa1 100644 --- a/release/scripts/io/netrender/model.py +++ b/release/scripts/io/netrender/model.py @@ -4,12 +4,12 @@ # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. -# +# # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. -# +# # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software Foundation, # Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. @@ -23,256 +23,256 @@ import subprocess, shutil, time, hashlib from netrender.utils import * class LogFile: - def __init__(self, job_id = 0, slave_id = 0, frames = []): - self.job_id = job_id - self.slave_id = slave_id - self.frames = frames - - def serialize(self): - return { - "job_id": self.job_id, - "slave_id": self.slave_id, - "frames": self.frames - } - - @staticmethod - def materialize(data): - if not data: - return None - - logfile = LogFile() - logfile.job_id = data["job_id"] - logfile.slave_id = data["slave_id"] - logfile.frames = data["frames"] - - return logfile + def __init__(self, job_id = 0, slave_id = 0, frames = []): + self.job_id = job_id + self.slave_id = slave_id + self.frames = frames + + def serialize(self): + return { + "job_id": self.job_id, + "slave_id": self.slave_id, + "frames": self.frames + } + + @staticmethod + def materialize(data): + if not data: + return None + + logfile = LogFile() + logfile.job_id = data["job_id"] + logfile.slave_id = data["slave_id"] + logfile.frames = data["frames"] + + return logfile class RenderSlave: - _slave_map = {} - - def __init__(self): - self.id = "" - self.name = "" - self.address = ("",0) - self.stats = "" - self.total_done = 0 - self.total_error = 0 - self.last_seen = 0.0 - - def serialize(self): - return { - "id": self.id, - "name": self.name, - "address": self.address, - "stats": self.stats, - "total_done": self.total_done, - "total_error": self.total_error, - "last_seen": self.last_seen - } - - @staticmethod - def materialize(data, cache = True): - if not data: - return None - - slave_id = data["id"] - - if cache and slave_id in RenderSlave._slave_map: - return RenderSlave._slave_map[slave_id] - - slave = RenderSlave() - slave.id = slave_id - slave.name = data["name"] - slave.address = data["address"] - slave.stats = data["stats"] - slave.total_done = data["total_done"] - slave.total_error = data["total_error"] - slave.last_seen = data["last_seen"] - - if cache: - RenderSlave._slave_map[slave_id] = slave - - return slave + _slave_map = {} + + def __init__(self): + self.id = "" + self.name = "" + self.address = ("",0) + self.stats = "" + self.total_done = 0 + self.total_error = 0 + self.last_seen = 0.0 + + def serialize(self): + return { + "id": self.id, + "name": self.name, + "address": self.address, + "stats": self.stats, + "total_done": self.total_done, + "total_error": self.total_error, + "last_seen": self.last_seen + } + + @staticmethod + def materialize(data, cache = True): + if not data: + return None + + slave_id = data["id"] + + if cache and slave_id in RenderSlave._slave_map: + return RenderSlave._slave_map[slave_id] + + slave = RenderSlave() + slave.id = slave_id + slave.name = data["name"] + slave.address = data["address"] + slave.stats = data["stats"] + slave.total_done = data["total_done"] + slave.total_error = data["total_error"] + slave.last_seen = data["last_seen"] + + if cache: + RenderSlave._slave_map[slave_id] = slave + + return slave JOB_BLENDER = 1 JOB_PROCESS = 2 JOB_TYPES = { - JOB_BLENDER: "Blender", - JOB_PROCESS: "Process" - } + JOB_BLENDER: "Blender", + JOB_PROCESS: "Process" + } class RenderFile: - def __init__(self, filepath = "", index = 0, start = -1, end = -1): - self.filepath = filepath - self.index = index - self.start = start - self.end = end - - def serialize(self): - return { - "filepath": self.filepath, - "index": self.index, - "start": self.start, - "end": self.end - } - - @staticmethod - def materialize(data): - if not data: - return None - - rfile = RenderFile(data["filepath"], data["index"], data["start"], data["end"]) - - return rfile + def __init__(self, filepath = "", index = 0, start = -1, end = -1): + self.filepath = filepath + self.index = index + self.start = start + self.end = end + + def serialize(self): + return { + "filepath": self.filepath, + "index": self.index, + "start": self.start, + "end": self.end + } + + @staticmethod + def materialize(data): + if not data: + return None + + rfile = RenderFile(data["filepath"], data["index"], data["start"], data["end"]) + + return rfile class RenderJob: - def __init__(self, job_info = None): - self.id = "" - self.type = JOB_BLENDER - self.name = "" - self.category = "None" - self.status = JOB_WAITING - self.files = [] - self.chunks = 0 - self.priority = 0 - self.blacklist = [] - - self.usage = 0.0 - self.last_dispatched = 0.0 - self.frames = [] - - if job_info: - self.type = job_info.type - self.name = job_info.name - self.category = job_info.category - self.status = job_info.status - self.files = job_info.files - self.chunks = job_info.chunks - self.priority = job_info.priority - self.blacklist = job_info.blacklist - - def addFile(self, file_path, start=-1, end=-1): - self.files.append(RenderFile(file_path, len(self.files), start, end)) - - def addFrame(self, frame_number, command = ""): - frame = RenderFrame(frame_number, command) - self.frames.append(frame) - return frame - - def __len__(self): - return len(self.frames) - - def countFrames(self, status=QUEUED): - total = 0 - for f in self.frames: - if f.status == status: - total += 1 - - return total - - def countSlaves(self): - return len(set((frame.slave for frame in self.frames if frame.status == DISPATCHED))) - - def statusText(self): - return JOB_STATUS_TEXT[self.status] - - def framesStatus(self): - results = { - QUEUED: 0, - DISPATCHED: 0, - DONE: 0, - ERROR: 0 - } - - for frame in self.frames: - results[frame.status] += 1 - - return results - - def __contains__(self, frame_number): - for f in self.frames: - if f.number == frame_number: - return True - else: - return False - - def __getitem__(self, frame_number): - for f in self.frames: - if f.number == frame_number: - return f - else: - return None - - def serialize(self, frames = None): - min_frame = min((f.number for f in frames)) if frames else -1 - max_frame = max((f.number for f in frames)) if frames else -1 - return { - "id": self.id, - "type": self.type, - "name": self.name, - "category": self.category, - "status": self.status, - "files": [f.serialize() for f in self.files if f.start == -1 or not frames or (f.start <= max_frame and f.end >= min_frame)], - "frames": [f.serialize() for f in self.frames if not frames or f in frames], - "chunks": self.chunks, - "priority": self.priority, - "usage": self.usage, - "blacklist": self.blacklist, - "last_dispatched": self.last_dispatched - } - - @staticmethod - def materialize(data): - if not data: - return None - - job = RenderJob() - job.id = data["id"] - job.type = data["type"] - job.name = data["name"] - job.category = data["category"] - job.status = data["status"] - job.files = [RenderFile.materialize(f) for f in data["files"]] - job.frames = [RenderFrame.materialize(f) for f in data["frames"]] - job.chunks = data["chunks"] - job.priority = data["priority"] - job.usage = data["usage"] - job.blacklist = data["blacklist"] - job.last_dispatched = data["last_dispatched"] - - return job + def __init__(self, job_info = None): + self.id = "" + self.type = JOB_BLENDER + self.name = "" + self.category = "None" + self.status = JOB_WAITING + self.files = [] + self.chunks = 0 + self.priority = 0 + self.blacklist = [] + + self.usage = 0.0 + self.last_dispatched = 0.0 + self.frames = [] + + if job_info: + self.type = job_info.type + self.name = job_info.name + self.category = job_info.category + self.status = job_info.status + self.files = job_info.files + self.chunks = job_info.chunks + self.priority = job_info.priority + self.blacklist = job_info.blacklist + + def addFile(self, file_path, start=-1, end=-1): + self.files.append(RenderFile(file_path, len(self.files), start, end)) + + def addFrame(self, frame_number, command = ""): + frame = RenderFrame(frame_number, command) + self.frames.append(frame) + return frame + + def __len__(self): + return len(self.frames) + + def countFrames(self, status=QUEUED): + total = 0 + for f in self.frames: + if f.status == status: + total += 1 + + return total + + def countSlaves(self): + return len(set((frame.slave for frame in self.frames if frame.status == DISPATCHED))) + + def statusText(self): + return JOB_STATUS_TEXT[self.status] + + def framesStatus(self): + results = { + QUEUED: 0, + DISPATCHED: 0, + DONE: 0, + ERROR: 0 + } + + for frame in self.frames: + results[frame.status] += 1 + + return results + + def __contains__(self, frame_number): + for f in self.frames: + if f.number == frame_number: + return True + else: + return False + + def __getitem__(self, frame_number): + for f in self.frames: + if f.number == frame_number: + return f + else: + return None + + def serialize(self, frames = None): + min_frame = min((f.number for f in frames)) if frames else -1 + max_frame = max((f.number for f in frames)) if frames else -1 + return { + "id": self.id, + "type": self.type, + "name": self.name, + "category": self.category, + "status": self.status, + "files": [f.serialize() for f in self.files if f.start == -1 or not frames or (f.start <= max_frame and f.end >= min_frame)], + "frames": [f.serialize() for f in self.frames if not frames or f in frames], + "chunks": self.chunks, + "priority": self.priority, + "usage": self.usage, + "blacklist": self.blacklist, + "last_dispatched": self.last_dispatched + } + + @staticmethod + def materialize(data): + if not data: + return None + + job = RenderJob() + job.id = data["id"] + job.type = data["type"] + job.name = data["name"] + job.category = data["category"] + job.status = data["status"] + job.files = [RenderFile.materialize(f) for f in data["files"]] + job.frames = [RenderFrame.materialize(f) for f in data["frames"]] + job.chunks = data["chunks"] + job.priority = data["priority"] + job.usage = data["usage"] + job.blacklist = data["blacklist"] + job.last_dispatched = data["last_dispatched"] + + return job class RenderFrame: - def __init__(self, number = 0, command = ""): - self.number = number - self.time = 0 - self.status = QUEUED - self.slave = None - self.command = command - - def statusText(self): - return FRAME_STATUS_TEXT[self.status] - - def serialize(self): - return { - "number": self.number, - "time": self.time, - "status": self.status, - "slave": None if not self.slave else self.slave.serialize(), - "command": self.command - } - - @staticmethod - def materialize(data): - if not data: - return None - - frame = RenderFrame() - frame.number = data["number"] - frame.time = data["time"] - frame.status = data["status"] - frame.slave = RenderSlave.materialize(data["slave"]) - frame.command = data["command"] - - return frame + def __init__(self, number = 0, command = ""): + self.number = number + self.time = 0 + self.status = QUEUED + self.slave = None + self.command = command + + def statusText(self): + return FRAME_STATUS_TEXT[self.status] + + def serialize(self): + return { + "number": self.number, + "time": self.time, + "status": self.status, + "slave": None if not self.slave else self.slave.serialize(), + "command": self.command + } + + @staticmethod + def materialize(data): + if not data: + return None + + frame = RenderFrame() + frame.number = data["number"] + frame.time = data["time"] + frame.status = data["status"] + frame.slave = RenderSlave.materialize(data["slave"]) + frame.command = data["command"] + + return frame diff --git a/release/scripts/io/netrender/operators.py b/release/scripts/io/netrender/operators.py index 6fc5d30dc4e..8052b43e5b6 100644 --- a/release/scripts/io/netrender/operators.py +++ b/release/scripts/io/netrender/operators.py @@ -4,12 +4,12 @@ # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. -# +# # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. -# +# # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software Foundation, # Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. @@ -28,414 +28,414 @@ import netrender.model @rnaType class RENDER_OT_netslave_bake(bpy.types.Operator): - '''NEED DESCRIPTION''' - bl_idname = "render.netslavebake" - bl_label = "Bake all in file" - - def poll(self, context): - return True - - def execute(self, context): - scene = context.scene - netsettings = scene.network_render - - filename = bpy.data.filename - path, name = os.path.split(filename) - root, ext = os.path.splitext(name) - default_path = path + os.sep + "blendcache_" + root + os.sep # need an API call for that - relative_path = os.sep + os.sep + "blendcache_" + root + os.sep - - # Force all point cache next to the blend file - for object in bpy.data.objects: - for modifier in object.modifiers: - if modifier.type == 'FLUID_SIMULATION' and modifier.settings.type == "DOMAIN": - modifier.settings.path = relative_path - bpy.ops.fluid.bake({"active_object": object, "scene": scene}) - elif modifier.type == "CLOTH": - modifier.point_cache.step = 1 - modifier.point_cache.disk_cache = True - modifier.point_cache.external = False - elif modifier.type == "SOFT_BODY": - modifier.point_cache.step = 1 - modifier.point_cache.disk_cache = True - modifier.point_cache.external = False - elif modifier.type == "SMOKE" and modifier.smoke_type == "TYPE_DOMAIN": - modifier.domain_settings.point_cache_low.step = 1 - modifier.domain_settings.point_cache_low.disk_cache = True - modifier.domain_settings.point_cache_low.external = False - modifier.domain_settings.point_cache_high.step = 1 - modifier.domain_settings.point_cache_high.disk_cache = True - modifier.domain_settings.point_cache_high.external = False - - # particles modifier are stupid and don't contain data - # we have to go through the object property - for psys in object.particle_systems: - psys.point_cache.step = 1 - psys.point_cache.disk_cache = True - psys.point_cache.external = False - psys.point_cache.filepath = relative_path - - bpy.ops.ptcache.bake_all() - - #bpy.ops.wm.save_mainfile(path = path + os.sep + root + "_baked.blend") - - return {'FINISHED'} - - def invoke(self, context, event): - return self.execute(context) - + '''NEED DESCRIPTION''' + bl_idname = "render.netslavebake" + bl_label = "Bake all in file" + + def poll(self, context): + return True + + def execute(self, context): + scene = context.scene + netsettings = scene.network_render + + filename = bpy.data.filename + path, name = os.path.split(filename) + root, ext = os.path.splitext(name) + default_path = path + os.sep + "blendcache_" + root + os.sep # need an API call for that + relative_path = os.sep + os.sep + "blendcache_" + root + os.sep + + # Force all point cache next to the blend file + for object in bpy.data.objects: + for modifier in object.modifiers: + if modifier.type == 'FLUID_SIMULATION' and modifier.settings.type == "DOMAIN": + modifier.settings.path = relative_path + bpy.ops.fluid.bake({"active_object": object, "scene": scene}) + elif modifier.type == "CLOTH": + modifier.point_cache.step = 1 + modifier.point_cache.disk_cache = True + modifier.point_cache.external = False + elif modifier.type == "SOFT_BODY": + modifier.point_cache.step = 1 + modifier.point_cache.disk_cache = True + modifier.point_cache.external = False + elif modifier.type == "SMOKE" and modifier.smoke_type == "TYPE_DOMAIN": + modifier.domain_settings.point_cache_low.step = 1 + modifier.domain_settings.point_cache_low.disk_cache = True + modifier.domain_settings.point_cache_low.external = False + modifier.domain_settings.point_cache_high.step = 1 + modifier.domain_settings.point_cache_high.disk_cache = True + modifier.domain_settings.point_cache_high.external = False + + # particles modifier are stupid and don't contain data + # we have to go through the object property + for psys in object.particle_systems: + psys.point_cache.step = 1 + psys.point_cache.disk_cache = True + psys.point_cache.external = False + psys.point_cache.filepath = relative_path + + bpy.ops.ptcache.bake_all() + + #bpy.ops.wm.save_mainfile(path = path + os.sep + root + "_baked.blend") + + return {'FINISHED'} + + def invoke(self, context, event): + return self.execute(context) + @rnaType class RENDER_OT_netclientanim(bpy.types.Operator): - '''Start rendering an animation on network''' - bl_idname = "render.netclientanim" - bl_label = "Animation on network" - - def poll(self, context): - return True - - def execute(self, context): - scene = context.scene - netsettings = scene.network_render - - conn = clientConnection(netsettings.server_address, netsettings.server_port, self.report) - - if conn: - # Sending file - scene.network_render.job_id = client.clientSendJob(conn, scene, True) - conn.close() - - bpy.ops.screen.render('INVOKE_AREA', animation=True) - - return {'FINISHED'} - - def invoke(self, context, event): - return self.execute(context) + '''Start rendering an animation on network''' + bl_idname = "render.netclientanim" + bl_label = "Animation on network" + + def poll(self, context): + return True + + def execute(self, context): + scene = context.scene + netsettings = scene.network_render + + conn = clientConnection(netsettings.server_address, netsettings.server_port, self.report) + + if conn: + # Sending file + scene.network_render.job_id = client.clientSendJob(conn, scene, True) + conn.close() + + bpy.ops.screen.render('INVOKE_AREA', animation=True) + + return {'FINISHED'} + + def invoke(self, context, event): + return self.execute(context) @rnaType class RENDER_OT_netclientsend(bpy.types.Operator): - '''Send Render Job to the Network''' - bl_idname = "render.netclientsend" - bl_label = "Send job" - - def poll(self, context): - return True - - def execute(self, context): - scene = context.scene - netsettings = scene.network_render - - try: - conn = clientConnection(netsettings.server_address, netsettings.server_port, self.report) - - if conn: - # Sending file - scene.network_render.job_id = client.clientSendJob(conn, scene, True) - conn.close() - self.report('INFO', "Job sent to master") - except Exception as err: - self.report('ERROR', str(err)) - - - return {'FINISHED'} - - def invoke(self, context, event): - return self.execute(context) + '''Send Render Job to the Network''' + bl_idname = "render.netclientsend" + bl_label = "Send job" + + def poll(self, context): + return True + + def execute(self, context): + scene = context.scene + netsettings = scene.network_render + + try: + conn = clientConnection(netsettings.server_address, netsettings.server_port, self.report) + + if conn: + # Sending file + scene.network_render.job_id = client.clientSendJob(conn, scene, True) + conn.close() + self.report('INFO', "Job sent to master") + except Exception as err: + self.report('ERROR', str(err)) + + + return {'FINISHED'} + + def invoke(self, context, event): + return self.execute(context) @rnaType class RENDER_OT_netclientstatus(bpy.types.Operator): - '''Refresh the status of the current jobs''' - bl_idname = "render.netclientstatus" - bl_label = "Client Status" - - def poll(self, context): - return True - - def execute(self, context): - netsettings = context.scene.network_render - conn = clientConnection(netsettings.server_address, netsettings.server_port, self.report) - - if conn: - conn.request("GET", "/status") - - response = conn.getresponse() - print( response.status, response.reason ) - - jobs = (netrender.model.RenderJob.materialize(j) for j in eval(str(response.read(), encoding='utf8'))) - - while(len(netsettings.jobs) > 0): - netsettings.jobs.remove(0) - - netrender.jobs = [] - - for j in jobs: - netrender.jobs.append(j) - netsettings.jobs.add() - job = netsettings.jobs[-1] - - j.results = j.framesStatus() # cache frame status - - job.name = j.name - - return {'FINISHED'} - - def invoke(self, context, event): - return self.execute(context) + '''Refresh the status of the current jobs''' + bl_idname = "render.netclientstatus" + bl_label = "Client Status" + + def poll(self, context): + return True + + def execute(self, context): + netsettings = context.scene.network_render + conn = clientConnection(netsettings.server_address, netsettings.server_port, self.report) + + if conn: + conn.request("GET", "/status") + + response = conn.getresponse() + print( response.status, response.reason ) + + jobs = (netrender.model.RenderJob.materialize(j) for j in eval(str(response.read(), encoding='utf8'))) + + while(len(netsettings.jobs) > 0): + netsettings.jobs.remove(0) + + netrender.jobs = [] + + for j in jobs: + netrender.jobs.append(j) + netsettings.jobs.add() + job = netsettings.jobs[-1] + + j.results = j.framesStatus() # cache frame status + + job.name = j.name + + return {'FINISHED'} + + def invoke(self, context, event): + return self.execute(context) @rnaType class RENDER_OT_netclientblacklistslave(bpy.types.Operator): - '''Operator documentation text, will be used for the operator tooltip and python docs.''' - bl_idname = "render.netclientblacklistslave" - bl_label = "Client Blacklist Slave" - - def poll(self, context): - return True - - def execute(self, context): - netsettings = context.scene.network_render - - if netsettings.active_slave_index >= 0: - - # deal with data - slave = netrender.slaves.pop(netsettings.active_slave_index) - netrender.blacklist.append(slave) - - # deal with rna - netsettings.slaves_blacklist.add() - netsettings.slaves_blacklist[-1].name = slave.name - - netsettings.slaves.remove(netsettings.active_slave_index) - netsettings.active_slave_index = -1 - - return {'FINISHED'} - - def invoke(self, context, event): - return self.execute(context) + '''Operator documentation text, will be used for the operator tooltip and python docs.''' + bl_idname = "render.netclientblacklistslave" + bl_label = "Client Blacklist Slave" + + def poll(self, context): + return True + + def execute(self, context): + netsettings = context.scene.network_render + + if netsettings.active_slave_index >= 0: + + # deal with data + slave = netrender.slaves.pop(netsettings.active_slave_index) + netrender.blacklist.append(slave) + + # deal with rna + netsettings.slaves_blacklist.add() + netsettings.slaves_blacklist[-1].name = slave.name + + netsettings.slaves.remove(netsettings.active_slave_index) + netsettings.active_slave_index = -1 + + return {'FINISHED'} + + def invoke(self, context, event): + return self.execute(context) @rnaType class RENDER_OT_netclientwhitelistslave(bpy.types.Operator): - '''Operator documentation text, will be used for the operator tooltip and python docs.''' - bl_idname = "render.netclientwhitelistslave" - bl_label = "Client Whitelist Slave" - - def poll(self, context): - return True - - def execute(self, context): - netsettings = context.scene.network_render - - if netsettings.active_blacklisted_slave_index >= 0: - - # deal with data - slave = netrender.blacklist.pop(netsettings.active_blacklisted_slave_index) - netrender.slaves.append(slave) - - # deal with rna - netsettings.slaves.add() - netsettings.slaves[-1].name = slave.name - - netsettings.slaves_blacklist.remove(netsettings.active_blacklisted_slave_index) - netsettings.active_blacklisted_slave_index = -1 - - return {'FINISHED'} - - def invoke(self, context, event): - return self.execute(context) + '''Operator documentation text, will be used for the operator tooltip and python docs.''' + bl_idname = "render.netclientwhitelistslave" + bl_label = "Client Whitelist Slave" + + def poll(self, context): + return True + + def execute(self, context): + netsettings = context.scene.network_render + + if netsettings.active_blacklisted_slave_index >= 0: + + # deal with data + slave = netrender.blacklist.pop(netsettings.active_blacklisted_slave_index) + netrender.slaves.append(slave) + + # deal with rna + netsettings.slaves.add() + netsettings.slaves[-1].name = slave.name + + netsettings.slaves_blacklist.remove(netsettings.active_blacklisted_slave_index) + netsettings.active_blacklisted_slave_index = -1 + + return {'FINISHED'} + + def invoke(self, context, event): + return self.execute(context) @rnaType class RENDER_OT_netclientslaves(bpy.types.Operator): - '''Refresh status about available Render slaves''' - bl_idname = "render.netclientslaves" - bl_label = "Client Slaves" - - def poll(self, context): - return True - - def execute(self, context): - netsettings = context.scene.network_render - conn = clientConnection(netsettings.server_address, netsettings.server_port, self.report) - - if conn: - conn.request("GET", "/slaves") - - response = conn.getresponse() - print( response.status, response.reason ) - - slaves = (netrender.model.RenderSlave.materialize(s) for s in eval(str(response.read(), encoding='utf8'))) - - while(len(netsettings.slaves) > 0): - netsettings.slaves.remove(0) - - netrender.slaves = [] - - for s in slaves: - for i in range(len(netrender.blacklist)): - slave = netrender.blacklist[i] - if slave.id == s.id: - netrender.blacklist[i] = s - netsettings.slaves_blacklist[i].name = s.name - break - else: - netrender.slaves.append(s) - - netsettings.slaves.add() - slave = netsettings.slaves[-1] - slave.name = s.name - - return {'FINISHED'} - - def invoke(self, context, event): - return self.execute(context) + '''Refresh status about available Render slaves''' + bl_idname = "render.netclientslaves" + bl_label = "Client Slaves" + + def poll(self, context): + return True + + def execute(self, context): + netsettings = context.scene.network_render + conn = clientConnection(netsettings.server_address, netsettings.server_port, self.report) + + if conn: + conn.request("GET", "/slaves") + + response = conn.getresponse() + print( response.status, response.reason ) + + slaves = (netrender.model.RenderSlave.materialize(s) for s in eval(str(response.read(), encoding='utf8'))) + + while(len(netsettings.slaves) > 0): + netsettings.slaves.remove(0) + + netrender.slaves = [] + + for s in slaves: + for i in range(len(netrender.blacklist)): + slave = netrender.blacklist[i] + if slave.id == s.id: + netrender.blacklist[i] = s + netsettings.slaves_blacklist[i].name = s.name + break + else: + netrender.slaves.append(s) + + netsettings.slaves.add() + slave = netsettings.slaves[-1] + slave.name = s.name + + return {'FINISHED'} + + def invoke(self, context, event): + return self.execute(context) @rnaType class RENDER_OT_netclientcancel(bpy.types.Operator): - '''Cancel the selected network rendering job.''' - bl_idname = "render.netclientcancel" - bl_label = "Client Cancel" - - def poll(self, context): - netsettings = context.scene.network_render - return netsettings.active_job_index >= 0 and len(netsettings.jobs) > 0 - - def execute(self, context): - netsettings = context.scene.network_render - conn = clientConnection(netsettings.server_address, netsettings.server_port, self.report) - - if conn: - job = netrender.jobs[netsettings.active_job_index] - - conn.request("POST", cancelURL(job.id)) - - response = conn.getresponse() - print( response.status, response.reason ) - - netsettings.jobs.remove(netsettings.active_job_index) - - return {'FINISHED'} - - def invoke(self, context, event): - return self.execute(context) - + '''Cancel the selected network rendering job.''' + bl_idname = "render.netclientcancel" + bl_label = "Client Cancel" + + def poll(self, context): + netsettings = context.scene.network_render + return netsettings.active_job_index >= 0 and len(netsettings.jobs) > 0 + + def execute(self, context): + netsettings = context.scene.network_render + conn = clientConnection(netsettings.server_address, netsettings.server_port, self.report) + + if conn: + job = netrender.jobs[netsettings.active_job_index] + + conn.request("POST", cancelURL(job.id)) + + response = conn.getresponse() + print( response.status, response.reason ) + + netsettings.jobs.remove(netsettings.active_job_index) + + return {'FINISHED'} + + def invoke(self, context, event): + return self.execute(context) + @rnaType class RENDER_OT_netclientcancelall(bpy.types.Operator): - '''Cancel all running network rendering jobs.''' - bl_idname = "render.netclientcancelall" - bl_label = "Client Cancel All" - - def poll(self, context): - return True - - def execute(self, context): - netsettings = context.scene.network_render - conn = clientConnection(netsettings.server_address, netsettings.server_port, self.report) - - if conn: - conn.request("POST", "/clear") - - response = conn.getresponse() - print( response.status, response.reason ) - - while(len(netsettings.jobs) > 0): - netsettings.jobs.remove(0) - - return {'FINISHED'} - - def invoke(self, context, event): - return self.execute(context) + '''Cancel all running network rendering jobs.''' + bl_idname = "render.netclientcancelall" + bl_label = "Client Cancel All" + + def poll(self, context): + return True + + def execute(self, context): + netsettings = context.scene.network_render + conn = clientConnection(netsettings.server_address, netsettings.server_port, self.report) + + if conn: + conn.request("POST", "/clear") + + response = conn.getresponse() + print( response.status, response.reason ) + + while(len(netsettings.jobs) > 0): + netsettings.jobs.remove(0) + + return {'FINISHED'} + + def invoke(self, context, event): + return self.execute(context) @rnaType class netclientdownload(bpy.types.Operator): - '''Download render results from the network''' - bl_idname = "render.netclientdownload" - bl_label = "Client Download" - - def poll(self, context): - netsettings = context.scene.network_render - return netsettings.active_job_index >= 0 and len(netsettings.jobs) > 0 - - def execute(self, context): - netsettings = context.scene.network_render - rd = context.scene.render_data - - conn = clientConnection(netsettings.server_address, netsettings.server_port, self.report) - - if conn: - job = netrender.jobs[netsettings.active_job_index] - - for frame in job.frames: - client.requestResult(conn, job.id, frame.number) - response = conn.getresponse() - - if response.status != http.client.OK: - print("missing", frame.number) - continue - - print("got back", frame.number) - - f = open(netsettings.path + "%06d" % frame.number + ".exr", "wb") - buf = response.read(1024) - - while buf: - f.write(buf) - buf = response.read(1024) - - f.close() - - conn.close() - - return {'FINISHED'} - - def invoke(self, context, event): - return self.execute(context) + '''Download render results from the network''' + bl_idname = "render.netclientdownload" + bl_label = "Client Download" + + def poll(self, context): + netsettings = context.scene.network_render + return netsettings.active_job_index >= 0 and len(netsettings.jobs) > 0 + + def execute(self, context): + netsettings = context.scene.network_render + rd = context.scene.render_data + + conn = clientConnection(netsettings.server_address, netsettings.server_port, self.report) + + if conn: + job = netrender.jobs[netsettings.active_job_index] + + for frame in job.frames: + client.requestResult(conn, job.id, frame.number) + response = conn.getresponse() + + if response.status != http.client.OK: + print("missing", frame.number) + continue + + print("got back", frame.number) + + f = open(netsettings.path + "%06d" % frame.number + ".exr", "wb") + buf = response.read(1024) + + while buf: + f.write(buf) + buf = response.read(1024) + + f.close() + + conn.close() + + return {'FINISHED'} + + def invoke(self, context, event): + return self.execute(context) @rnaType class netclientscan(bpy.types.Operator): - __slots__ = [] - '''Operator documentation text, will be used for the operator tooltip and python docs.''' - bl_idname = "render.netclientscan" - bl_label = "Client Scan" - - def poll(self, context): - return True - - def execute(self, context): - address, port = clientScan(self.report) - - if address: - scene = context.scene - netsettings = scene.network_render - netsettings.server_address = address - netsettings.server_port = port - - return {'FINISHED'} - - def invoke(self, context, event): - print(dir(self)) - return self.execute(context) + __slots__ = [] + '''Operator documentation text, will be used for the operator tooltip and python docs.''' + bl_idname = "render.netclientscan" + bl_label = "Client Scan" + + def poll(self, context): + return True + + def execute(self, context): + address, port = clientScan(self.report) + + if address: + scene = context.scene + netsettings = scene.network_render + netsettings.server_address = address + netsettings.server_port = port + + return {'FINISHED'} + + def invoke(self, context, event): + print(dir(self)) + return self.execute(context) @rnaType class netclientweb(bpy.types.Operator): - '''Open new window with information about running rendering jobs''' - bl_idname = "render.netclientweb" - bl_label = "Open Master Monitor" - - def poll(self, context): - netsettings = context.scene.network_render - return netsettings.server_address != "[default]" - - def execute(self, context): - netsettings = context.scene.network_render - - - # open connection to make sure server exists - conn = clientConnection(netsettings.server_address, netsettings.server_port, self.report) - - if conn: - conn.close() - - webbrowser.open("http://%s:%i" % (netsettings.server_address, netsettings.server_port)) - - return {'FINISHED'} - - def invoke(self, context, event): - return self.execute(context) + '''Open new window with information about running rendering jobs''' + bl_idname = "render.netclientweb" + bl_label = "Open Master Monitor" + + def poll(self, context): + netsettings = context.scene.network_render + return netsettings.server_address != "[default]" + + def execute(self, context): + netsettings = context.scene.network_render + + + # open connection to make sure server exists + conn = clientConnection(netsettings.server_address, netsettings.server_port, self.report) + + if conn: + conn.close() + + webbrowser.open("http://%s:%i" % (netsettings.server_address, netsettings.server_port)) + + return {'FINISHED'} + + def invoke(self, context, event): + return self.execute(context) diff --git a/release/scripts/io/netrender/slave.py b/release/scripts/io/netrender/slave.py index 10f73dbe258..e7cac28ace3 100644 --- a/release/scripts/io/netrender/slave.py +++ b/release/scripts/io/netrender/slave.py @@ -4,12 +4,12 @@ # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. -# +# # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. -# +# # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software Foundation, # Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. @@ -30,218 +30,218 @@ MAX_TIMEOUT = 10 INCREMENT_TIMEOUT = 1 if platform.system() == 'Windows' and platform.version() >= '5': # Error mode is only available on Win2k or higher, that's version 5 - import ctypes - def SetErrorMode(): - val = ctypes.windll.kernel32.SetErrorMode(0x0002) - ctypes.windll.kernel32.SetErrorMode(val | 0x0002) - return val - - def RestoreErrorMode(val): - ctypes.windll.kernel32.SetErrorMode(val) + import ctypes + def SetErrorMode(): + val = ctypes.windll.kernel32.SetErrorMode(0x0002) + ctypes.windll.kernel32.SetErrorMode(val | 0x0002) + return val + + def RestoreErrorMode(val): + ctypes.windll.kernel32.SetErrorMode(val) else: - def SetErrorMode(): - return 0 - - def RestoreErrorMode(val): - pass + def SetErrorMode(): + return 0 + + def RestoreErrorMode(val): + pass def slave_Info(): - sysname, nodename, release, version, machine, processor = platform.uname() - slave = netrender.model.RenderSlave() - slave.name = nodename - slave.stats = sysname + " " + release + " " + machine + " " + processor - return slave + sysname, nodename, release, version, machine, processor = platform.uname() + slave = netrender.model.RenderSlave() + slave.name = nodename + slave.stats = sysname + " " + release + " " + machine + " " + processor + return slave def testCancel(conn, job_id, frame_number): - conn.request("HEAD", "/status", headers={"job-id":job_id, "job-frame": str(frame_number)}) + conn.request("HEAD", "/status", headers={"job-id":job_id, "job-frame": str(frame_number)}) - # cancelled if job isn't found anymore - if conn.getresponse().status == http.client.NO_CONTENT: - return True - else: - return False + # cancelled if job isn't found anymore + if conn.getresponse().status == http.client.NO_CONTENT: + return True + else: + return False def testFile(conn, job_id, slave_id, file_index, JOB_PREFIX, file_path, main_path = None): - job_full_path = prefixPath(JOB_PREFIX, file_path, main_path) - - if not os.path.exists(job_full_path): - temp_path = JOB_PREFIX + "slave.temp.blend" - conn.request("GET", fileURL(job_id, file_index), headers={"slave-id":slave_id}) - response = conn.getresponse() - - if response.status != http.client.OK: - return None # file for job not returned by server, need to return an error code to server - - f = open(temp_path, "wb") - buf = response.read(1024) - - while buf: - f.write(buf) - buf = response.read(1024) - - f.close() - - os.renames(temp_path, job_full_path) - - return job_full_path + job_full_path = prefixPath(JOB_PREFIX, file_path, main_path) + + if not os.path.exists(job_full_path): + temp_path = JOB_PREFIX + "slave.temp.blend" + conn.request("GET", fileURL(job_id, file_index), headers={"slave-id":slave_id}) + response = conn.getresponse() + + if response.status != http.client.OK: + return None # file for job not returned by server, need to return an error code to server + + f = open(temp_path, "wb") + buf = response.read(1024) + + while buf: + f.write(buf) + buf = response.read(1024) + + f.close() + + os.renames(temp_path, job_full_path) + + return job_full_path def render_slave(engine, netsettings): - timeout = 1 - - engine.update_stats("", "Network render node initiation") - - conn = clientConnection(netsettings.server_address, netsettings.server_port) - - if conn: - conn.request("POST", "/slave", repr(slave_Info().serialize())) - response = conn.getresponse() - - slave_id = response.getheader("slave-id") - - NODE_PREFIX = netsettings.path + "slave_" + slave_id + os.sep - if not os.path.exists(NODE_PREFIX): - os.mkdir(NODE_PREFIX) - - while not engine.test_break(): - - conn.request("GET", "/job", headers={"slave-id":slave_id}) - response = conn.getresponse() - - if response.status == http.client.OK: - timeout = 1 # reset timeout on new job - - job = netrender.model.RenderJob.materialize(eval(str(response.read(), encoding='utf8'))) - - JOB_PREFIX = NODE_PREFIX + "job_" + job.id + os.sep - if not os.path.exists(JOB_PREFIX): - os.mkdir(JOB_PREFIX) - - - if job.type == netrender.model.JOB_BLENDER: - job_path = job.files[0].filepath # path of main file - main_path, main_file = os.path.split(job_path) - - job_full_path = testFile(conn, job.id, slave_id, 0, JOB_PREFIX, job_path) - print("Fullpath", job_full_path) - print("File:", main_file, "and %i other files" % (len(job.files) - 1,)) - engine.update_stats("", "Render File "+ main_file+ " for job "+ job.id) - - for rfile in job.files[1:]: - print("\t", rfile.filepath) - testFile(conn, job.id, slave_id, rfile.index, JOB_PREFIX, rfile.filepath, main_path) - - # announce log to master - logfile = netrender.model.LogFile(job.id, slave_id, [frame.number for frame in job.frames]) - conn.request("POST", "/log", bytes(repr(logfile.serialize()), encoding='utf8')) - response = conn.getresponse() - - - first_frame = job.frames[0].number - - # start render - start_t = time.time() - - if job.type == netrender.model.JOB_BLENDER: - frame_args = [] - - for frame in job.frames: - print("frame", frame.number) - frame_args += ["-f", str(frame.number)] - - val = SetErrorMode() - process = subprocess.Popen([BLENDER_PATH, "-b", "-noaudio", job_full_path, "-o", JOB_PREFIX + "######", "-E", "BLENDER_RENDER", "-F", "MULTILAYER"] + frame_args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - RestoreErrorMode(val) - elif job.type == netrender.model.JOB_PROCESS: - command = job.frames[0].command - val = SetErrorMode() - process = subprocess.Popen(command.split(" "), stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - RestoreErrorMode(val) - - headers = {"slave-id":slave_id} - - cancelled = False - stdout = bytes() - run_t = time.time() - while process.poll() == None and not cancelled: - stdout += process.stdout.read(32) - current_t = time.time() - cancelled = engine.test_break() - if current_t - run_t > CANCEL_POLL_SPEED: - - # update logs if needed - if stdout: - # (only need to update on one frame, they are linked - conn.request("PUT", logURL(job.id, first_frame), stdout, headers=headers) - response = conn.getresponse() - - stdout = bytes() - - run_t = current_t - if testCancel(conn, job.id, first_frame): - cancelled = True - - # read leftovers if needed - stdout += process.stdout.read() - - if cancelled: - # kill process if needed - if process.poll() == None: - process.terminate() - continue # to next frame - - total_t = time.time() - start_t - - avg_t = total_t / len(job.frames) - - status = process.returncode - - print("status", status) - - # flush the rest of the logs - if stdout: - # (only need to update on one frame, they are linked - conn.request("PUT", logURL(job.id, first_frame), stdout, headers=headers) - if conn.getresponse().status == http.client.NO_CONTENT: - continue - - headers = {"job-id":job.id, "slave-id":slave_id, "job-time":str(avg_t)} - - - if status == 0: # non zero status is error - headers["job-result"] = str(DONE) - for frame in job.frames: - headers["job-frame"] = str(frame.number) - - if job.type == netrender.model.JOB_BLENDER: - # send image back to server - f = open(JOB_PREFIX + "%06d" % frame.number + ".exr", 'rb') - conn.request("PUT", "/render", f, headers=headers) - f.close() - if conn.getresponse().status == http.client.NO_CONTENT: - continue - elif job.type == netrender.model.JOB_PROCESS: - conn.request("PUT", "/render", headers=headers) - if conn.getresponse().status == http.client.NO_CONTENT: - continue - else: - headers["job-result"] = str(ERROR) - for frame in job.frames: - headers["job-frame"] = str(frame.number) - # send error result back to server - conn.request("PUT", "/render", headers=headers) - if conn.getresponse().status == http.client.NO_CONTENT: - continue - else: - if timeout < MAX_TIMEOUT: - timeout += INCREMENT_TIMEOUT - - for i in range(timeout): - time.sleep(1) - if engine.test_break(): - conn.close() - return - - conn.close() + timeout = 1 + + engine.update_stats("", "Network render node initiation") + + conn = clientConnection(netsettings.server_address, netsettings.server_port) + + if conn: + conn.request("POST", "/slave", repr(slave_Info().serialize())) + response = conn.getresponse() + + slave_id = response.getheader("slave-id") + + NODE_PREFIX = netsettings.path + "slave_" + slave_id + os.sep + if not os.path.exists(NODE_PREFIX): + os.mkdir(NODE_PREFIX) + + while not engine.test_break(): + + conn.request("GET", "/job", headers={"slave-id":slave_id}) + response = conn.getresponse() + + if response.status == http.client.OK: + timeout = 1 # reset timeout on new job + + job = netrender.model.RenderJob.materialize(eval(str(response.read(), encoding='utf8'))) + + JOB_PREFIX = NODE_PREFIX + "job_" + job.id + os.sep + if not os.path.exists(JOB_PREFIX): + os.mkdir(JOB_PREFIX) + + + if job.type == netrender.model.JOB_BLENDER: + job_path = job.files[0].filepath # path of main file + main_path, main_file = os.path.split(job_path) + + job_full_path = testFile(conn, job.id, slave_id, 0, JOB_PREFIX, job_path) + print("Fullpath", job_full_path) + print("File:", main_file, "and %i other files" % (len(job.files) - 1,)) + engine.update_stats("", "Render File "+ main_file+ " for job "+ job.id) + + for rfile in job.files[1:]: + print("\t", rfile.filepath) + testFile(conn, job.id, slave_id, rfile.index, JOB_PREFIX, rfile.filepath, main_path) + + # announce log to master + logfile = netrender.model.LogFile(job.id, slave_id, [frame.number for frame in job.frames]) + conn.request("POST", "/log", bytes(repr(logfile.serialize()), encoding='utf8')) + response = conn.getresponse() + + + first_frame = job.frames[0].number + + # start render + start_t = time.time() + + if job.type == netrender.model.JOB_BLENDER: + frame_args = [] + + for frame in job.frames: + print("frame", frame.number) + frame_args += ["-f", str(frame.number)] + + val = SetErrorMode() + process = subprocess.Popen([BLENDER_PATH, "-b", "-noaudio", job_full_path, "-o", JOB_PREFIX + "######", "-E", "BLENDER_RENDER", "-F", "MULTILAYER"] + frame_args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + RestoreErrorMode(val) + elif job.type == netrender.model.JOB_PROCESS: + command = job.frames[0].command + val = SetErrorMode() + process = subprocess.Popen(command.split(" "), stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + RestoreErrorMode(val) + + headers = {"slave-id":slave_id} + + cancelled = False + stdout = bytes() + run_t = time.time() + while process.poll() == None and not cancelled: + stdout += process.stdout.read(32) + current_t = time.time() + cancelled = engine.test_break() + if current_t - run_t > CANCEL_POLL_SPEED: + + # update logs if needed + if stdout: + # (only need to update on one frame, they are linked + conn.request("PUT", logURL(job.id, first_frame), stdout, headers=headers) + response = conn.getresponse() + + stdout = bytes() + + run_t = current_t + if testCancel(conn, job.id, first_frame): + cancelled = True + + # read leftovers if needed + stdout += process.stdout.read() + + if cancelled: + # kill process if needed + if process.poll() == None: + process.terminate() + continue # to next frame + + total_t = time.time() - start_t + + avg_t = total_t / len(job.frames) + + status = process.returncode + + print("status", status) + + # flush the rest of the logs + if stdout: + # (only need to update on one frame, they are linked + conn.request("PUT", logURL(job.id, first_frame), stdout, headers=headers) + if conn.getresponse().status == http.client.NO_CONTENT: + continue + + headers = {"job-id":job.id, "slave-id":slave_id, "job-time":str(avg_t)} + + + if status == 0: # non zero status is error + headers["job-result"] = str(DONE) + for frame in job.frames: + headers["job-frame"] = str(frame.number) + + if job.type == netrender.model.JOB_BLENDER: + # send image back to server + f = open(JOB_PREFIX + "%06d" % frame.number + ".exr", 'rb') + conn.request("PUT", "/render", f, headers=headers) + f.close() + if conn.getresponse().status == http.client.NO_CONTENT: + continue + elif job.type == netrender.model.JOB_PROCESS: + conn.request("PUT", "/render", headers=headers) + if conn.getresponse().status == http.client.NO_CONTENT: + continue + else: + headers["job-result"] = str(ERROR) + for frame in job.frames: + headers["job-frame"] = str(frame.number) + # send error result back to server + conn.request("PUT", "/render", headers=headers) + if conn.getresponse().status == http.client.NO_CONTENT: + continue + else: + if timeout < MAX_TIMEOUT: + timeout += INCREMENT_TIMEOUT + + for i in range(timeout): + time.sleep(1) + if engine.test_break(): + conn.close() + return + + conn.close() if __name__ == "__main__": - pass + pass diff --git a/release/scripts/io/netrender/ui.py b/release/scripts/io/netrender/ui.py index 40dd5d11b19..86eb4bf5c30 100644 --- a/release/scripts/io/netrender/ui.py +++ b/release/scripts/io/netrender/ui.py @@ -4,12 +4,12 @@ # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. -# +# # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. -# +# # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software Foundation, # Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. @@ -37,319 +37,319 @@ DONE = 2 ERROR = 3 class RenderButtonsPanel(bpy.types.Panel): - bl_space_type = "PROPERTIES" - bl_region_type = "WINDOW" - bl_context = "render" - # COMPAT_ENGINES must be defined in each subclass, external engines can add themselves here - - def poll(self, context): - rd = context.scene.render_data - return (rd.use_game_engine==False) and (rd.engine in self.COMPAT_ENGINES) + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "render" + # COMPAT_ENGINES must be defined in each subclass, external engines can add themselves here + + def poll(self, context): + rd = context.scene.render_data + return (rd.use_game_engine==False) and (rd.engine in self.COMPAT_ENGINES) # Setting panel, use in the scene for now. @rnaType class RENDER_PT_network_settings(RenderButtonsPanel): - bl_label = "Network Settings" - COMPAT_ENGINES = {'NET_RENDER'} - - def draw(self, context): - layout = self.layout - - scene = context.scene - rd = scene.render_data - - layout.active = True - - split = layout.split() - - col = split.column() - - - if scene.network_render.mode in ("RENDER_MASTER", "RENDER_SLAVE"): - col.operator("screen.render", text="Start", icon='PLAY').animation = True - - col.prop(scene.network_render, "mode") - col.prop(scene.network_render, "path") - col.prop(scene.network_render, "server_address") - col.prop(scene.network_render, "server_port") - - if scene.network_render.mode == "RENDER_MASTER": - col.prop(scene.network_render, "server_broadcast") - else: - col.operator("render.netclientscan", icon='FILE_REFRESH', text="") - - col.operator("render.netclientweb", icon='QUESTION') + bl_label = "Network Settings" + COMPAT_ENGINES = {'NET_RENDER'} + + def draw(self, context): + layout = self.layout + + scene = context.scene + rd = scene.render_data + + layout.active = True + + split = layout.split() + + col = split.column() + + + if scene.network_render.mode in ("RENDER_MASTER", "RENDER_SLAVE"): + col.operator("screen.render", text="Start", icon='PLAY').animation = True + + col.prop(scene.network_render, "mode") + col.prop(scene.network_render, "path") + col.prop(scene.network_render, "server_address") + col.prop(scene.network_render, "server_port") + + if scene.network_render.mode == "RENDER_MASTER": + col.prop(scene.network_render, "server_broadcast") + else: + col.operator("render.netclientscan", icon='FILE_REFRESH', text="") + + col.operator("render.netclientweb", icon='QUESTION') @rnaType class RENDER_PT_network_job(RenderButtonsPanel): - bl_label = "Job Settings" - COMPAT_ENGINES = {'NET_RENDER'} - - def poll(self, context): - scene = context.scene - return (super().poll(context) - and scene.network_render.mode == "RENDER_CLIENT") - - def draw(self, context): - layout = self.layout - - scene = context.scene - rd = scene.render_data - - layout.active = True - - split = layout.split() - - col = split.column() - if scene.network_render.server_address != "[default]": - col.operator("render.netclientanim", icon='RENDER_ANIMATION') - col.operator("render.netclientsend", icon='FILE_BLEND') - if scene.network_render.job_id: - col.operator("screen.render", text="Get Results", icon='RENDER_ANIMATION').animation = True - col.prop(scene.network_render, "job_name") - col.prop(scene.network_render, "job_category") - row = col.row() - row.prop(scene.network_render, "priority") - row.prop(scene.network_render, "chunks") + bl_label = "Job Settings" + COMPAT_ENGINES = {'NET_RENDER'} + + def poll(self, context): + scene = context.scene + return (super().poll(context) + and scene.network_render.mode == "RENDER_CLIENT") + + def draw(self, context): + layout = self.layout + + scene = context.scene + rd = scene.render_data + + layout.active = True + + split = layout.split() + + col = split.column() + if scene.network_render.server_address != "[default]": + col.operator("render.netclientanim", icon='RENDER_ANIMATION') + col.operator("render.netclientsend", icon='FILE_BLEND') + if scene.network_render.job_id: + col.operator("screen.render", text="Get Results", icon='RENDER_ANIMATION').animation = True + col.prop(scene.network_render, "job_name") + col.prop(scene.network_render, "job_category") + row = col.row() + row.prop(scene.network_render, "priority") + row.prop(scene.network_render, "chunks") @rnaType class RENDER_PT_network_slaves(RenderButtonsPanel): - bl_label = "Slaves Status" - COMPAT_ENGINES = {'NET_RENDER'} - - def poll(self, context): - scene = context.scene - return (super().poll(context) - and scene.network_render.mode == "RENDER_CLIENT" - and scene.network_render.server_address != "[default]") - - def draw(self, context): - layout = self.layout - - scene = context.scene - netsettings = scene.network_render - - row = layout.row() - row.template_list(netsettings, "slaves", netsettings, "active_slave_index", rows=2) - - sub = row.column(align=True) - sub.operator("render.netclientslaves", icon='FILE_REFRESH', text="") - sub.operator("render.netclientblacklistslave", icon='ZOOMOUT', text="") - - if len(netrender.slaves) == 0 and len(netsettings.slaves) > 0: - while(len(netsettings.slaves) > 0): - netsettings.slaves.remove(0) - - if netsettings.active_slave_index >= 0 and len(netsettings.slaves) > 0: - layout.separator() - - slave = netrender.slaves[netsettings.active_slave_index] - - layout.label(text="Name: " + slave.name) - layout.label(text="Address: " + slave.address[0]) - layout.label(text="Seen: " + time.ctime(slave.last_seen)) - layout.label(text="Stats: " + slave.stats) + bl_label = "Slaves Status" + COMPAT_ENGINES = {'NET_RENDER'} + + def poll(self, context): + scene = context.scene + return (super().poll(context) + and scene.network_render.mode == "RENDER_CLIENT" + and scene.network_render.server_address != "[default]") + + def draw(self, context): + layout = self.layout + + scene = context.scene + netsettings = scene.network_render + + row = layout.row() + row.template_list(netsettings, "slaves", netsettings, "active_slave_index", rows=2) + + sub = row.column(align=True) + sub.operator("render.netclientslaves", icon='FILE_REFRESH', text="") + sub.operator("render.netclientblacklistslave", icon='ZOOMOUT', text="") + + if len(netrender.slaves) == 0 and len(netsettings.slaves) > 0: + while(len(netsettings.slaves) > 0): + netsettings.slaves.remove(0) + + if netsettings.active_slave_index >= 0 and len(netsettings.slaves) > 0: + layout.separator() + + slave = netrender.slaves[netsettings.active_slave_index] + + layout.label(text="Name: " + slave.name) + layout.label(text="Address: " + slave.address[0]) + layout.label(text="Seen: " + time.ctime(slave.last_seen)) + layout.label(text="Stats: " + slave.stats) @rnaType class RENDER_PT_network_slaves_blacklist(RenderButtonsPanel): - bl_label = "Slaves Blacklist" - COMPAT_ENGINES = {'NET_RENDER'} - - def poll(self, context): - scene = context.scene - return (super().poll(context) - and scene.network_render.mode == "RENDER_CLIENT" - and scene.network_render.server_address != "[default]") - - def draw(self, context): - layout = self.layout - - scene = context.scene - netsettings = scene.network_render - - row = layout.row() - row.template_list(netsettings, "slaves_blacklist", netsettings, "active_blacklisted_slave_index", rows=2) - - sub = row.column(align=True) - sub.operator("render.netclientwhitelistslave", icon='ZOOMOUT', text="") - - if len(netrender.blacklist) == 0 and len(netsettings.slaves_blacklist) > 0: - while(len(netsettings.slaves_blacklist) > 0): - netsettings.slaves_blacklist.remove(0) - - if netsettings.active_blacklisted_slave_index >= 0 and len(netsettings.slaves_blacklist) > 0: - layout.separator() - - slave = netrender.blacklist[netsettings.active_blacklisted_slave_index] - - layout.label(text="Name: " + slave.name) - layout.label(text="Address: " + slave.address[0]) - layout.label(text="Seen: " + time.ctime(slave.last_seen)) - layout.label(text="Stats: " + slave.stats) + bl_label = "Slaves Blacklist" + COMPAT_ENGINES = {'NET_RENDER'} + + def poll(self, context): + scene = context.scene + return (super().poll(context) + and scene.network_render.mode == "RENDER_CLIENT" + and scene.network_render.server_address != "[default]") + + def draw(self, context): + layout = self.layout + + scene = context.scene + netsettings = scene.network_render + + row = layout.row() + row.template_list(netsettings, "slaves_blacklist", netsettings, "active_blacklisted_slave_index", rows=2) + + sub = row.column(align=True) + sub.operator("render.netclientwhitelistslave", icon='ZOOMOUT', text="") + + if len(netrender.blacklist) == 0 and len(netsettings.slaves_blacklist) > 0: + while(len(netsettings.slaves_blacklist) > 0): + netsettings.slaves_blacklist.remove(0) + + if netsettings.active_blacklisted_slave_index >= 0 and len(netsettings.slaves_blacklist) > 0: + layout.separator() + + slave = netrender.blacklist[netsettings.active_blacklisted_slave_index] + + layout.label(text="Name: " + slave.name) + layout.label(text="Address: " + slave.address[0]) + layout.label(text="Seen: " + time.ctime(slave.last_seen)) + layout.label(text="Stats: " + slave.stats) @rnaType class RENDER_PT_network_jobs(RenderButtonsPanel): - bl_label = "Jobs" - COMPAT_ENGINES = {'NET_RENDER'} - - def poll(self, context): - scene = context.scene - return (super().poll(context) - and scene.network_render.mode == "RENDER_CLIENT" - and scene.network_render.server_address != "[default]") - - def draw(self, context): - layout = self.layout - - scene = context.scene - netsettings = scene.network_render - - row = layout.row() - row.template_list(netsettings, "jobs", netsettings, "active_job_index", rows=2) - - sub = row.column(align=True) - sub.operator("render.netclientstatus", icon='FILE_REFRESH', text="") - sub.operator("render.netclientcancel", icon='ZOOMOUT', text="") - sub.operator("render.netclientcancelall", icon='PANEL_CLOSE', text="") - sub.operator("render.netclientdownload", icon='RENDER_ANIMATION', text="") - - if len(netrender.jobs) == 0 and len(netsettings.jobs) > 0: - while(len(netsettings.jobs) > 0): - netsettings.jobs.remove(0) - - if netsettings.active_job_index >= 0 and len(netsettings.jobs) > 0: - layout.separator() - - job = netrender.jobs[netsettings.active_job_index] - - layout.label(text="Name: %s" % job.name) - layout.label(text="Length: %04i" % len(job)) - layout.label(text="Done: %04i" % job.results[DONE]) - layout.label(text="Error: %04i" % job.results[ERROR]) + bl_label = "Jobs" + COMPAT_ENGINES = {'NET_RENDER'} + + def poll(self, context): + scene = context.scene + return (super().poll(context) + and scene.network_render.mode == "RENDER_CLIENT" + and scene.network_render.server_address != "[default]") + + def draw(self, context): + layout = self.layout + + scene = context.scene + netsettings = scene.network_render + + row = layout.row() + row.template_list(netsettings, "jobs", netsettings, "active_job_index", rows=2) + + sub = row.column(align=True) + sub.operator("render.netclientstatus", icon='FILE_REFRESH', text="") + sub.operator("render.netclientcancel", icon='ZOOMOUT', text="") + sub.operator("render.netclientcancelall", icon='PANEL_CLOSE', text="") + sub.operator("render.netclientdownload", icon='RENDER_ANIMATION', text="") + + if len(netrender.jobs) == 0 and len(netsettings.jobs) > 0: + while(len(netsettings.jobs) > 0): + netsettings.jobs.remove(0) + + if netsettings.active_job_index >= 0 and len(netsettings.jobs) > 0: + layout.separator() + + job = netrender.jobs[netsettings.active_job_index] + + layout.label(text="Name: %s" % job.name) + layout.label(text="Length: %04i" % len(job)) + layout.label(text="Done: %04i" % job.results[DONE]) + layout.label(text="Error: %04i" % job.results[ERROR]) @rnaType class NetRenderSettings(bpy.types.IDPropertyGroup): - pass + pass @rnaType class NetRenderSlave(bpy.types.IDPropertyGroup): - pass + pass @rnaType class NetRenderJob(bpy.types.IDPropertyGroup): - pass + pass bpy.types.Scene.PointerProperty(attr="network_render", type=NetRenderSettings, name="Network Render", description="Network Render Settings") NetRenderSettings.StringProperty( attr="server_address", - name="Server address", - description="IP or name of the master render server", - maxlen = 128, - default = "[default]") + name="Server address", + description="IP or name of the master render server", + maxlen = 128, + default = "[default]") NetRenderSettings.IntProperty( attr="server_port", - name="Server port", - description="port of the master render server", - default = 8000, - min=1, - max=65535) + name="Server port", + description="port of the master render server", + default = 8000, + min=1, + max=65535) NetRenderSettings.BoolProperty( attr="server_broadcast", - name="Broadcast server address", - description="broadcast server address on local network", - default = True) + name="Broadcast server address", + description="broadcast server address on local network", + default = True) default_path = os.environ.get("TEMP", None) if not default_path: - if os.name == 'nt': - default_path = "c:/tmp/" - else: - default_path = "/tmp/" + if os.name == 'nt': + default_path = "c:/tmp/" + else: + default_path = "/tmp/" elif not default_path.endswith(os.sep): - default_path += os.sep + default_path += os.sep NetRenderSettings.StringProperty( attr="path", - name="Path", - description="Path for temporary files", - maxlen = 128, - default = default_path) + name="Path", + description="Path for temporary files", + maxlen = 128, + default = default_path) NetRenderSettings.StringProperty( attr="job_name", - name="Job name", - description="Name of the job", - maxlen = 128, - default = "[default]") + name="Job name", + description="Name of the job", + maxlen = 128, + default = "[default]") NetRenderSettings.StringProperty( attr="job_category", - name="Job category", - description="Category of the job", - maxlen = 128, - default = "") + name="Job category", + description="Category of the job", + maxlen = 128, + default = "") NetRenderSettings.IntProperty( attr="chunks", - name="Chunks", - description="Number of frame to dispatch to each slave in one chunk", - default = 5, - min=1, - max=65535) + name="Chunks", + description="Number of frame to dispatch to each slave in one chunk", + default = 5, + min=1, + max=65535) NetRenderSettings.IntProperty( attr="priority", - name="Priority", - description="Priority of the job", - default = 1, - min=1, - max=10) + name="Priority", + description="Priority of the job", + default = 1, + min=1, + max=10) NetRenderSettings.StringProperty( attr="job_id", - name="Network job id", - description="id of the last sent render job", - maxlen = 64, - default = "") + name="Network job id", + description="id of the last sent render job", + maxlen = 64, + default = "") NetRenderSettings.IntProperty( attr="active_slave_index", - name="Index of the active slave", - description="", - default = -1, - min= -1, - max=65535) + name="Index of the active slave", + description="", + default = -1, + min= -1, + max=65535) NetRenderSettings.IntProperty( attr="active_blacklisted_slave_index", - name="Index of the active slave", - description="", - default = -1, - min= -1, - max=65535) + name="Index of the active slave", + description="", + default = -1, + min= -1, + max=65535) NetRenderSettings.IntProperty( attr="active_job_index", - name="Index of the active job", - description="", - default = -1, - min= -1, - max=65535) + name="Index of the active job", + description="", + default = -1, + min= -1, + max=65535) NetRenderSettings.EnumProperty(attr="mode", - items=( - ("RENDER_CLIENT", "Client", "Act as render client"), - ("RENDER_MASTER", "Master", "Act as render master"), - ("RENDER_SLAVE", "Slave", "Act as render slave"), - ), - name="Network mode", - description="Mode of operation of this instance", - default="RENDER_CLIENT") + items=( + ("RENDER_CLIENT", "Client", "Act as render client"), + ("RENDER_MASTER", "Master", "Act as render master"), + ("RENDER_SLAVE", "Slave", "Act as render slave"), + ), + name="Network mode", + description="Mode of operation of this instance", + default="RENDER_CLIENT") NetRenderSettings.CollectionProperty(attr="slaves", type=NetRenderSlave, name="Slaves", description="") NetRenderSettings.CollectionProperty(attr="slaves_blacklist", type=NetRenderSlave, name="Slaves Blacklist", description="") NetRenderSettings.CollectionProperty(attr="jobs", type=NetRenderJob, name="Job List", description="") NetRenderSlave.StringProperty( attr="name", - name="Name of the slave", - description="", - maxlen = 64, - default = "") + name="Name of the slave", + description="", + maxlen = 64, + default = "") NetRenderJob.StringProperty( attr="name", - name="Name of the job", - description="", - maxlen = 128, - default = "") + name="Name of the job", + description="", + maxlen = 128, + default = "") diff --git a/release/scripts/io/netrender/utils.py b/release/scripts/io/netrender/utils.py index eadc7504b57..84275710391 100644 --- a/release/scripts/io/netrender/utils.py +++ b/release/scripts/io/netrender/utils.py @@ -4,12 +4,12 @@ # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. -# +# # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. -# +# # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software Foundation, # Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. @@ -51,22 +51,22 @@ DONE = 2 ERROR = 3 FRAME_STATUS_TEXT = { - QUEUED: "Queued", - DISPATCHED: "Dispatched", - DONE: "Done", - ERROR: "Error" - } + QUEUED: "Queued", + DISPATCHED: "Dispatched", + DONE: "Done", + ERROR: "Error" + } def rnaType(rna_type): - if bpy: bpy.types.register(rna_type) - return rna_type + if bpy: bpy.types.register(rna_type) + return rna_type def reporting(report, message, errorType = None): if errorType: t = 'ERROR' else: t = 'INFO' - + if report: report(t, message) return None @@ -82,14 +82,14 @@ def clientScan(report = None): s.settimeout(30) s.bind(('', 8000)) - + buf, address = s.recvfrom(64) - + address = address[0] port = int(str(buf, encoding='utf8')) - + reporting(report, "Master server found") - + return (address, port) except socket.timeout: reporting(report, "No master server on network", IOError) @@ -105,10 +105,10 @@ def clientConnection(address, port, report = None): address, port = clientScan() if address == "": return None - - try: + + try: conn = http.client.HTTPConnection(address, port) - + if conn: if clientVerifyVersion(conn): return conn @@ -123,21 +123,21 @@ def clientConnection(address, port, report = None): raise def clientVerifyVersion(conn): - conn.request("GET", "/version") - response = conn.getresponse() - - if response.status != http.client.OK: - conn.close() - return False - - server_version = response.read() - - if server_version != VERSION: - print("Incorrect server version!") - print("expected", str(VERSION, encoding='utf8'), "received", str(server_version, encoding='utf8')) - return False - - return True + conn.request("GET", "/version") + response = conn.getresponse() + + if response.status != http.client.OK: + conn.close() + return False + + server_version = response.read() + + if server_version != VERSION: + print("Incorrect server version!") + print("expected", str(VERSION, encoding='utf8'), "received", str(server_version, encoding='utf8')) + return False + + return True def fileURL(job_id, file_index): return "/file_%s_%i" % (job_id, file_index) @@ -152,20 +152,20 @@ def cancelURL(job_id): return "/cancel_%s" % (job_id) def prefixPath(prefix_directory, file_path, prefix_path): - if os.path.isabs(file_path): - # if an absolute path, make sure path exists, if it doesn't, use relative local path - full_path = file_path - if not os.path.exists(full_path): - p, n = os.path.split(full_path) - - if prefix_path and p.startswith(prefix_path): - directory = prefix_directory + p[len(prefix_path):] - full_path = directory + os.sep + n - if not os.path.exists(directory): - os.mkdir(directory) - else: - full_path = prefix_directory + n - else: - full_path = prefix_directory + file_path - - return full_path + if os.path.isabs(file_path): + # if an absolute path, make sure path exists, if it doesn't, use relative local path + full_path = file_path + if not os.path.exists(full_path): + p, n = os.path.split(full_path) + + if prefix_path and p.startswith(prefix_path): + directory = prefix_directory + p[len(prefix_path):] + full_path = directory + os.sep + n + if not os.path.exists(directory): + os.mkdir(directory) + else: + full_path = prefix_directory + n + else: + full_path = prefix_directory + file_path + + return full_path diff --git a/release/scripts/presets/render/HDTV_1080p.py b/release/scripts/presets/render/HDTV_1080p.py index a0297a200f3..75a5faeb27d 100644 --- a/release/scripts/presets/render/HDTV_1080p.py +++ b/release/scripts/presets/render/HDTV_1080p.py @@ -4,4 +4,4 @@ bpy.context.scene.render_data.resolution_percentage = 100 bpy.context.scene.render_data.pixel_aspect_x = 1 bpy.context.scene.render_data.pixel_aspect_y = 1 bpy.context.scene.render_data.fps = 24 -bpy.context.scene.render_data.fps_base = 1
\ No newline at end of file +bpy.context.scene.render_data.fps_base = 1 diff --git a/release/scripts/presets/render/HDTV_720p.py b/release/scripts/presets/render/HDTV_720p.py index 4d34a928eb3..4f46f4045e0 100644 --- a/release/scripts/presets/render/HDTV_720p.py +++ b/release/scripts/presets/render/HDTV_720p.py @@ -4,4 +4,4 @@ bpy.context.scene.render_data.resolution_percentage = 100 bpy.context.scene.render_data.pixel_aspect_x = 1 bpy.context.scene.render_data.pixel_aspect_y = 1 bpy.context.scene.render_data.fps = 24 -bpy.context.scene.render_data.fps_base = 1
\ No newline at end of file +bpy.context.scene.render_data.fps_base = 1 diff --git a/release/scripts/presets/render/TV_PAL.py b/release/scripts/presets/render/TV_PAL.py index 7044070fb0c..0992b9b8de8 100644 --- a/release/scripts/presets/render/TV_PAL.py +++ b/release/scripts/presets/render/TV_PAL.py @@ -4,4 +4,4 @@ bpy.context.scene.render_data.resolution_percentage = 100 bpy.context.scene.render_data.pixel_aspect_x = 54 bpy.context.scene.render_data.pixel_aspect_y = 51 bpy.context.scene.render_data.fps = 25 -bpy.context.scene.render_data.fps_base = 1
\ No newline at end of file +bpy.context.scene.render_data.fps_base = 1 diff --git a/release/scripts/presets/render/TV_PAL_16_colon_9.py b/release/scripts/presets/render/TV_PAL_16_colon_9.py index 756d34c8af3..9b85805352d 100644 --- a/release/scripts/presets/render/TV_PAL_16_colon_9.py +++ b/release/scripts/presets/render/TV_PAL_16_colon_9.py @@ -4,4 +4,4 @@ bpy.context.scene.render_data.resolution_percentage = 100 bpy.context.scene.render_data.pixel_aspect_x = 64 bpy.context.scene.render_data.pixel_aspect_y = 45 bpy.context.scene.render_data.fps = 25 -bpy.context.scene.render_data.fps_base = 1
\ No newline at end of file +bpy.context.scene.render_data.fps_base = 1 diff --git a/release/scripts/ui/space_userpref.py b/release/scripts/ui/space_userpref.py index ed41b897539..4c807fd9bd2 100644 --- a/release/scripts/ui/space_userpref.py +++ b/release/scripts/ui/space_userpref.py @@ -1476,13 +1476,13 @@ class USERPREF_PT_input(bpy.types.Panel): for km in kc.keymaps: filtered_items = [kmi for kmi in km.items if filter in kmi.name.lower()] - + if len(filtered_items) != 0: km = km.active() layout.set_context_pointer("keymap", km) col = layout.column() - + row = col.row() row.label(text=km.name, icon="DOT") @@ -1496,7 +1496,7 @@ class USERPREF_PT_input(bpy.types.Panel): for kmi in filtered_items: self.draw_kmi(kc, km, kmi, col, 1) - + # "Add New" at end of keymap item list col = self.indented_layout(layout, 1) subcol = col.split(percentage=0.2).column() @@ -1813,18 +1813,18 @@ class WM_OT_keyitem_add(bpy.types.Operator): wm = context.manager km = context.keymap kc = wm.default_keyconfig - + if km.modal: km.add_modal_item("", 'A', 'PRESS') # kmi else: km.add_item("none", 'A', 'PRESS') # kmi - + # clear filter and expand keymap so we can see the newly added item if kc.filter != '': kc.filter = '' km.items_expanded = True km.children_expanded = True - + return {'FINISHED'} diff --git a/release/scripts/ui/space_view3d_toolbar.py b/release/scripts/ui/space_view3d_toolbar.py index fb8a4cf335f..b2c8c39cb3a 100644 --- a/release/scripts/ui/space_view3d_toolbar.py +++ b/release/scripts/ui/space_view3d_toolbar.py @@ -498,14 +498,14 @@ class VIEW3D_PT_tools_brush(PaintPanel): if not context.particle_edit_object: col = layout.split().column() row = col.row() - + if context.sculpt_object and brush: defaulttools = 8 elif context.texture_paint_object and brush: defaulttools = 4 else: defaulttools = 2 - + row.template_list(settings, "brushes", settings, "active_brush_index", rows=2, maxrows=defaulttools) col.template_ID(settings, "brush", new="brush.add") @@ -539,7 +539,7 @@ class VIEW3D_PT_tools_brush(PaintPanel): elif context.sculpt_object and brush: col = layout.column() col.separator() - + row = col.row(align=True) row.prop(brush, "size", slider=True) row.prop(brush, "use_size_pressure", toggle=True, text="") @@ -642,7 +642,7 @@ class VIEW3D_PT_tools_brush_tool(PaintPanel): sculpt = context.sculpt_object col = layout.column(align=True) - + if context.sculpt_object: col.prop(brush, "sculpt_tool", expand=True) elif context.texture_paint_object: |