renamed bpy.sys to bpy.utils, since it used to be a attempt to replace pythons sys...
[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.model
25 import netrender.slave as slave
26 import netrender.master as master
27 from netrender.utils import *
28
29 def addFluidFiles(job, path):
30         if os.path.exists(path):
31                 pattern = re.compile("fluidsurface_(final|preview)_([0-9]+)\.(bobj|bvel)\.gz")
32
33                 for fluid_file in sorted(os.listdir(path)):
34                         match = pattern.match(fluid_file)
35                         
36                         if match:
37                                 current_frame = int(match.groups()[1])
38                                 job.addFile(path + fluid_file, current_frame, current_frame)
39
40 def addPointCache(job, ob, point_cache, default_path):
41         if not point_cache.disk_cache:
42                 return
43         
44         
45         name = point_cache.name
46         if name == "":
47                 name = "".join(["%02X" % ord(c) for c in ob.name])
48         
49         cache_path = bpy.utils.expandpath(point_cache.filepath) if point_cache.external else default_path
50         
51         index = "%02i" % point_cache.index
52         
53         if os.path.exists(cache_path):
54                 pattern = re.compile(name + "_([0-9]+)_" + index + "\.bphys")
55                 
56                 cache_files = []
57
58                 for cache_file in sorted(os.listdir(cache_path)):
59                         match = pattern.match(cache_file)
60                         
61                         if match:
62                                 cache_frame = int(match.groups()[0])
63                                 cache_files.append((cache_frame, cache_file))
64                                 
65                 cache_files.sort()
66                 
67                 if len(cache_files) == 1:
68                         cache_frame, cache_file = cache_files[0]
69                         job.addFile(cache_path + cache_file, cache_frame, cache_frame)
70                 else:
71                         for i in range(len(cache_files)):
72                                 current_item = cache_files[i]
73                                 next_item = cache_files[i+1] if i + 1 < len(cache_files) else None
74                                 previous_item = cache_files[i - 1] if i > 0 else None
75                                 
76                                 current_frame, current_file = current_item
77                                 
78                                 if  not next_item and not previous_item:
79                                         job.addFile(cache_path + current_file, current_frame, current_frame)
80                                 elif next_item and not previous_item:
81                                         next_frame = next_item[0]
82                                         job.addFile(cache_path + current_file, current_frame, next_frame - 1)
83                                 elif not next_item and previous_item:
84                                         previous_frame = previous_item[0]
85                                         job.addFile(cache_path + current_file, previous_frame + 1, current_frame)
86                                 else:
87                                         next_frame = next_item[0]
88                                         previous_frame = previous_item[0]
89                                         job.addFile(cache_path + current_file, previous_frame + 1, next_frame - 1)
90                                                 
91 def clientSendJob(conn, scene, anim = False):
92         netsettings = scene.network_render
93         job = netrender.model.RenderJob()
94         
95         if anim:
96                 for f in range(scene.start_frame, scene.end_frame + 1):
97                         job.addFrame(f)
98         else:
99                 job.addFrame(scene.current_frame)
100         
101         filename = bpy.data.filename
102         job.addFile(filename)
103         
104         job_name = netsettings.job_name
105         path, name = os.path.split(filename)
106         path += os.sep
107         if job_name == "[default]":
108                 job_name = name
109         
110         ###########################
111         # LIBRARIES
112         ###########################
113         for lib in bpy.data.libraries:
114                 job.addFile(bpy.utils.expandpath(lib_path))
115                 
116         ###########################
117         # IMAGES
118         ###########################
119         for image in bpy.data.images:
120                 if image.source == "FILE" and not image.packed_file:
121                         job.addFile(bpy.utils.expandpath(image.filename))
122         
123         ###########################
124         # FLUID + POINT CACHE
125         ###########################
126         root, ext = os.path.splitext(name)
127         default_path = path + "blendcache_" + root + os.sep # need an API call for that
128
129         for object in bpy.data.objects:
130                 for modifier in object.modifiers:
131                         if modifier.type == 'FLUID_SIMULATION' and modifier.settings.type == "DOMAIN":
132                                 addFluidFiles(job, bpy.utils.expandpath(modifier.settings.path))
133                         elif modifier.type == "CLOTH":
134                                 addPointCache(job, object, modifier.point_cache, default_path)
135                         elif modifier.type == "SOFT_BODY":
136                                 addPointCache(job, object, modifier.point_cache, default_path)
137                         elif modifier.type == "SMOKE" and modifier.smoke_type == "TYPE_DOMAIN":
138                                 addPointCache(job, object, modifier.domain_settings.point_cache_low, default_path)
139                                 if modifier.domain_settings.highres:
140                                         addPointCache(job, object, modifier.domain_settings.point_cache_high, default_path)
141
142                 # particles modifier are stupid and don't contain data
143                 # we have to go through the object property
144                 for psys in object.particle_systems:
145                         addPointCache(job, object, psys.point_cache, default_path)
146         
147         # print(job.files)
148         
149         job.name = job_name
150         
151         for slave in scene.network_render.slaves_blacklist:
152                 job.blacklist.append(slave.id)
153         
154         job.chunks = netsettings.chunks
155         job.priority = netsettings.priority
156         
157         # try to send path first
158         conn.request("POST", "/job", repr(job.serialize()))
159         response = conn.getresponse()
160         
161         job_id = response.getheader("job-id")
162         
163         # if not ACCEPTED (but not processed), send files
164         if response.status == http.client.ACCEPTED:
165                 for filepath, start, end in job.files:
166                         f = open(filepath, "rb")
167                         conn.request("PUT", "/file", f, headers={"job-id": job_id, "job-file": filepath})
168                         f.close()
169                         response = conn.getresponse()
170         
171         # server will reply with NOT_FOUD until all files are found
172         
173         return job_id
174
175 def requestResult(conn, job_id, frame):
176         conn.request("GET", "/render", headers={"job-id": job_id, "job-frame":str(frame)})
177
178 @rnaType
179 class NetworkRenderEngine(bpy.types.RenderEngine):
180         bl_idname = 'NET_RENDER'
181         bl_label = "Network Render"
182         def render(self, scene):
183                 if scene.network_render.mode == "RENDER_CLIENT":
184                         self.render_client(scene)
185                 elif scene.network_render.mode == "RENDER_SLAVE":
186                         self.render_slave(scene)
187                 elif scene.network_render.mode == "RENDER_MASTER":
188                         self.render_master(scene)
189                 else:
190                         print("UNKNOWN OPERATION MODE")
191         
192         def render_master(self, scene):
193                 netsettings = scene.network_render
194                 
195                 address = "" if netsettings.server_address == "[default]" else netsettings.server_address
196                 
197                 master.runMaster((address, netsettings.server_port), netsettings.server_broadcast, netsettings.path, self.update_stats, self.test_break)
198
199
200         def render_slave(self, scene):
201                 slave.render_slave(self, scene)
202         
203         def render_client(self, scene):
204                 netsettings = scene.network_render
205                 self.update_stats("", "Network render client initiation")
206                 
207                 
208                 conn = clientConnection(scene)
209                 
210                 if conn:
211                         # Sending file
212                         
213                         self.update_stats("", "Network render exporting")
214                         
215                         job_id = netsettings.job_id
216                         
217                         # reading back result
218                         
219                         self.update_stats("", "Network render waiting for results")
220                         
221                         requestResult(conn, job_id, scene.current_frame)
222                         response = conn.getresponse()
223                         
224                         if response.status == http.client.NO_CONTENT:
225                                 netsettings.job_id = clientSendJob(conn, scene)
226                                 requestResult(conn, job_id, scene.current_frame)
227                         
228                         while response.status == http.client.ACCEPTED and not self.test_break():
229                                 time.sleep(1)
230                                 requestResult(conn, job_id, scene.current_frame)
231                                 response = conn.getresponse()
232         
233                         if response.status != http.client.OK:
234                                 conn.close()
235                                 return
236                         
237                         r = scene.render_data
238                         x= int(r.resolution_x*r.resolution_percentage*0.01)
239                         y= int(r.resolution_y*r.resolution_percentage*0.01)
240                         
241                         f = open(netsettings.path + "output.exr", "wb")
242                         buf = response.read(1024)
243                         
244                         while buf:
245                                 f.write(buf)
246                                 buf = response.read(1024)
247                         
248                         f.close()
249                         
250                         result = self.begin_result(0, 0, x, y)
251                         result.load_from_file(netsettings.path + "output.exr", 0, 0)
252                         self.end_result(result)
253                         
254                         conn.close()
255