Merge with trunk, revision 28528 - 28976.
[blender-staging.git] / release / scripts / io / engine_render_pov.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 # <pep8 compliant>
20
21 import bpy
22
23 from math import atan, pi, degrees
24 import subprocess
25 import os
26 import sys
27 import time
28
29 import platform as pltfrm
30
31 if pltfrm.architecture()[0] == '64bit':
32     bitness = 64
33 else:
34     bitness = 32
35
36
37 def write_pov(filename, scene=None, info_callback=None):
38     file = open(filename, 'w')
39
40     # Only for testing
41     if not scene:
42         scene = bpy.data.scenes[0]
43
44     render = scene.render
45     world = scene.world
46
47     def uniqueName(name, nameSeq):
48
49         if name not in nameSeq:
50             return name
51
52         name_orig = name
53         i = 1
54         while name in nameSeq:
55             name = '%s_%.3d' % (name_orig, i)
56             i += 1
57
58         return name
59
60     def writeMatrix(matrix):
61         file.write('\tmatrix <%.6f, %.6f, %.6f,  %.6f, %.6f, %.6f,  %.6f, %.6f, %.6f,  %.6f, %.6f, %.6f>\n' %\
62         (matrix[0][0], matrix[0][1], matrix[0][2], matrix[1][0], matrix[1][1], matrix[1][2], matrix[2][0], matrix[2][1], matrix[2][2], matrix[3][0], matrix[3][1], matrix[3][2]))
63
64     def writeObjectMaterial(material):
65         if material and material.transparency_method == 'RAYTRACE':
66             file.write('\tinterior { ior %.6f }\n' % material.raytrace_transparency.ior)
67
68             # Other interior args
69             # fade_distance 2
70             # fade_power [Value]
71             # fade_color
72
73             # dispersion
74             # dispersion_samples
75
76     materialNames = {}
77     DEF_MAT_NAME = 'Default'
78
79     def writeMaterial(material):
80         # Assumes only called once on each material
81
82         if material:
83             name_orig = material.name
84         else:
85             name_orig = DEF_MAT_NAME
86
87         name = materialNames[name_orig] = uniqueName(bpy.utils.clean_name(name_orig), materialNames)
88
89         file.write('#declare %s = finish {\n' % name)
90
91         if material:
92             file.write('\tdiffuse %.3g\n' % material.diffuse_intensity)
93             file.write('\tspecular %.3g\n' % material.specular_intensity)
94
95             file.write('\tambient %.3g\n' % material.ambient)
96             #file.write('\tambient rgb <%.3g, %.3g, %.3g>\n' % tuple([c*material.ambient for c in world.ambient_color])) # povray blends the global value
97
98             # map hardness between 0.0 and 1.0
99             roughness = ((1.0 - ((material.specular_hardness - 1.0) / 510.0)))
100             # scale from 0.0 to 0.1
101             roughness *= 0.1
102             # add a small value because 0.0 is invalid
103             roughness += (1 / 511.0)
104
105             file.write('\troughness %.3g\n' % roughness)
106
107             # 'phong 70.0 '
108
109             if material.raytrace_mirror.enabled:
110                 raytrace_mirror = material.raytrace_mirror
111                 if raytrace_mirror.reflect_factor:
112                     file.write('\treflection {\n')
113                     file.write('\t\trgb <%.3g, %.3g, %.3g>' % tuple(material.mirror_color))
114                     file.write('\t\tfresnel 1 falloff %.3g exponent %.3g metallic %.3g} ' % (raytrace_mirror.fresnel, raytrace_mirror.fresnel_factor, raytrace_mirror.reflect_factor))
115
116         else:
117             file.write('\tdiffuse 0.8\n')
118             file.write('\tspecular 0.2\n')
119
120
121         # This is written into the object
122         '''
123         if material and material.transparency_method=='RAYTRACE':
124             'interior { ior %.3g} ' % material.raytrace_transparency.ior
125         '''
126
127         #file.write('\t\t\tcrand 1.0\n') # Sand granyness
128         #file.write('\t\t\tmetallic %.6f\n' % material.spec)
129         #file.write('\t\t\tphong %.6f\n' % material.spec)
130         #file.write('\t\t\tphong_size %.6f\n' % material.spec)
131         #file.write('\t\t\tbrilliance %.6f ' % (material.specular_hardness/256.0) # Like hardness
132
133         file.write('}\n')
134
135     def exportCamera():
136         camera = scene.camera
137         matrix = camera.matrix
138
139         # compute resolution
140         Qsize = float(render.resolution_x) / float(render.resolution_y)
141
142         file.write('camera {\n')
143         file.write('\tlocation  <0, 0, 0>\n')
144         file.write('\tlook_at  <0, 0, -1>\n')
145         file.write('\tright <%s, 0, 0>\n' % - Qsize)
146         file.write('\tup <0, 1, 0>\n')
147         file.write('\tangle  %f \n' % (360.0 * atan(16.0 / camera.data.lens) / pi))
148
149         file.write('\trotate  <%.6f, %.6f, %.6f>\n' % tuple([degrees(e) for e in matrix.rotation_part().to_euler()]))
150         file.write('\ttranslate <%.6f, %.6f, %.6f>\n' % (matrix[3][0], matrix[3][1], matrix[3][2]))
151         file.write('}\n')
152
153     def exportLamps(lamps):
154         # Get all lamps
155         for ob in lamps:
156             lamp = ob.data
157
158             matrix = ob.matrix
159
160             color = tuple([c * lamp.energy for c in lamp.color]) # Colour is modified by energy
161
162             file.write('light_source {\n')
163             file.write('\t< 0,0,0 >\n')
164             file.write('\tcolor rgb<%.3g, %.3g, %.3g>\n' % color)
165
166             if lamp.type == 'POINT': # Point Lamp
167                 pass
168             elif lamp.type == 'SPOT': # Spot
169                 file.write('\tspotlight\n')
170
171                 # Falloff is the main radius from the centre line
172                 file.write('\tfalloff %.2f\n' % (degrees(lamp.spot_size) / 2.0)) # 1 TO 179 FOR BOTH
173                 file.write('\tradius %.6f\n' % ((degrees(lamp.spot_size) / 2.0) * (1.0 - lamp.spot_blend)))
174
175                 # Blender does not have a tightness equivilent, 0 is most like blender default.
176                 file.write('\ttightness 0\n') # 0:10f
177
178                 file.write('\tpoint_at  <0, 0, -1>\n')
179             elif lamp.type == 'SUN':
180                 file.write('\tparallel\n')
181                 file.write('\tpoint_at  <0, 0, -1>\n') # *must* be after 'parallel'
182
183             elif lamp.type == 'AREA':
184
185                 size_x = lamp.size
186                 samples_x = lamp.shadow_ray_samples_x
187                 if lamp.shape == 'SQUARE':
188                     size_y = size_x
189                     samples_y = samples_x
190                 else:
191                     size_y = lamp.size_y
192                     samples_y = lamp.shadow_ray_samples_y
193
194                 file.write('\tarea_light <%d,0,0>,<0,0,%d> %d, %d\n' % (size_x, size_y, samples_x, samples_y))
195                 if lamp.shadow_ray_sampling_method == 'CONSTANT_JITTERED':
196                     if lamp.jitter:
197                         file.write('\tjitter\n')
198                 else:
199                     file.write('\tadaptive 1\n')
200                     file.write('\tjitter\n')
201
202             if lamp.shadow_method == 'NOSHADOW':
203                 file.write('\tshadowless\n')
204
205             file.write('\tfade_distance %.6f\n' % lamp.distance)
206             file.write('\tfade_power %d\n' % 1) # Could use blenders lamp quad?
207             writeMatrix(matrix)
208
209             file.write('}\n')
210
211     def exportMeta(metas):
212
213         # TODO - blenders 'motherball' naming is not supported.
214
215         for ob in metas:
216             meta = ob.data
217
218             file.write('blob {\n')
219             file.write('\t\tthreshold %.4g\n' % meta.threshold)
220
221             try:
222                 material = meta.materials[0] # lame! - blender cant do enything else.
223             except:
224                 material = None
225
226             for elem in meta.elements:
227
228                 if elem.type not in ('BALL', 'ELLIPSOID'):
229                     continue # Not supported
230
231                 loc = elem.location
232
233                 stiffness = elem.stiffness
234                 if elem.negative:
235                     stiffness = - stiffness
236
237                 if elem.type == 'BALL':
238
239                     file.write('\tsphere { <%.6g, %.6g, %.6g>, %.4g, %.4g ' % (loc.x, loc.y, loc.z, elem.radius, stiffness))
240
241                     # After this wecould do something simple like...
242                     #   "pigment {Blue} }"
243                     # except we'll write the color
244
245                 elif elem.type == 'ELLIPSOID':
246                     # location is modified by scale
247                     file.write('\tsphere { <%.6g, %.6g, %.6g>, %.4g, %.4g ' % (loc.x / elem.size_x, loc.y / elem.size_y, loc.z / elem.size_z, elem.radius, stiffness))
248                     file.write('scale <%.6g, %.6g, %.6g> ' % (elem.size_x, elem.size_y, elem.size_z))
249
250                 if material:
251                     diffuse_color = material.diffuse_color
252
253                     if material.transparency and material.transparency_method == 'RAYTRACE':
254                         trans = 1.0 - material.raytrace_transparency.filter
255                     else:
256                         trans = 0.0
257
258                     file.write('pigment {rgbft<%.3g, %.3g, %.3g, %.3g, %.3g>} finish {%s} }\n' % \
259                         (diffuse_color[0], diffuse_color[1], diffuse_color[2], 1.0 - material.alpha, trans, materialNames[material.name]))
260
261                 else:
262                     file.write('pigment {rgb<1 1 1>} finish {%s} }\n' % DEF_MAT_NAME)           # Write the finish last.
263
264             writeObjectMaterial(material)
265
266             writeMatrix(ob.matrix)
267
268             file.write('}\n')
269
270     def exportMeshs(scene, sel):
271
272         ob_num = 0
273
274         for ob in sel:
275             ob_num += 1
276
277             if ob.type in ('LAMP', 'CAMERA', 'EMPTY', 'META', 'ARMATURE'):
278                 continue
279
280             me = ob.data
281             me_materials = me.materials
282
283             me = ob.create_mesh(scene, True, 'RENDER')
284
285             if not me:
286                 continue
287
288             if info_callback:
289                 info_callback('Object %2.d of %2.d (%s)' % (ob_num, len(sel), ob.name))
290
291             #if ob.type!='MESH':
292             #   continue
293             # me = ob.data
294
295             matrix = ob.matrix
296             try:
297                 uv_layer = me.active_uv_texture.data
298             except:
299                 uv_layer = None
300
301             try:
302                 vcol_layer = me.active_vertex_color.data
303             except:
304                 vcol_layer = None
305
306             faces_verts = [f.verts for f in me.faces]
307             faces_normals = [tuple(f.normal) for f in me.faces]
308             verts_normals = [tuple(v.normal) for v in me.verts]
309
310             # quads incur an extra face
311             quadCount = len([f for f in faces_verts if len(f) == 4])
312
313             file.write('mesh2 {\n')
314             file.write('\tvertex_vectors {\n')
315             file.write('\t\t%s' % (len(me.verts))) # vert count
316             for v in me.verts:
317                 file.write(',\n\t\t<%.6f, %.6f, %.6f>' % tuple(v.co)) # vert count
318             file.write('\n  }\n')
319
320
321             # Build unique Normal list
322             uniqueNormals = {}
323             for fi, f in enumerate(me.faces):
324                 fv = faces_verts[fi]
325                 # [-1] is a dummy index, use a list so we can modify in place
326                 if f.smooth: # Use vertex normals
327                     for v in fv:
328                         key = verts_normals[v]
329                         uniqueNormals[key] = [-1]
330                 else: # Use face normal
331                     key = faces_normals[fi]
332                     uniqueNormals[key] = [-1]
333
334             file.write('\tnormal_vectors {\n')
335             file.write('\t\t%d' % len(uniqueNormals)) # vert count
336             idx = 0
337             for no, index in uniqueNormals.items():
338                 file.write(',\n\t\t<%.6f, %.6f, %.6f>' % no) # vert count
339                 index[0] = idx
340                 idx += 1
341             file.write('\n  }\n')
342
343
344             # Vertex colours
345             vertCols = {} # Use for material colours also.
346
347             if uv_layer:
348                 # Generate unique UV's
349                 uniqueUVs = {}
350
351                 for fi, uv in enumerate(uv_layer):
352
353                     if len(faces_verts[fi]) == 4:
354                         uvs = uv.uv1, uv.uv2, uv.uv3, uv.uv4
355                     else:
356                         uvs = uv.uv1, uv.uv2, uv.uv3
357
358                     for uv in uvs:
359                         uniqueUVs[tuple(uv)] = [-1]
360
361                 file.write('\tuv_vectors {\n')
362                 #print unique_uvs
363                 file.write('\t\t%s' % (len(uniqueUVs))) # vert count
364                 idx = 0
365                 for uv, index in uniqueUVs.items():
366                     file.write(',\n\t\t<%.6f, %.6f>' % uv)
367                     index[0] = idx
368                     idx += 1
369                 '''
370                 else:
371                     # Just add 1 dummy vector, no real UV's
372                     file.write('\t\t1') # vert count
373                     file.write(',\n\t\t<0.0, 0.0>')
374                 '''
375                 file.write('\n  }\n')
376
377
378             if me.vertex_colors:
379
380                 for fi, f in enumerate(me.faces):
381                     material_index = f.material_index
382                     material = me_materials[material_index]
383
384                     if material and material.vertex_color_paint:
385
386                         col = vcol_layer[fi]
387
388                         if len(faces_verts[fi]) == 4:
389                             cols = col.color1, col.color2, col.color3, col.color4
390                         else:
391                             cols = col.color1, col.color2, col.color3
392
393                         for col in cols:
394                             key = col[0], col[1], col[2], material_index # Material index!
395                             vertCols[key] = [-1]
396
397                     else:
398                         if material:
399                             diffuse_color = tuple(material.diffuse_color)
400                             key = diffuse_color[0], diffuse_color[1], diffuse_color[2], material_index
401                             vertCols[key] = [-1]
402
403
404             else:
405                 # No vertex colours, so write material colours as vertex colours
406                 for i, material in enumerate(me_materials):
407
408                     if material:
409                         diffuse_color = tuple(material.diffuse_color)
410                         key = diffuse_color[0], diffuse_color[1], diffuse_color[2], i # i == f.mat
411                         vertCols[key] = [-1]
412
413
414             # Vert Colours
415             file.write('\ttexture_list {\n')
416             file.write('\t\t%s' % (len(vertCols))) # vert count
417             idx = 0
418             for col, index in vertCols.items():
419
420                 if me_materials:
421                     material = me_materials[col[3]]
422                     material_finish = materialNames[material.name]
423
424                     if material.transparency and material.transparency_method == 'RAYTRACE':
425                         trans = 1.0 - material.raytrace_transparency.filter
426                     else:
427                         trans = 0.0
428
429                 else:
430                     material_finish = DEF_MAT_NAME # not working properly,
431                     trans = 0.0
432
433                 #print material.apl
434                 file.write(',\n\t\ttexture { pigment {rgbft<%.3g, %.3g, %.3g, %.3g, %.3g>} finish {%s}}' %
435                             (col[0], col[1], col[2], 1.0 - material.alpha, trans, material_finish))
436
437                 index[0] = idx
438                 idx += 1
439
440             file.write('\n  }\n')
441
442             # Face indicies
443             file.write('\tface_indices {\n')
444             file.write('\t\t%d' % (len(me.faces) + quadCount)) # faces count
445             for fi, f in enumerate(me.faces):
446                 fv = faces_verts[fi]
447                 material_index = f.material_index
448                 if len(fv) == 4:
449                     indicies = (0, 1, 2), (0, 2, 3)
450                 else:
451                     indicies = ((0, 1, 2),)
452
453                 if vcol_layer:
454                     col = vcol_layer[fi]
455
456                     if len(fv) == 4:
457                         cols = col.color1, col.color2, col.color3, col.color4
458                     else:
459                         cols = col.color1, col.color2, col.color3
460
461
462                 if not me_materials or me_materials[material_index] == None: # No materials
463                     for i1, i2, i3 in indicies:
464                         file.write(',\n\t\t<%d,%d,%d>' % (fv[i1], fv[i2], fv[i3])) # vert count
465                 else:
466                     material = me_materials[material_index]
467                     for i1, i2, i3 in indicies:
468                         if me.vertex_colors and material.vertex_color_paint:
469                             # Colour per vertex - vertex colour
470
471                             col1 = cols[i1]
472                             col2 = cols[i2]
473                             col3 = cols[i3]
474
475                             ci1 = vertCols[col1[0], col1[1], col1[2], material_index][0]
476                             ci2 = vertCols[col2[0], col2[1], col2[2], material_index][0]
477                             ci3 = vertCols[col3[0], col3[1], col3[2], material_index][0]
478                         else:
479                             # Colour per material - flat material colour
480                             diffuse_color = material.diffuse_color
481                             ci1 = ci2 = ci3 = vertCols[diffuse_color[0], diffuse_color[1], diffuse_color[2], f.material_index][0]
482
483                         file.write(',\n\t\t<%d,%d,%d>, %d,%d,%d' % (fv[i1], fv[i2], fv[i3], ci1, ci2, ci3)) # vert count
484
485
486             file.write('\n  }\n')
487
488             # normal_indices indicies
489             file.write('\tnormal_indices {\n')
490             file.write('\t\t%d' % (len(me.faces) + quadCount)) # faces count
491             for fi, fv in enumerate(faces_verts):
492
493                 if len(fv) == 4:
494                     indicies = (0, 1, 2), (0, 2, 3)
495                 else:
496                     indicies = ((0, 1, 2),)
497
498                 for i1, i2, i3 in indicies:
499                     if f.smooth:
500                         file.write(',\n\t\t<%d,%d,%d>' %\
501                         (uniqueNormals[verts_normals[fv[i1]]][0],\
502                          uniqueNormals[verts_normals[fv[i2]]][0],\
503                          uniqueNormals[verts_normals[fv[i3]]][0])) # vert count
504                     else:
505                         idx = uniqueNormals[faces_normals[fi]][0]
506                         file.write(',\n\t\t<%d,%d,%d>' % (idx, idx, idx)) # vert count
507
508             file.write('\n  }\n')
509
510             if uv_layer:
511                 file.write('\tuv_indices {\n')
512                 file.write('\t\t%d' % (len(me.faces) + quadCount)) # faces count
513                 for fi, fv in enumerate(faces_verts):
514
515                     if len(fv) == 4:
516                         indicies = (0, 1, 2), (0, 2, 3)
517                     else:
518                         indicies = ((0, 1, 2),)
519
520                     uv = uv_layer[fi]
521                     if len(faces_verts[fi]) == 4:
522                         uvs = tuple(uv.uv1), tuple(uv.uv2), tuple(uv.uv3), tuple(uv.uv4)
523                     else:
524                         uvs = tuple(uv.uv1), tuple(uv.uv2), tuple(uv.uv3)
525
526                     for i1, i2, i3 in indicies:
527                         file.write(',\n\t\t<%d,%d,%d>' %\
528                         (uniqueUVs[uvs[i1]][0],\
529                          uniqueUVs[uvs[i2]][0],\
530                          uniqueUVs[uvs[i2]][0])) # vert count
531                 file.write('\n  }\n')
532
533             if me.materials:
534                 material = me.materials[0] # dodgy
535                 writeObjectMaterial(material)
536
537             writeMatrix(matrix)
538             file.write('}\n')
539
540             bpy.data.meshes.remove(me)
541
542     def exportWorld(world):
543         if not world:
544             return
545
546         mist = world.mist
547
548         if mist.use_mist:
549             file.write('fog {\n')
550             file.write('\tdistance %.6f\n' % mist.depth)
551             file.write('\tcolor rgbt<%.3g, %.3g, %.3g, %.3g>\n' % (tuple(world.horizon_color) + (1 - mist.intensity,)))
552             #file.write('\tfog_offset %.6f\n' % mist.start)
553             #file.write('\tfog_alt 5\n')
554             #file.write('\tturbulence 0.2\n')
555             #file.write('\tturb_depth 0.3\n')
556             file.write('\tfog_type 1\n')
557             file.write('}\n')
558
559     def exportGlobalSettings(scene):
560
561         file.write('global_settings {\n')
562
563         if scene.pov_radio_enable:
564             file.write('\tradiosity {\n')
565             file.write("\t\tadc_bailout %.4g\n" % scene.pov_radio_adc_bailout)
566             file.write("\t\talways_sample %d\n" % scene.pov_radio_always_sample)
567             file.write("\t\tbrightness %.4g\n" % scene.pov_radio_brightness)
568             file.write("\t\tcount %d\n" % scene.pov_radio_count)
569             file.write("\t\terror_bound %.4g\n" % scene.pov_radio_error_bound)
570             file.write("\t\tgray_threshold %.4g\n" % scene.pov_radio_gray_threshold)
571             file.write("\t\tlow_error_factor %.4g\n" % scene.pov_radio_low_error_factor)
572             file.write("\t\tmedia %d\n" % scene.pov_radio_media)
573             file.write("\t\tminimum_reuse %.4g\n" % scene.pov_radio_minimum_reuse)
574             file.write("\t\tnearest_count %d\n" % scene.pov_radio_nearest_count)
575             file.write("\t\tnormal %d\n" % scene.pov_radio_normal)
576             file.write("\t\trecursion_limit %d\n" % scene.pov_radio_recursion_limit)
577             file.write('\t}\n')
578
579         if world:
580             file.write("\tambient_light rgb<%.3g, %.3g, %.3g>\n" % tuple(world.ambient_color))
581
582         file.write('}\n')
583
584
585     # Convert all materials to strings we can access directly per vertex.
586     writeMaterial(None) # default material
587
588     for material in bpy.data.materials:
589         writeMaterial(material)
590
591     exportCamera()
592     #exportMaterials()
593     sel = scene.objects
594     exportLamps([l for l in sel if l.type == 'LAMP'])
595     exportMeta([l for l in sel if l.type == 'META'])
596     exportMeshs(scene, sel)
597     exportWorld(scene.world)
598     exportGlobalSettings(scene)
599
600     file.close()
601
602
603 def write_pov_ini(filename_ini, filename_pov, filename_image):
604     scene = bpy.data.scenes[0]
605     render = scene.render
606
607     x = int(render.resolution_x * render.resolution_percentage * 0.01)
608     y = int(render.resolution_y * render.resolution_percentage * 0.01)
609
610     file = open(filename_ini, 'w')
611
612     file.write('Input_File_Name="%s"\n' % filename_pov)
613     file.write('Output_File_Name="%s"\n' % filename_image)
614
615     file.write('Width=%d\n' % x)
616     file.write('Height=%d\n' % y)
617
618     # Needed for border render.
619     '''
620     file.write('Start_Column=%d\n' % part.x)
621     file.write('End_Column=%d\n' % (part.x+part.w))
622
623     file.write('Start_Row=%d\n' % (part.y))
624     file.write('End_Row=%d\n' % (part.y+part.h))
625     '''
626
627     file.write('Display=0\n')
628     file.write('Pause_When_Done=0\n')
629     file.write('Output_File_Type=T\n') # TGA, best progressive loading
630     file.write('Output_Alpha=1\n')
631
632     if render.render_antialiasing:
633         aa_mapping = {'5': 2, '8': 3, '11': 4, '16': 5} # method 1 assumed
634         file.write('Antialias=1\n')
635         file.write('Antialias_Depth=%d\n' % aa_mapping[render.antialiasing_samples])
636     else:
637         file.write('Antialias=0\n')
638
639     file.close()
640
641 # Radiosity panel, use in the scene for now.
642 FloatProperty = bpy.types.Scene.FloatProperty
643 IntProperty = bpy.types.Scene.IntProperty
644 BoolProperty = bpy.types.Scene.BoolProperty
645
646 # Not a real pov option, just to know if we should write
647 BoolProperty(attr="pov_radio_enable",
648                 name="Enable Radiosity",
649                 description="Enable povrays radiosity calculation",
650                 default=False)
651 BoolProperty(attr="pov_radio_display_advanced",
652                 name="Advanced Options",
653                 description="Show advanced options",
654                 default=False)
655
656 # Real pov options
657 FloatProperty(attr="pov_radio_adc_bailout",
658                 name="ADC Bailout",
659                 description="The adc_bailout for radiosity rays. Use adc_bailout = 0.01 / brightest_ambient_object for good results",
660                 min=0.0, max=1000.0, soft_min=0.0, soft_max=1.0, default=0.01)
661
662 BoolProperty(attr="pov_radio_always_sample",
663                 name="Always Sample",
664                 description="Only use the data from the pretrace step and not gather any new samples during the final radiosity pass",
665                 default=True)
666
667 FloatProperty(attr="pov_radio_brightness",
668                 name="Brightness",
669                 description="Amount objects are brightened before being returned upwards to the rest of the system",
670                 min=0.0, max=1000.0, soft_min=0.0, soft_max=10.0, default=1.0)
671
672 IntProperty(attr="pov_radio_count",
673                 name="Ray Count",
674                 description="Number of rays that are sent out whenever a new radiosity value has to be calculated",
675                 min=1, max=1600, default=35)
676
677 FloatProperty(attr="pov_radio_error_bound",
678                 name="Error Bound",
679                 description="One of the two main speed/quality tuning values, lower values are more accurate",
680                 min=0.0, max=1000.0, soft_min=0.1, soft_max=10.0, default=1.8)
681
682 FloatProperty(attr="pov_radio_gray_threshold",
683                 name="Gray Threshold",
684                 description="One of the two main speed/quality tuning values, lower values are more accurate",
685                 min=0.0, max=1.0, soft_min=0, soft_max=1, default=0.0)
686
687 FloatProperty(attr="pov_radio_low_error_factor",
688                 name="Low Error Factor",
689                 description="If you calculate just enough samples, but no more, you will get an image which has slightly blotchy lighting",
690                 min=0.0, max=1.0, soft_min=0.0, soft_max=1.0, default=0.5)
691
692 # max_sample - not available yet
693 BoolProperty(attr="pov_radio_media",
694                 name="Media",
695                 description="Radiosity estimation can be affected by media",
696                 default=False)
697
698 FloatProperty(attr="pov_radio_minimum_reuse",
699                 name="Minimum Reuse",
700                 description="Fraction of the screen width which sets the minimum radius of reuse for each sample point (At values higher than 2% expect errors)",
701                 min=0.0, max=1.0, soft_min=0.1, soft_max=0.1, default=0.015)
702
703 IntProperty(attr="pov_radio_nearest_count",
704                 name="Nearest Count",
705                 description="Number of old ambient values blended together to create a new interpolated value",
706                 min=1, max=20, default=5)
707
708 BoolProperty(attr="pov_radio_normal",
709                 name="Normals",
710                 description="Radiosity estimation can be affected by normals",
711                 default=False)
712
713 IntProperty(attr="pov_radio_recursion_limit",
714                 name="Recursion Limit",
715                 description="how many recursion levels are used to calculate the diffuse inter-reflection",
716                 min=1, max=20, default=3)
717
718
719 class PovrayRender(bpy.types.RenderEngine):
720     bl_idname = 'POVRAY_RENDER'
721     bl_label = "Povray"
722     DELAY = 0.02
723
724     def _export(self, scene):
725         import tempfile
726
727         self._temp_file_in = tempfile.mktemp(suffix='.pov')
728         self._temp_file_out = tempfile.mktemp(suffix='.tga')
729         self._temp_file_ini = tempfile.mktemp(suffix='.ini')
730         '''
731         self._temp_file_in = '/test.pov'
732         self._temp_file_out = '/test.tga'
733         self._temp_file_ini = '/test.ini'
734         '''
735
736         def info_callback(txt):
737             self.update_stats("", "POVRAY: " + txt)
738
739         write_pov(self._temp_file_in, scene, info_callback)
740
741     def _render(self):
742
743         try:
744             os.remove(self._temp_file_out) # so as not to load the old file
745         except:
746             pass
747
748         write_pov_ini(self._temp_file_ini, self._temp_file_in, self._temp_file_out)
749
750         print ("***-STARTING-***")
751
752         pov_binary = "povray"
753
754         if sys.platform == 'win32':
755             import winreg
756             regKey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, 'Software\\POV-Ray\\v3.6\\Windows')
757
758             if bitness == 64:
759                 pov_binary = winreg.QueryValueEx(regKey, 'Home')[0] + '\\bin\\pvengine64'
760             else:
761                 pov_binary = winreg.QueryValueEx(regKey, 'Home')[0] + '\\bin\\pvengine'
762
763         if 1:
764             # TODO, when povray isnt found this gives a cryptic error, would be nice to be able to detect if it exists
765             self._process = subprocess.Popen([pov_binary, self._temp_file_ini]) # stdout=subprocess.PIPE, stderr=subprocess.PIPE
766         else:
767             # This works too but means we have to wait until its done
768             os.system('%s %s' % (pov_binary, self._temp_file_ini))
769
770         print ("***-DONE-***")
771
772     def _cleanup(self):
773         for f in (self._temp_file_in, self._temp_file_ini, self._temp_file_out):
774             try:
775                 os.remove(f)
776             except:
777                 pass
778
779         self.update_stats("", "")
780
781     def render(self, scene):
782
783         self.update_stats("", "POVRAY: Exporting data from Blender")
784         self._export(scene)
785         self.update_stats("", "POVRAY: Parsing File")
786         self._render()
787
788         r = scene.render
789
790         # compute resolution
791         x = int(r.resolution_x * r.resolution_percentage * 0.01)
792         y = int(r.resolution_y * r.resolution_percentage * 0.01)
793
794         # Wait for the file to be created
795         while not os.path.exists(self._temp_file_out):
796             if self.test_break():
797                 try:
798                     self._process.terminate()
799                 except:
800                     pass
801                 break
802
803             if self._process.poll() != None:
804                 self.update_stats("", "POVRAY: Failed")
805                 break
806
807             time.sleep(self.DELAY)
808
809         if os.path.exists(self._temp_file_out):
810
811             self.update_stats("", "POVRAY: Rendering")
812
813             prev_size = -1
814
815             def update_image():
816                 result = self.begin_result(0, 0, x, y)
817                 lay = result.layers[0]
818                 # possible the image wont load early on.
819                 try:
820                     lay.load_from_file(self._temp_file_out)
821                 except:
822                     pass
823                 self.end_result(result)
824
825             # Update while povray renders
826             while True:
827
828                 # test if povray exists
829                 if self._process.poll() is not None:
830                     update_image()
831                     break
832
833                 # user exit
834                 if self.test_break():
835                     try:
836                         self._process.terminate()
837                     except:
838                         pass
839
840                     break
841
842                 # Would be nice to redirect the output
843                 # stdout_value, stderr_value = self._process.communicate() # locks
844
845
846                 # check if the file updated
847                 new_size = os.path.getsize(self._temp_file_out)
848
849                 if new_size != prev_size:
850                     update_image()
851                     prev_size = new_size
852
853                 time.sleep(self.DELAY)
854
855         self._cleanup()
856
857
858 # Use some of the existing buttons.
859 import properties_render
860 properties_render.RENDER_PT_render.COMPAT_ENGINES.add('POVRAY_RENDER')
861 properties_render.RENDER_PT_dimensions.COMPAT_ENGINES.add('POVRAY_RENDER')
862 properties_render.RENDER_PT_antialiasing.COMPAT_ENGINES.add('POVRAY_RENDER')
863 properties_render.RENDER_PT_output.COMPAT_ENGINES.add('POVRAY_RENDER')
864 del properties_render
865
866 # Use only a subset of the world panels
867 import properties_world
868 properties_world.WORLD_PT_preview.COMPAT_ENGINES.add('POVRAY_RENDER')
869 properties_world.WORLD_PT_context_world.COMPAT_ENGINES.add('POVRAY_RENDER')
870 properties_world.WORLD_PT_world.COMPAT_ENGINES.add('POVRAY_RENDER')
871 properties_world.WORLD_PT_mist.COMPAT_ENGINES.add('POVRAY_RENDER')
872 del properties_world
873
874 # Example of wrapping every class 'as is'
875 import properties_material
876 for member in dir(properties_material):
877     subclass = getattr(properties_material, member)
878     try:
879         subclass.COMPAT_ENGINES.add('POVRAY_RENDER')
880     except:
881         pass
882 del properties_material
883 import properties_data_mesh
884 for member in dir(properties_data_mesh):
885     subclass = getattr(properties_data_mesh, member)
886     try:
887         subclass.COMPAT_ENGINES.add('POVRAY_RENDER')
888     except:
889         pass
890 del properties_data_mesh
891 import properties_texture
892 for member in dir(properties_texture):
893     subclass = getattr(properties_texture, member)
894     try:
895         subclass.COMPAT_ENGINES.add('POVRAY_RENDER')
896     except:
897         pass
898 del properties_texture
899 import properties_data_camera
900 for member in dir(properties_data_camera):
901     subclass = getattr(properties_data_camera, member)
902     try:
903         subclass.COMPAT_ENGINES.add('POVRAY_RENDER')
904     except:
905         pass
906 del properties_data_camera
907
908
909 class RenderButtonsPanel(bpy.types.Panel):
910     bl_space_type = 'PROPERTIES'
911     bl_region_type = 'WINDOW'
912     bl_context = "render"
913     # COMPAT_ENGINES must be defined in each subclass, external engines can add themselves here
914
915     def poll(self, context):
916         rd = context.scene.render
917         return (rd.use_game_engine == False) and (rd.engine in self.COMPAT_ENGINES)
918
919
920 class RENDER_PT_povray_radiosity(RenderButtonsPanel):
921     bl_label = "Radiosity"
922     COMPAT_ENGINES = {'POVRAY_RENDER'}
923
924     def draw_header(self, context):
925         scene = context.scene
926
927         self.layout.prop(scene, "pov_radio_enable", text="")
928
929     def draw(self, context):
930         layout = self.layout
931
932         scene = context.scene
933         rd = scene.render
934
935         layout.active = scene.pov_radio_enable
936
937         split = layout.split()
938
939         col = split.column()
940         col.prop(scene, "pov_radio_count", text="Rays")
941         col.prop(scene, "pov_radio_recursion_limit", text="Recursions")
942         col = split.column()
943         col.prop(scene, "pov_radio_error_bound", text="Error")
944
945         layout.prop(scene, "pov_radio_display_advanced")
946
947         if scene.pov_radio_display_advanced:
948             split = layout.split()
949
950             col = split.column()
951             col.prop(scene, "pov_radio_adc_bailout", slider=True)
952             col.prop(scene, "pov_radio_gray_threshold", slider=True)
953             col.prop(scene, "pov_radio_low_error_factor", slider=True)
954
955             col = split.column()
956             col.prop(scene, "pov_radio_brightness")
957             col.prop(scene, "pov_radio_minimum_reuse", text="Min Reuse")
958             col.prop(scene, "pov_radio_nearest_count")
959
960             split = layout.split()
961
962             col = split.column()
963             col.label(text="Estimation Influence:")
964             col.prop(scene, "pov_radio_media")
965             col.prop(scene, "pov_radio_normal")
966
967             col = split.column()
968             col.prop(scene, "pov_radio_always_sample")
969
970
971 classes = [
972     PovrayRender,
973     RENDER_PT_povray_radiosity]
974
975
976 def register():
977     register = bpy.types.register
978     for cls in classes:
979         register(cls)
980
981
982 def unregister():
983     unregister = bpy.types.unregister
984     for cls in classes:
985         unregister(cls)
986
987 if __name__ == "__main__":
988     register()