Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMartin Poirier <theeth@yahoo.com>2010-10-27 22:24:58 +0400
committerMartin Poirier <theeth@yahoo.com>2010-10-27 22:24:58 +0400
commit58a1ddcc9e5fe4b685ee328fa5318e1261f7c160 (patch)
treedc31b7191abc27b170ebaa068c7fbf07103d4f65
parent05bb6b5d6ccfc1846fc524f4f790fd057f1a7616 (diff)
netrender
New Feature: VCS job type Render a file (with dependencies) from a version control system (currently only supports subversion, but system is already generic). On client, working path, server path and current revision can be guessed from data on disk or entered manually. On slave, a working copy is created (if needed) where specified by the job and updated to the proper revision. On master web page, job types now appear in the job lists. The job page shows the list of dependencies for "normal" jobs or versioning information for VCS jobs. Limitations: Need to have command line tools "svn" and "svnversion". Working copy path must be the same on client and slaves (the client gets the job path relative to the working copy). When guessing, working copy path is set to the folder where the current file is (this can be changed manually after). On the slave, it will update the working copy AS SPECIFIED to the revision, so if that path is too deep, some dependencies will not be updated properly. Doesn't support mixed revisions (and will not give any warnings for that), it will always use the first revision specified by "svnversion" Bugfix: Thumbnail generation doesn't chew down memory anymore and always gives correct result (thumbnail on master especially could mess up between jobs with the name result filename)
-rw-r--r--release/scripts/io/netrender/__init__.py2
-rw-r--r--release/scripts/io/netrender/client.py84
-rw-r--r--release/scripts/io/netrender/master.py14
-rw-r--r--release/scripts/io/netrender/master_html.py78
-rw-r--r--release/scripts/io/netrender/model.py84
-rw-r--r--release/scripts/io/netrender/operators.py33
-rw-r--r--release/scripts/io/netrender/slave.py20
-rw-r--r--release/scripts/io/netrender/ui.py60
-rw-r--r--release/scripts/io/netrender/utils.py23
-rw-r--r--release/scripts/io/netrender/versioning.py72
10 files changed, 415 insertions, 55 deletions
diff --git a/release/scripts/io/netrender/__init__.py b/release/scripts/io/netrender/__init__.py
index b8c1fddc691..32480030363 100644
--- a/release/scripts/io/netrender/__init__.py
+++ b/release/scripts/io/netrender/__init__.py
@@ -30,6 +30,7 @@ if "init_data" in locals():
reload(balancing)
reload(ui)
reload(repath)
+ reload(versioning)
else:
from netrender import model
from netrender import operators
@@ -41,6 +42,7 @@ else:
from netrender import balancing
from netrender import ui
from netrender import repath
+ from netrender import versioning
jobs = []
slaves = []
diff --git a/release/scripts/io/netrender/client.py b/release/scripts/io/netrender/client.py
index 128ae99ab1d..c469c80264e 100644
--- a/release/scripts/io/netrender/client.py
+++ b/release/scripts/io/netrender/client.py
@@ -92,8 +92,83 @@ def addPointCache(job, ob, point_cache, default_path):
previous_frame = previous_item[0]
job.addFile(cache_path + current_file, previous_frame + 1, next_frame - 1)
+def fillCommonJobSettings(job, job_name, netsettings):
+ 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
+
+ if netsettings.job_type == "JOB_BLENDER":
+ job.type = netrender.model.JOB_BLENDER
+ elif netsettings.job_type == "JOB_PROCESS":
+ job.type = netrender.model.JOB_PROCESS
+ elif netsettings.job_type == "JOB_VCS":
+ job.type = netrender.model.JOB_VCS
+
def clientSendJob(conn, scene, anim = False):
netsettings = scene.network_render
+ if netsettings.job_type == "JOB_BLENDER":
+ return clientSendJobBlender(conn, scene, anim)
+ elif netsettings.job_type == "JOB_VCS":
+ return clientSendJobVCS(conn, scene, anim)
+
+def clientSendJobVCS(conn, scene, anim = False):
+ netsettings = scene.network_render
+ job = netrender.model.RenderJob()
+
+ if anim:
+ for f in range(scene.frame_start, scene.frame_end + 1):
+ job.addFrame(f)
+ else:
+ job.addFrame(scene.frame_current)
+
+ filename = bpy.data.filepath
+
+ if not filename.startswith(netsettings.vcs_wpath):
+ # this is an error, need better way to handle this
+ return
+
+ filename = filename[len(netsettings.vcs_wpath):]
+
+ if filename[0] in (os.sep, os.altsep):
+ filename = filename[1:]
+
+ print("CREATING VCS JOB", filename)
+
+ job.addFile(filename, signed=False)
+
+ job_name = netsettings.job_name
+ path, name = os.path.split(filename)
+ if job_name == "[default]":
+ job_name = name
+
+
+ fillCommonJobSettings(job, job_name, netsettings)
+
+ # VCS Specific code
+ job.version_info = netrender.model.VersioningInfo()
+ job.version_info.system = netsettings.vcs_system
+ job.version_info.wpath = netsettings.vcs_wpath
+ job.version_info.rpath = netsettings.vcs_rpath
+ job.version_info.revision = netsettings.vcs_revision
+
+ # try to send path first
+ conn.request("POST", "/job", json.dumps(job.serialize()))
+ response = conn.getresponse()
+ response.read()
+
+ job_id = response.getheader("job-id")
+
+ # a VCS job is always good right now, need error handling
+
+ return job_id
+
+def clientSendJobBlender(conn, scene, anim = False):
+ netsettings = scene.network_render
job = netrender.model.RenderJob()
if anim:
@@ -160,14 +235,7 @@ def clientSendJob(conn, scene, anim = False):
#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
+ fillCommonJobSettings(job, job_name, netsettings)
# try to send path first
conn.request("POST", "/job", json.dumps(job.serialize()))
diff --git a/release/scripts/io/netrender/master.py b/release/scripts/io/netrender/master.py
index 11409bf372e..93f2baf4a36 100644
--- a/release/scripts/io/netrender/master.py
+++ b/release/scripts/io/netrender/master.py
@@ -35,7 +35,7 @@ class MRenderFile(netrender.model.RenderFile):
def test(self):
self.found = os.path.exists(self.filepath)
- if self.found:
+ if self.found and self.signature != None:
found_signature = hashFile(self.filepath)
self.found = self.signature == found_signature
@@ -105,9 +105,11 @@ class MRenderJob(netrender.model.RenderJob):
self.chunks = info_map["chunks"]
def testStart(self):
- for f in self.files:
- if not f.test():
- return False
+ # Don't test files for versionned jobs
+ if not self.version_info:
+ for f in self.files:
+ if not f.test():
+ return False
self.start()
self.initInfo()
@@ -769,7 +771,7 @@ class RenderHandler(http.server.BaseHTTPRequestHandler):
frame = job[job_frame]
if frame:
- if job.type == netrender.model.JOB_BLENDER:
+ if job.hasRenderResult():
if job_result == DONE:
length = int(self.headers['content-length'])
buf = self.rfile.read(length)
@@ -820,7 +822,7 @@ class RenderHandler(http.server.BaseHTTPRequestHandler):
frame = job[job_frame]
if frame:
- if job.type == netrender.model.JOB_BLENDER:
+ if job.hasRenderResult():
length = int(self.headers['content-length'])
buf = self.rfile.read(length)
f = open(os.path.join(job.save_path, "%06d.jpg" % job_frame), 'wb')
diff --git a/release/scripts/io/netrender/master_html.py b/release/scripts/io/netrender/master_html.py
index 74155f6bd66..0e14905a86b 100644
--- a/release/scripts/io/netrender/master_html.py
+++ b/release/scripts/io/netrender/master_html.py
@@ -20,6 +20,7 @@ import os
import re
import shutil
from netrender.utils import *
+import netrender.model
src_folder = os.path.split(__file__)[0]
@@ -115,6 +116,7 @@ def get(handler):
"id",
"name",
"category",
+ "type",
"chunks",
"priority",
"usage",
@@ -139,6 +141,7 @@ def get(handler):
job.id,
link(job.name, "/html/job" + job.id),
job.category if job.category else "<i>None</i>",
+ netrender.model.JOB_TYPES[job.type],
str(job.chunks) +
"""<button title="increase chunks size" onclick="request('/edit_%s', &quot;{'chunks': %i}&quot;);">+</button>""" % (job.id, job.chunks + 1) +
"""<button title="decrease chunks size" onclick="request('/edit_%s', &quot;{'chunks': %i}&quot;);" %s>-</button>""" % (job.id, job.chunks - 1, "disabled=True" if job.chunks == 1 else ""),
@@ -228,39 +231,52 @@ def get(handler):
endTable()
- output("<h2>Files</h2>")
-
- startTable()
- headerTable("path")
-
- tot_cache = 0
- tot_fluid = 0
-
- rowTable(job.files[0].filepath)
- rowTable("Other Files", class_style = "toggle", extra = "onclick='toggleDisplay(&quot;.other&quot;, &quot;none&quot;, &quot;table-row&quot;)'")
-
- 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:
- if file != job.files[0]:
- rowTable(file.filepath, class_style = "other")
-
- if tot_cache > 0:
- rowTable("%i physic cache files" % tot_cache, class_style = "toggle", extra = "onclick='toggleDisplay(&quot;.cache&quot;, &quot;none&quot;, &quot;table-row&quot;)'")
+ if job.type == netrender.model.JOB_BLENDER:
+ output("<h2>Files</h2>")
+
+ startTable()
+ headerTable("path")
+
+ tot_cache = 0
+ tot_fluid = 0
+
+ rowTable(job.files[0].filepath)
+ rowTable("Other Files", class_style = "toggle", extra = "onclick='toggleDisplay(&quot;.other&quot;, &quot;none&quot;, &quot;table-row&quot;)'")
+
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(&quot;.fluid&quot;, &quot;none&quot;, &quot;table-row&quot;)'")
- 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()
+ tot_cache += 1
+ elif file.filepath.endswith(".bobj.gz") or file.filepath.endswith(".bvel.gz"):
+ tot_fluid += 1
+ else:
+ if file != job.files[0]:
+ rowTable(file.filepath, class_style = "other")
+
+ if tot_cache > 0:
+ rowTable("%i physic cache files" % tot_cache, class_style = "toggle", extra = "onclick='toggleDisplay(&quot;.cache&quot;, &quot;none&quot;, &quot;table-row&quot;)'")
+ 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(&quot;.fluid&quot;, &quot;none&quot;, &quot;table-row&quot;)'")
+ 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()
+ elif job.type == netrender.model.JOB_VCS:
+ output("<h2>Versioning</h2>")
+
+ startTable()
+
+ rowTable("System", job.version_info.system.name)
+ rowTable("Remote Path", job.version_info.rpath)
+ rowTable("Working Path", job.version_info.wpath)
+ rowTable("Revision", job.version_info.revision)
+ rowTable("Render File", job.files[0].filepath)
+
+ endTable()
if job.blacklist:
output("<h2>Blacklist</h2>")
diff --git a/release/scripts/io/netrender/model.py b/release/scripts/io/netrender/model.py
index e7656f498b4..60186396ac7 100644
--- a/release/scripts/io/netrender/model.py
+++ b/release/scripts/io/netrender/model.py
@@ -20,6 +20,7 @@ import sys, os
import http, http.client, http.server, urllib
import subprocess, shutil, time, hashlib
+import netrender.versioning as versioning
from netrender.utils import *
class LogFile:
@@ -96,11 +97,65 @@ class RenderSlave:
JOB_BLENDER = 1
JOB_PROCESS = 2
+JOB_VCS = 3
JOB_TYPES = {
- JOB_BLENDER: "Blender",
- JOB_PROCESS: "Process"
- }
+ JOB_BLENDER: "Blender",
+ JOB_PROCESS: "Process",
+ JOB_VCS: "Versioned",
+ }
+
+class VersioningInfo:
+ def __init__(self, info = None):
+ self._system = None
+ self.wpath = ""
+ self.rpath = ""
+ self.revision = ""
+
+ @property
+ def system(self):
+ return self._system
+
+ @system.setter
+ def system(self, value):
+ self._system = versioning.SYSTEMS[value]
+
+ def update(self):
+ self.system.update(self)
+
+ def serialize(self):
+ return {
+ "wpath": self.wpath,
+ "rpath": self.rpath,
+ "revision": self.revision,
+ "system": self.system.name
+ }
+
+ @staticmethod
+ def generate(system, path):
+ vs = VersioningInfo()
+ vs.wpath = path
+ vs.system = system
+
+ vs.rpath = vs.system.path(path)
+ vs.revision = vs.system.revision(path)
+
+ return vs
+
+
+ @staticmethod
+ def materialize(data):
+ if not data:
+ return None
+
+ vs = VersioningInfo()
+ vs.wpath = data["wpath"]
+ vs.rpath = data["rpath"]
+ vs.revision = data["revision"]
+ vs.system = data["system"]
+
+ return vs
+
class RenderFile:
def __init__(self, filepath = "", index = 0, start = -1, end = -1, signature=0):
@@ -142,6 +197,8 @@ class RenderJob:
self.chunks = 0
self.priority = 0
self.blacklist = []
+
+ self.version_info = None
self.usage = 0.0
self.last_dispatched = 0.0
@@ -156,9 +213,19 @@ class RenderJob:
self.chunks = job_info.chunks
self.priority = job_info.priority
self.blacklist = job_info.blacklist
+ self.version_info = job_info.version_info
+
+ def hasRenderResult(self):
+ return self.type in (JOB_BLENDER, JOB_VCS)
- def addFile(self, file_path, start=-1, end=-1):
- signature = hashFile(file_path)
+ def rendersWithBlender(self):
+ return self.type in (JOB_BLENDER, JOB_VCS)
+
+ def addFile(self, file_path, start=-1, end=-1, signed=True):
+ if signed:
+ signature = hashFile(file_path)
+ else:
+ signature = None
self.files.append(RenderFile(file_path, len(self.files), start, end, signature))
def addFrame(self, frame_number, command = ""):
@@ -225,7 +292,8 @@ class RenderJob:
"priority": self.priority,
"usage": self.usage,
"blacklist": self.blacklist,
- "last_dispatched": self.last_dispatched
+ "last_dispatched": self.last_dispatched,
+ "version_info": self.version_info.serialize() if self.version_info else None
}
@staticmethod
@@ -246,6 +314,10 @@ class RenderJob:
job.usage = data["usage"]
job.blacklist = data["blacklist"]
job.last_dispatched = data["last_dispatched"]
+
+ version_info = data.get("version_info", None)
+ if version_info:
+ job.version_info = VersioningInfo.materialize(version_info)
return job
diff --git a/release/scripts/io/netrender/operators.py b/release/scripts/io/netrender/operators.py
index 96601dcf653..fe82011d706 100644
--- a/release/scripts/io/netrender/operators.py
+++ b/release/scripts/io/netrender/operators.py
@@ -26,6 +26,7 @@ import netrender
from netrender.utils import *
import netrender.client as client
import netrender.model
+import netrender.versioning as versioning
class RENDER_OT_netslave_bake(bpy.types.Operator):
'''NEED DESCRIPTION'''
@@ -461,6 +462,38 @@ class netclientscan(bpy.types.Operator):
def invoke(self, context, event):
return self.execute(context)
+class netclientvcsguess(bpy.types.Operator):
+ '''Guess VCS setting for the current file'''
+ bl_idname = "render.netclientvcsguess"
+ bl_label = "VCS Guess"
+
+ @classmethod
+ def poll(cls, context):
+ return True
+
+ def execute(self, context):
+ netsettings = context.scene.network_render
+
+ system = versioning.SYSTEMS.get(netsettings.vcs_system, None)
+
+ if system:
+ wpath, name = os.path.split(os.path.abspath(bpy.data.filepath))
+
+ rpath = system.path(wpath)
+ revision = system.revision(wpath)
+
+ netsettings.vcs_wpath = wpath
+ netsettings.vcs_rpath = rpath
+ netsettings.vcs_revision = revision
+
+
+
+ return {'FINISHED'}
+
+ def invoke(self, context, event):
+ return self.execute(context)
+
+
class netclientweb(bpy.types.Operator):
'''Open new window with information about running rendering jobs'''
bl_idname = "render.netclientweb"
diff --git a/release/scripts/io/netrender/slave.py b/release/scripts/io/netrender/slave.py
index 8629a38c04a..08bd48ae70b 100644
--- a/release/scripts/io/netrender/slave.py
+++ b/release/scripts/io/netrender/slave.py
@@ -78,7 +78,7 @@ def testFile(conn, job_id, slave_id, rfile, JOB_PREFIX, main_path = None):
found = os.path.exists(job_full_path)
- if found:
+ if found and rfile.signature != None:
found_signature = hashFile(job_full_path)
found = found_signature == rfile.signature
@@ -161,6 +161,20 @@ def render_slave(engine, netsettings, threads):
netrender.repath.update(job)
engine.update_stats("", "Render File "+ main_file+ " for job "+ job.id)
+ elif job.type == netrender.model.JOB_VCS:
+ if not job.version_info:
+ # Need to return an error to server, incorrect job type
+ pass
+
+ job_path = job.files[0].filepath # path of main file
+ main_path, main_file = os.path.split(job_path)
+
+ job.version_info.update()
+
+ # For VCS jobs, file path is relative to the working copy path
+ job_full_path = os.path.join(job.version_info.wpath, job_path)
+
+ engine.update_stats("", "Render File "+ main_file+ " for job "+ job.id)
# announce log to master
logfile = netrender.model.LogFile(job.id, slave_id, [frame.number for frame in job.frames])
@@ -174,7 +188,7 @@ def render_slave(engine, netsettings, threads):
# start render
start_t = time.time()
- if job.type == netrender.model.JOB_BLENDER:
+ if job.rendersWithBlender():
frame_args = []
for frame in job.frames:
@@ -259,7 +273,7 @@ def render_slave(engine, netsettings, threads):
headers["job-result"] = str(DONE)
for frame in job.frames:
headers["job-frame"] = str(frame.number)
- if job.type == netrender.model.JOB_BLENDER:
+ if job.hasRenderResult():
# send image back to server
filename = os.path.join(JOB_PREFIX, "%06d.exr" % frame.number)
diff --git a/release/scripts/io/netrender/ui.py b/release/scripts/io/netrender/ui.py
index c065b95b928..916f567e299 100644
--- a/release/scripts/io/netrender/ui.py
+++ b/release/scripts/io/netrender/ui.py
@@ -203,10 +203,12 @@ class RENDER_PT_network_job(bpy.types.Panel, RenderButtonsPanel):
split = layout.split(percentage=0.3)
col = split.column()
+ col.label(text="Type:")
col.label(text="Name:")
col.label(text="Category:")
col = split.column()
+ col.prop(netsettings, "job_type", text="")
col.prop(netsettings, "job_name", text="")
col.prop(netsettings, "job_category", text="")
@@ -214,6 +216,30 @@ class RENDER_PT_network_job(bpy.types.Panel, RenderButtonsPanel):
row.prop(netsettings, "priority")
row.prop(netsettings, "chunks")
+class RENDER_PT_network_job_vcs(bpy.types.Panel, RenderButtonsPanel):
+ bl_label = "VCS Job Settings"
+ COMPAT_ENGINES = {'NET_RENDER'}
+
+ @classmethod
+ def poll(cls, context):
+ scene = context.scene
+ return (super(RENDER_PT_network_job_vcs, cls).poll(context)
+ and scene.network_render.mode == "RENDER_CLIENT"
+ and scene.network_render.job_type == "JOB_VCS")
+
+ def draw(self, context):
+ layout = self.layout
+
+ scene = context.scene
+ netsettings = scene.network_render
+
+ layout.operator("render.netclientvcsguess", icon='FILE_REFRESH', text="")
+
+ layout.prop(netsettings, "vcs_system")
+ layout.prop(netsettings, "vcs_revision")
+ layout.prop(netsettings, "vcs_rpath")
+ layout.prop(netsettings, "vcs_wpath")
+
class RENDER_PT_network_slaves(bpy.types.Panel, RenderButtonsPanel):
bl_label = "Slaves Status"
COMPAT_ENGINES = {'NET_RENDER'}
@@ -397,6 +423,16 @@ def addProperties():
default = default_path,
subtype='FILE_PATH')
+ NetRenderSettings.job_type = EnumProperty(
+ items=(
+ ("JOB_BLENDER", "Blender", "Standard Blender Job"),
+ ("JOB_PROCESS", "Process", "Custom Process Job"),
+ ("JOB_VCS", "VCS", "Version Control System Managed Job"),
+ ),
+ name="Job Type",
+ description="Type of render job",
+ default="JOB_BLENDER")
+
NetRenderSettings.job_name = StringProperty(
name="Job name",
description="Name of the job",
@@ -423,6 +459,30 @@ def addProperties():
min=1,
max=10)
+ NetRenderSettings.vcs_wpath = StringProperty(
+ name="Working Copy",
+ description="Path of the local working copy",
+ maxlen = 1024,
+ default = "")
+
+ NetRenderSettings.vcs_rpath = StringProperty(
+ name="Remote Path",
+ description="Path of the server copy (protocol specific)",
+ maxlen = 1024,
+ default = "")
+
+ NetRenderSettings.vcs_revision = StringProperty(
+ name="Revision",
+ description="Revision for this job",
+ maxlen = 256,
+ default = "")
+
+ NetRenderSettings.vcs_system = StringProperty(
+ name="VCS",
+ description="Version Control System",
+ maxlen = 64,
+ default = "Subversion")
+
NetRenderSettings.job_id = StringProperty(
name="Network job id",
description="id of the last sent render job",
diff --git a/release/scripts/io/netrender/utils.py b/release/scripts/io/netrender/utils.py
index e2a5051cf64..7ad65fee66a 100644
--- a/release/scripts/io/netrender/utils.py
+++ b/release/scripts/io/netrender/utils.py
@@ -28,7 +28,7 @@ try:
except:
bpy = None
-VERSION = bytes("0.9", encoding='utf8')
+VERSION = bytes("1.0", encoding='utf8')
# Jobs status
JOB_WAITING = 0 # before all data has been entered
@@ -57,6 +57,17 @@ FRAME_STATUS_TEXT = {
ERROR: "Error"
}
+class DirectoryContext:
+ def __init__(self, path):
+ self.path = path
+
+ def __enter__(self):
+ self.curdir = os.path.abspath(os.curdir)
+ os.chdir(self.path)
+
+ def __exit__(self, exc_type, exc_value, traceback):
+ os.chdir(self.curdir)
+
def responseStatus(conn):
response = conn.getresponse()
response.read()
@@ -216,9 +227,19 @@ def thumbnail(filename):
scene = bpy.data.scenes[0] # FIXME, this is dodgy!
scene.render.file_format = "JPEG"
scene.render.file_quality = 90
+
+ # remove existing image, if there's a leftover (otherwise open changes the name)
+ if imagename in bpy.data.images:
+ img = bpy.data.images[imagename]
+ bpy.data.images.remove(img)
+
bpy.ops.image.open(filepath=filename)
img = bpy.data.images[imagename]
+
img.save_render(thumbname, scene=scene)
+
+ img.user_clear()
+ bpy.data.images.remove(img)
try:
process = subprocess.Popen(["convert", thumbname, "-resize", "300x300", thumbname])
diff --git a/release/scripts/io/netrender/versioning.py b/release/scripts/io/netrender/versioning.py
new file mode 100644
index 00000000000..d4f8522cce8
--- /dev/null
+++ b/release/scripts/io/netrender/versioning.py
@@ -0,0 +1,72 @@
+import sys, os
+import re
+import subprocess
+
+from netrender.utils import *
+
+class AbstractVCS:
+ name = "ABSTRACT VCS"
+ def __init__(self):
+ pass
+
+ def update(self, info):
+ """update(info)
+ Update a working copy to the specified revision.
+ If working copy doesn't exist, do a full get from server to create it.
+ [info] model.VersioningInfo instance, specifies the working path, remote path and version number."""
+ pass
+
+ def revision(self, path):
+ """revision(path)
+ return the current revision of the specified working copy path"""
+ pass
+
+ def path(self, path):
+ """path(path)
+ return the remote path of the specified working copy path"""
+ pass
+
+class Subversion(AbstractVCS):
+ name = "Subversion"
+ def __init__(self):
+ super().__init__()
+ self.version_exp = re.compile("([0-9]*)")
+ self.path_exp = re.compile("URL: (.*)")
+
+ def update(self, info):
+ if not os.path.exists(info.wpath):
+ base, folder = os.path.split(info.wpath)
+
+ with DirectoryContext(base):
+ subprocess.call(["svn", "co", "%s@%s" % (info.rpath, str(info.revision)), folder])
+ else:
+ with DirectoryContext(info.wpath):
+ subprocess.call(["svn", "up", "--accept", "theirs-full", "-r", str(info.revision)])
+
+ def revision(self, path):
+ if not os.path.exists(path):
+ return
+
+ with DirectoryContext(path):
+ stdout = subprocess.check_output(["svnversion"])
+
+ match = self.version_exp.match(str(stdout, encoding="utf-8"))
+
+ if match:
+ return match.group(1)
+
+ def path(self, path):
+ if not os.path.exists(path):
+ return
+
+ with DirectoryContext(path):
+ stdout = subprocess.check_output(["svn", "info"])
+
+ match = self.path_exp.search(str(stdout, encoding="utf-8"))
+
+ if match:
+ return match.group(1)
+
+SYSTEMS = {
+ Subversion.name: Subversion()
+ }