rna naming, *_frame --> frame_*
[blender-staging.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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 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.frame_start, scene.frame_end + 1):
100             job.addFrame(f)
101     else:
102         job.addFrame(scene.frame_current)
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     if job_name == "[default]":
110         job_name = name
111
112     ###########################
113     # LIBRARIES
114     ###########################
115     for lib in bpy.data.libraries:
116         file_path = bpy.utils.expandpath(lib.filename)
117         if os.path.exists(file_path):
118             job.addFile(file_path)
119
120     ###########################
121     # IMAGES
122     ###########################
123     for image in bpy.data.images:
124         if image.source == "FILE" and not image.packed_file:
125             file_path = bpy.utils.expandpath(image.filename)
126             if os.path.exists(file_path):
127                 job.addFile(file_path)
128
129     ###########################
130     # FLUID + POINT CACHE
131     ###########################
132     root, ext = os.path.splitext(name)
133     default_path = path + os.sep + "blendcache_" + root + os.sep # need an API call for that
134
135     for object in bpy.data.objects:
136         for modifier in object.modifiers:
137             if modifier.type == 'FLUID_SIMULATION' and modifier.settings.type == "DOMAIN":
138                 addFluidFiles(job, bpy.utils.expandpath(modifier.settings.path))
139             elif modifier.type == "CLOTH":
140                 addPointCache(job, object, modifier.point_cache, default_path)
141             elif modifier.type == "SOFT_BODY":
142                 addPointCache(job, object, modifier.point_cache, default_path)
143             elif modifier.type == "SMOKE" and modifier.smoke_type == "TYPE_DOMAIN":
144                 addPointCache(job, object, modifier.domain_settings.point_cache_low, default_path)
145                 if modifier.domain_settings.highres:
146                     addPointCache(job, object, modifier.domain_settings.point_cache_high, default_path)
147
148         # particles modifier are stupid and don't contain data
149         # we have to go through the object property
150         for psys in object.particle_systems:
151             addPointCache(job, object, psys.point_cache, default_path)
152
153     #print(job.files)
154
155     job.name = job_name
156     job.category = netsettings.job_category
157
158     for slave in netrender.blacklist:
159         job.blacklist.append(slave.id)
160
161     job.chunks = netsettings.chunks
162     job.priority = netsettings.priority
163
164     # try to send path first
165     conn.request("POST", "/job", repr(job.serialize()))
166     response = conn.getresponse()
167
168     job_id = response.getheader("job-id")
169
170     # if not ACCEPTED (but not processed), send files
171     if response.status == http.client.ACCEPTED:
172         for rfile in job.files:
173             f = open(rfile.filepath, "rb")
174             conn.request("PUT", fileURL(job_id, rfile.index), f)
175             f.close()
176             response = conn.getresponse()
177
178     # server will reply with ACCEPTED until all files are found
179
180     return job_id
181
182 def requestResult(conn, job_id, frame):
183     conn.request("GET", renderURL(job_id, frame))
184
185 @rnaType
186 class NetworkRenderEngine(bpy.types.RenderEngine):
187     bl_idname = 'NET_RENDER'
188     bl_label = "Network Render"
189     bl_postprocess = False
190     def render(self, scene):
191         if scene.network_render.mode == "RENDER_CLIENT":
192             self.render_client(scene)
193         elif scene.network_render.mode == "RENDER_SLAVE":
194             self.render_slave(scene)
195         elif scene.network_render.mode == "RENDER_MASTER":
196             self.render_master(scene)
197         else:
198             print("UNKNOWN OPERATION MODE")
199
200     def render_master(self, scene):
201         netsettings = scene.network_render
202
203         address = "" if netsettings.server_address == "[default]" else netsettings.server_address
204
205         master.runMaster((address, netsettings.server_port), netsettings.master_broadcast, netsettings.master_clear, netsettings.path, self.update_stats, self.test_break)
206
207
208     def render_slave(self, scene):
209         slave.render_slave(self, scene.network_render, scene.render.threads)
210
211     def render_client(self, scene):
212         netsettings = scene.network_render
213         self.update_stats("", "Network render client initiation")
214
215
216         conn = clientConnection(netsettings.server_address, netsettings.server_port)
217
218         if conn:
219             # Sending file
220
221             self.update_stats("", "Network render exporting")
222
223             new_job = False
224
225             job_id = netsettings.job_id
226
227             # reading back result
228
229             self.update_stats("", "Network render waiting for results")
230
231             requestResult(conn, job_id, scene.frame_current)
232             response = conn.getresponse()
233
234             if response.status == http.client.NO_CONTENT:
235                 new_job = True
236                 netsettings.job_id = clientSendJob(conn, scene)
237                 job_id = netsettings.job_id
238
239                 requestResult(conn, job_id, scene.frame_current)
240                 response = conn.getresponse()
241
242             while response.status == http.client.ACCEPTED and not self.test_break():
243                 time.sleep(1)
244                 requestResult(conn, job_id, scene.frame_current)
245                 response = conn.getresponse()
246
247             # cancel new jobs (animate on network) on break
248             if self.test_break() and new_job:
249                 conn.request("POST", cancelURL(job_id))
250                 response = conn.getresponse()
251                 print( response.status, response.reason )
252                 netsettings.job_id = 0
253
254             if response.status != http.client.OK:
255                 conn.close()
256                 return
257
258             r = scene.render
259             x= int(r.resolution_x*r.resolution_percentage*0.01)
260             y= int(r.resolution_y*r.resolution_percentage*0.01)
261
262             f = open(netsettings.path + "output.exr", "wb")
263             buf = response.read(1024)
264
265             while buf:
266                 f.write(buf)
267                 buf = response.read(1024)
268
269             f.close()
270
271             result = self.begin_result(0, 0, x, y)
272             result.load_from_file(netsettings.path + "output.exr")
273             self.end_result(result)
274
275             conn.close()
276
277 def compatible(module):
278     module = __import__(module)
279     for subclass in module.__dict__.values():
280         try:            subclass.COMPAT_ENGINES.add('NET_RENDER')
281         except: pass
282     del module
283
284 #compatible("properties_render")
285 compatible("properties_world")
286 compatible("properties_material")