pep8 compliance, also removed use of exec() for netrender module importing
[blender.git] / release / scripts / io / netrender / client.py
1 # ##### BEGIN GPL LICENSE BLOCK #####
2 #
3 #  This program is free software; you can redistribute it and/or
4 #  modify it under the terms of the GNU General Public License
5 #  as published by the Free Software Foundation; either version 2
6 #  of the License, or (at your option) any later version.
7
8 #  This program is distributed in the hope that it will be useful,
9 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
10 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 #  GNU General Public License for more details.
12
13 #  You should have received a copy of the GNU General Public License
14 #  along with this program; if not, write to the Free Software Foundation,
15 #  Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
16 #
17 # ##### END GPL LICENSE BLOCK #####
18
19 import bpy
20 import sys, os, re
21 import http, http.client, http.server, urllib
22 import subprocess, shutil, time, hashlib
23
24 import netrender
25 import netrender.model
26 import netrender.slave as slave
27 import netrender.master as master
28 from netrender.utils import *
29
30 def addFluidFiles(job, path):
31         if os.path.exists(path):
32                 pattern = re.compile("fluidsurface_(final|preview)_([0-9]+)\.(bobj|bvel)\.gz")
33
34                 for fluid_file in sorted(os.listdir(path)):
35                         match = pattern.match(fluid_file)
36                         
37                         if match:
38                                 # fluid frames starts at 0, which explains the +1
39                                 # This is stupid
40                                 current_frame = int(match.groups()[1]) + 1 
41                                 job.addFile(path + fluid_file, current_frame, current_frame)
42
43 def addPointCache(job, ob, point_cache, default_path):
44         if not point_cache.disk_cache:
45                 return
46         
47         
48         name = point_cache.name
49         if name == "":
50                 name = "".join(["%02X" % ord(c) for c in ob.name])
51         
52         cache_path = bpy.utils.expandpath(point_cache.filepath) if point_cache.external else default_path
53         
54         index = "%02i" % point_cache.index
55         
56         if os.path.exists(cache_path):
57                 pattern = re.compile(name + "_([0-9]+)_" + index + "\.bphys")
58                 
59                 cache_files = []
60
61                 for cache_file in sorted(os.listdir(cache_path)):
62                         match = pattern.match(cache_file)
63                         
64                         if match:
65                                 cache_frame = int(match.groups()[0])
66                                 cache_files.append((cache_frame, cache_file))
67                                 
68                 cache_files.sort()
69                 
70                 if len(cache_files) == 1:
71                         cache_frame, cache_file = cache_files[0]
72                         job.addFile(cache_path + cache_file, cache_frame, cache_frame)
73                 else:
74                         for i in range(len(cache_files)):
75                                 current_item = cache_files[i]
76                                 next_item = cache_files[i+1] if i + 1 < len(cache_files) else None
77                                 previous_item = cache_files[i - 1] if i > 0 else None
78                                 
79                                 current_frame, current_file = current_item
80                                 
81                                 if  not next_item and not previous_item:
82                                         job.addFile(cache_path + current_file, current_frame, current_frame)
83                                 elif next_item and not previous_item:
84                                         next_frame = next_item[0]
85                                         job.addFile(cache_path + current_file, current_frame, next_frame - 1)
86                                 elif not next_item and previous_item:
87                                         previous_frame = previous_item[0]
88                                         job.addFile(cache_path + current_file, previous_frame + 1, current_frame)
89                                 else:
90                                         next_frame = next_item[0]
91                                         previous_frame = previous_item[0]
92                                         job.addFile(cache_path + current_file, previous_frame + 1, next_frame - 1)
93                                                 
94 def clientSendJob(conn, scene, anim = False):
95         netsettings = scene.network_render
96         job = netrender.model.RenderJob()
97         
98         if anim:
99                 for f in range(scene.start_frame, scene.end_frame + 1):
100                         job.addFrame(f)
101         else:
102                 job.addFrame(scene.current_frame)
103         
104         filename = bpy.data.filename
105         job.addFile(filename)
106         
107         job_name = netsettings.job_name
108         path, name = os.path.split(filename)
109         path += os.sep
110         if job_name == "[default]":
111                 job_name = name
112         
113         ###########################
114         # LIBRARIES
115         ###########################
116         for lib in bpy.data.libraries:
117                 job.addFile(bpy.utils.expandpath(lib.filename))
118                 
119         ###########################
120         # IMAGES
121         ###########################
122         for image in bpy.data.images:
123                 if image.source == "FILE" and not image.packed_file:
124                         job.addFile(bpy.utils.expandpath(image.filename))
125         
126         ###########################
127         # FLUID + POINT CACHE
128         ###########################
129         root, ext = os.path.splitext(name)
130         default_path = path + "blendcache_" + root + os.sep # need an API call for that
131
132         for object in bpy.data.objects:
133                 for modifier in object.modifiers:
134                         if modifier.type == 'FLUID_SIMULATION' and modifier.settings.type == "DOMAIN":
135                                 addFluidFiles(job, bpy.utils.expandpath(modifier.settings.path))
136                         elif modifier.type == "CLOTH":
137                                 addPointCache(job, object, modifier.point_cache, default_path)
138                         elif modifier.type == "SOFT_BODY":
139                                 addPointCache(job, object, modifier.point_cache, default_path)
140                         elif modifier.type == "SMOKE" and modifier.smoke_type == "TYPE_DOMAIN":
141                                 addPointCache(job, object, modifier.domain_settings.point_cache_low, default_path)
142                                 if modifier.domain_settings.highres:
143                                         addPointCache(job, object, modifier.domain_settings.point_cache_high, default_path)
144
145                 # particles modifier are stupid and don't contain data
146                 # we have to go through the object property
147                 for psys in object.particle_systems:
148                         addPointCache(job, object, psys.point_cache, default_path)
149         
150         # print(job.files)
151         
152         job.name = job_name
153         
154         for slave in netrender.blacklist:
155                 job.blacklist.append(slave.id)
156         
157         job.chunks = netsettings.chunks
158         job.priority = netsettings.priority
159         
160         # try to send path first
161         conn.request("POST", "/job", repr(job.serialize()))
162         response = conn.getresponse()
163         
164         job_id = response.getheader("job-id")
165         
166         # if not ACCEPTED (but not processed), send files
167         if response.status == http.client.ACCEPTED:
168                 for filepath, start, end in job.files:
169                         f = open(filepath, "rb")
170                         conn.request("PUT", "/file", f, headers={"job-id": job_id, "job-file": filepath})
171                         f.close()
172                         response = conn.getresponse()
173         
174         # server will reply with NOT_FOUD until all files are found
175         
176         return job_id
177
178 def requestResult(conn, job_id, frame):
179         conn.request("GET", "/render", headers={"job-id": job_id, "job-frame":str(frame)})
180
181 @rnaType
182 class NetworkRenderEngine(bpy.types.RenderEngine):
183         bl_idname = 'NET_RENDER'
184         bl_label = "Network Render"
185         def render(self, scene):
186                 if scene.network_render.mode == "RENDER_CLIENT":
187                         self.render_client(scene)
188                 elif scene.network_render.mode == "RENDER_SLAVE":
189                         self.render_slave(scene)
190                 elif scene.network_render.mode == "RENDER_MASTER":
191                         self.render_master(scene)
192                 else:
193                         print("UNKNOWN OPERATION MODE")
194         
195         def render_master(self, scene):
196                 netsettings = scene.network_render
197                 
198                 address = "" if netsettings.server_address == "[default]" else netsettings.server_address
199                 
200                 master.runMaster((address, netsettings.server_port), netsettings.server_broadcast, netsettings.path, self.update_stats, self.test_break)
201
202
203         def render_slave(self, scene):
204                 slave.render_slave(self, scene.network_render)
205         
206         def render_client(self, scene):
207                 netsettings = scene.network_render
208                 self.update_stats("", "Network render client initiation")
209                 
210                 
211                 conn = clientConnection(netsettings.server_address, netsettings.server_port)
212                 
213                 if conn:
214                         # Sending file
215                         
216                         self.update_stats("", "Network render exporting")
217                         
218                         job_id = netsettings.job_id
219                         
220                         # reading back result
221                         
222                         self.update_stats("", "Network render waiting for results")
223                         
224                         requestResult(conn, job_id, scene.current_frame)
225                         response = conn.getresponse()
226                         
227                         if response.status == http.client.NO_CONTENT:
228                                 netsettings.job_id = clientSendJob(conn, scene)
229                                 requestResult(conn, job_id, scene.current_frame)
230                         
231                         while response.status == http.client.ACCEPTED and not self.test_break():
232                                 time.sleep(1)
233                                 requestResult(conn, job_id, scene.current_frame)
234                                 response = conn.getresponse()
235         
236                         if response.status != http.client.OK:
237                                 conn.close()
238                                 return
239                         
240                         r = scene.render_data
241                         x= int(r.resolution_x*r.resolution_percentage*0.01)
242                         y= int(r.resolution_y*r.resolution_percentage*0.01)
243                         
244                         f = open(netsettings.path + "output.exr", "wb")
245                         buf = response.read(1024)
246                         
247                         while buf:
248                                 f.write(buf)
249                                 buf = response.read(1024)
250                         
251                         f.close()
252                         
253                         result = self.begin_result(0, 0, x, y)
254                         result.load_from_file(netsettings.path + "output.exr", 0, 0)
255                         self.end_result(result)
256                         
257                         conn.close()
258
259 def compatible(module):
260         module = __import__(module)
261         for subclass in module.__dict__.values():
262                 try:            subclass.COMPAT_ENGINES.add('NET_RENDER')
263                 except: pass
264         del module
265
266 compatible("properties_render")
267 compatible("properties_world")
268 compatible("properties_material")