
Supports... - camera/lamp/mesh object types - meshes with modifiers applied, normals/uv/vertex colors - materials, reflection, transparency - spot/area/point lamps, samples, raytrace options - scene render size, AA setting Details... - Doesn't need any 3rd party modules. - Runs povray from the subprocess module, updating the image from a TARGA. - Currently no UI panels or support for custom settings. This could be used as an example for other scripts.
565 lines
16 KiB
Python
565 lines
16 KiB
Python
import bpy
|
|
|
|
from math import atan, pi, degrees
|
|
import subprocess
|
|
import os
|
|
import sys
|
|
import time
|
|
|
|
def write_pov(filename, scene=None, info_callback = None):
|
|
file = open(filename, 'w')
|
|
|
|
# Only for testing
|
|
if not scene:
|
|
scene = bpy.data.scenes[0]
|
|
|
|
render = scene.render_data
|
|
materialTable = {}
|
|
|
|
def saneName(name):
|
|
name = name.lower()
|
|
for ch in ' /\\+=-[]{}().,<>\'":;~!@#$%^&*|?':
|
|
name = name.replace(ch, '_')
|
|
return name
|
|
|
|
def writeMatrix(matrix):
|
|
file.write('\tmatrix <%.6f, %.6f, %.6f, %.6f, %.6f, %.6f, %.6f, %.6f, %.6f, %.6f, %.6f, %.6f>\n' %\
|
|
(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]) )
|
|
|
|
def exportCamera():
|
|
camera = scene.camera
|
|
matrix = camera.matrix
|
|
|
|
# compute resolution
|
|
Qsize=float(render.resolution_x)/float(render.resolution_y)
|
|
|
|
file.write('camera {\n')
|
|
file.write('\tlocation <0, 0, 0>\n')
|
|
file.write('\tlook_at <0, 0, -1>\n')
|
|
file.write('\tright <%s, 0, 0>\n' % -Qsize)
|
|
file.write('\tup <0, 1, 0>\n')
|
|
file.write('\tangle %f \n' % (360.0*atan(16.0/camera.data.lens)/pi))
|
|
|
|
file.write('\trotate <%.6f, %.6f, %.6f>\n' % tuple([degrees(e) for e in matrix.rotationPart().toEuler()]))
|
|
file.write('\ttranslate <%.6f, %.6f, %.6f>\n' % (matrix[3][0], matrix[3][1], matrix[3][2]))
|
|
file.write('}\n')
|
|
|
|
|
|
|
|
def exportLamps(lamps):
|
|
# Get all lamps
|
|
for ob in lamps:
|
|
lamp = ob.data
|
|
|
|
matrix = ob.matrix
|
|
|
|
color = tuple([c * lamp.energy for c in lamp.color]) # Colour is modified by energy
|
|
|
|
file.write('light_source')
|
|
file.write('{\n')
|
|
file.write('\t< 0,0,0 >\n')
|
|
file.write('\tcolor red %.6f green %.6f blue %.6f\n' % color)
|
|
|
|
if lamp.type == 'POINT': # Point Lamp
|
|
pass
|
|
elif lamp.type == 'SPOT': # Spot
|
|
file.write('\tspotlight\n')
|
|
|
|
# Falloff is the main radius from the centre line
|
|
file.write('\tfalloff %.2f\n' % (lamp.spot_size/2.0) ) # 1 TO 179 FOR BOTH
|
|
file.write('\tradius %.6f\n' % ((lamp.spot_size/2.0) * (1-lamp.spot_blend)) )
|
|
|
|
# Blender does not have a tightness equivilent, 0 is most like blender default.
|
|
file.write('\ttightness 0\n') # 0:10f
|
|
|
|
file.write('\tpoint_at <0, 0, -1>\n')
|
|
elif lamp.type == 'AREA':
|
|
|
|
size_x = lamp.size
|
|
samples_x = lamp.shadow_ray_samples_x
|
|
if lamp.shape == 'SQUARE':
|
|
size_y = size_x
|
|
samples_y = samples_x
|
|
else:
|
|
size_y = lamp.size_y
|
|
samples_y = lamp.shadow_ray_samples_y
|
|
|
|
|
|
|
|
file.write('\tarea_light <%d,0,0>,<0,0,%d> %d, %d\n' % (size_x, size_y, samples_x, samples_y))
|
|
if lamp.shadow_ray_sampling_method == 'CONSTANT_JITTERED':
|
|
if lamp.jitter:
|
|
file.write('\tjitter\n')
|
|
else:
|
|
file.write('\tadaptive 1\n')
|
|
file.write('\tjitter\n')
|
|
|
|
if lamp.shadow_method == 'NOSHADOW':
|
|
file.write('\tshadowless\n')
|
|
|
|
file.write('\tfade_distance %.6f\n' % lamp.distance)
|
|
file.write('\tfade_power %d\n' % 1) # Could use blenders lamp quad?
|
|
writeMatrix(matrix)
|
|
|
|
file.write('}\n')
|
|
|
|
def exportMeshs(sel):
|
|
def bMat2PovString(material):
|
|
povstring = 'finish {'
|
|
if world != None:
|
|
povstring += 'ambient <%.6f, %.6f, %.6f> ' % tuple([c*material.ambient for c in world.ambient_color])
|
|
|
|
povstring += 'diffuse %.6f ' % material.diffuse_reflection
|
|
povstring += 'specular %.6f ' % material.specular_reflection
|
|
|
|
|
|
if material.raytrace_mirror.enabled:
|
|
#povstring += 'interior { ior %.6f } ' % material.IOR
|
|
raytrace_mirror= material.raytrace_mirror
|
|
if raytrace_mirror.reflect:
|
|
povstring += 'reflection {'
|
|
povstring += '<%.6f, %.6f, %.6f>' % tuple(material.mirror_color) # Should ask for ray mirror flag
|
|
povstring += 'fresnel 1 falloff %.6f exponent %.6f metallic %.6f} ' % (raytrace_mirror.fresnel, raytrace_mirror.fresnel_fac, raytrace_mirror.reflect)
|
|
|
|
|
|
|
|
if material.raytrace_transparency.enabled:
|
|
#povstring += 'interior { ior %.6f } ' % material.IOR
|
|
pass
|
|
|
|
#file.write('\t\troughness %.6f\n' % (material.hard*0.5))
|
|
#file.write('\t\t\tcrand 0.0\n') # Sand granyness
|
|
#file.write('\t\t\tmetallic %.6f\n' % material.spec)
|
|
#file.write('\t\t\tphong %.6f\n' % material.spec)
|
|
#file.write('\t\t\tphong_size %.6f\n' % material.spec)
|
|
povstring += 'brilliance %.6f ' % (material.specular_hardness/256.0) # Like hardness
|
|
povstring += '}'
|
|
#file.write('\t}\n')
|
|
return povstring
|
|
|
|
|
|
world = scene.world
|
|
|
|
# Convert all materials to strings we can access directly per vertex.
|
|
for material in bpy.data.materials:
|
|
materialTable[material.name] = bMat2PovString(material)
|
|
|
|
|
|
ob_num = 0
|
|
|
|
for ob in sel:
|
|
ob_num+= 1
|
|
|
|
if ob.type in ('LAMP', 'CAMERA', 'EMPTY'):
|
|
continue
|
|
|
|
me = ob.data
|
|
me_materials= me.materials
|
|
|
|
me = ob.create_render_mesh(scene)
|
|
|
|
if not me:
|
|
continue
|
|
|
|
if info_callback:
|
|
info_callback('Object %2.d of %2.d (%s)' % (ob_num, len(sel), ob.name))
|
|
|
|
#if ob.type!='MESH':
|
|
# continue
|
|
# me = ob.data
|
|
|
|
matrix = ob.matrix
|
|
try: uv_layer = me.active_uv_texture.data
|
|
except:uv_layer = None
|
|
|
|
try: vcol_layer = me.active_vertex_color.data
|
|
except:vcol_layer = None
|
|
|
|
|
|
def regular_face(f):
|
|
fv = f.verts
|
|
if fv[3]== 0:
|
|
return fv[0], fv[1], fv[2]
|
|
return fv[0], fv[1], fv[2], fv[3]
|
|
|
|
faces_verts = [regular_face(f) for f in me.faces]
|
|
faces_normals = [tuple(f.normal) for f in me.faces]
|
|
verts_normals = [tuple(v.normal) for v in me.verts]
|
|
|
|
# quads incur an extra face
|
|
quadCount = len([f for f in faces_verts if len(f)==4])
|
|
|
|
file.write('mesh2 {\n')
|
|
file.write('\tvertex_vectors {\n')
|
|
file.write('\t\t%s' % (len(me.verts))) # vert count
|
|
for v in me.verts:
|
|
file.write(',\n\t\t<%.6f, %.6f, %.6f>' % tuple(v.co)) # vert count
|
|
file.write('\n }\n')
|
|
|
|
|
|
# Build unique Normal list
|
|
uniqueNormals = {}
|
|
for fi, f in enumerate(me.faces):
|
|
fv = faces_verts[fi]
|
|
# [-1] is a dummy index, use a list so we can modify in place
|
|
if f.smooth: # Use vertex normals
|
|
for v in fv:
|
|
key = verts_normals[v]
|
|
uniqueNormals[key] = [-1]
|
|
else: # Use face normal
|
|
key = faces_normals[fi]
|
|
uniqueNormals[key] = [-1]
|
|
|
|
file.write('\tnormal_vectors {\n')
|
|
file.write('\t\t%d' % len(uniqueNormals)) # vert count
|
|
idx = 0
|
|
for no, index in uniqueNormals.items():
|
|
file.write(',\n\t\t<%.6f, %.6f, %.6f>' % no) # vert count
|
|
index[0] = idx
|
|
idx +=1
|
|
file.write('\n }\n')
|
|
|
|
|
|
# Vertex colours
|
|
vertCols = {} # Use for material colours also.
|
|
|
|
if uv_layer:
|
|
# Generate unique UV's
|
|
uniqueUVs = {}
|
|
|
|
for fi, uv in enumerate(uv_layer):
|
|
|
|
if len(faces_verts[fi])==4:
|
|
uvs = uv.uv1, uv.uv2, uv.uv3, uv.uv4
|
|
else:
|
|
uvs = uv.uv1, uv.uv2, uv.uv3
|
|
|
|
for uv in uvs:
|
|
uniqueUVs[tuple(uv)] = [-1]
|
|
|
|
file.write('\tuv_vectors {\n')
|
|
#print unique_uvs
|
|
file.write('\t\t%s' % (len(uniqueUVs))) # vert count
|
|
idx = 0
|
|
for uv, index in uniqueUVs.items():
|
|
file.write(',\n\t\t<%.6f, %.6f>' % uv)
|
|
index[0] = idx
|
|
idx +=1
|
|
'''
|
|
else:
|
|
# Just add 1 dummy vector, no real UV's
|
|
file.write('\t\t1') # vert count
|
|
file.write(',\n\t\t<0.0, 0.0>')
|
|
'''
|
|
file.write('\n }\n')
|
|
|
|
|
|
if me.vertex_colors:
|
|
|
|
for fi, f in enumerate(me.faces):
|
|
material_index = f.material_index
|
|
material = me_materials[material_index]
|
|
|
|
if material and material.vertex_color_paint:
|
|
|
|
col = vcol_layer[fi]
|
|
|
|
if len(faces_verts[fi])==4:
|
|
cols = col.color1, col.color2, col.color3, col.color4
|
|
else:
|
|
cols = col.color1, col.color2, col.color3
|
|
|
|
for col in cols:
|
|
key = col[0], col[1], col[2], material_index # Material index!
|
|
vertCols[key] = [-1]
|
|
|
|
else:
|
|
if material:
|
|
diffuse_color = tuple(material.diffuse_color)
|
|
key = diffuse_color[0], diffuse_color[1], diffuse_color[2], material_index
|
|
vertCols[key] = [-1]
|
|
|
|
|
|
else:
|
|
# No vertex colours, so write material colours as vertex colours
|
|
for i, material in enumerate(me_materials):
|
|
|
|
if material:
|
|
diffuse_color = tuple(material.diffuse_color)
|
|
key = diffuse_color[0], diffuse_color[1], diffuse_color[2], i # i == f.mat
|
|
vertCols[key] = [-1]
|
|
|
|
|
|
# Vert Colours
|
|
file.write('\ttexture_list {\n')
|
|
file.write('\t\t%s' % (len(vertCols))) # vert count
|
|
idx=0
|
|
for col, index in vertCols.items():
|
|
|
|
if me_materials:
|
|
material = me_materials[col[3]]
|
|
materialString = materialTable[material.name]
|
|
else:
|
|
materialString = '' # Dont write anything
|
|
|
|
float_col = col[0], col[1], col[2], 1-material.alpha, materialString
|
|
#print material.apl
|
|
file.write(',\n\t\ttexture { pigment {rgbf<%.6f, %.6f, %.6f, %.6f>}%s}' % float_col)
|
|
index[0] = idx
|
|
idx+=1
|
|
|
|
file.write( '\n }\n' )
|
|
|
|
# Face indicies
|
|
file.write('\tface_indices {\n')
|
|
file.write('\t\t%d' % (len(me.faces) + quadCount)) # faces count
|
|
for fi, f in enumerate(me.faces):
|
|
fv = faces_verts[fi]
|
|
material_index= f.material_index
|
|
if len(fv) == 4: indicies = (0,1,2), (0,2,3)
|
|
else: indicies = ((0,1,2),)
|
|
|
|
if vcol_layer:
|
|
col = vcol_layer[fi]
|
|
|
|
if len(fv) == 4:
|
|
cols = col.color1, col.color2, col.color3, col.color4
|
|
else:
|
|
cols = col.color1, col.color2, col.color3
|
|
|
|
|
|
if not me_materials or me_materials[material_index] == None: # No materials
|
|
for i1, i2, i3 in indicies:
|
|
file.write(',\n\t\t<%d,%d,%d>' % (fv[i1], fv[i2], fv[i3])) # vert count
|
|
else:
|
|
material = me_materials[material_index]
|
|
for i1, i2, i3 in indicies:
|
|
if me.vertex_colors and material.vertex_color_paint:
|
|
# Colour per vertex - vertex colour
|
|
|
|
col1 = cols[i1]
|
|
col2 = cols[i2]
|
|
col3 = cols[i3]
|
|
|
|
ci1 = vertCols[col1[0], col1[1], col1[2], material_index][0]
|
|
ci2 = vertCols[col2[0], col2[1], col2[2], material_index][0]
|
|
ci3 = vertCols[col3[0], col3[1], col3[2], material_index][0]
|
|
else:
|
|
# Colour per material - flat material colour
|
|
diffuse_color= material.diffuse_color
|
|
ci1 = ci2 = ci3 = vertCols[diffuse_color[0], diffuse_color[1], diffuse_color[2], f.material_index][0]
|
|
|
|
file.write(',\n\t\t<%d,%d,%d>, %d,%d,%d' % (fv[i1], fv[i2], fv[i3], ci1, ci2, ci3)) # vert count
|
|
|
|
|
|
|
|
file.write('\n }\n')
|
|
|
|
# normal_indices indicies
|
|
file.write('\tnormal_indices {\n')
|
|
file.write('\t\t%d' % (len(me.faces) + quadCount)) # faces count
|
|
for fi, f in enumerate(me.faces):
|
|
fv = faces_verts[fi]
|
|
if len(fv) == 4: indicies = (0,1,2), (0,2,3)
|
|
else: indicies = ((0,1,2),)
|
|
|
|
for i1, i2, i3 in indicies:
|
|
if f.smooth:
|
|
file.write(',\n\t\t<%d,%d,%d>' %\
|
|
(uniqueNormals[verts_normals[fv[i1]]][0],\
|
|
uniqueNormals[verts_normals[fv[i2]]][0],\
|
|
uniqueNormals[verts_normals[fv[i3]]][0])) # vert count
|
|
else:
|
|
idx = uniqueNormals[faces_normals[fi]][0]
|
|
file.write(',\n\t\t<%d,%d,%d>' % (idx, idx, idx)) # vert count
|
|
|
|
|
|
file.write('\n }\n')
|
|
|
|
|
|
# normal_indices indicies
|
|
|
|
if uv_layer:
|
|
file.write('\tuv_indices {\n')
|
|
file.write('\t\t%d' % (len(me.faces) + quadCount)) # faces count
|
|
for f in me.faces:
|
|
fv = faces_verts[fi]
|
|
|
|
if len(fv) == 4: indicies = (0,1,2), (0,2,3)
|
|
else: indicies = ((0,1,2),)
|
|
|
|
uv = uv_layer[fi]
|
|
if len(faces_verts[fi])==4:
|
|
uvs = uv.uv1, uv.uv2, uv.uv3, uv.uv4
|
|
else:
|
|
uvs = uv.uv1, uv.uv2, uv.uv3
|
|
|
|
for i1, i2, i3 in indicies:
|
|
file.write(',\n\t\t<%d,%d,%d>' %\
|
|
(uniqueUVs[tuple(uvs[i1][0:2])][0],\
|
|
uniqueUVs[tuple(uvs[i2][0:2])][0],\
|
|
uniqueUVs[tuple(uvs[i2][0:2])][0])) # vert count
|
|
file.write('\n }\n')
|
|
|
|
if me.materials:
|
|
material = me.materials[0] # dodgy
|
|
if material and material.raytrace_transparency.enabled:
|
|
file.write('\tinterior { ior %.6f }\n' % material.raytrace_transparency.ior)
|
|
|
|
writeMatrix(matrix)
|
|
file.write('}\n')
|
|
|
|
bpy.data.remove_mesh(me)
|
|
|
|
|
|
exportCamera()
|
|
#exportMaterials()
|
|
sel = scene.objects
|
|
lamps = [l for l in sel if l.type == 'LAMP']
|
|
exportLamps(lamps)
|
|
exportMeshs(sel)
|
|
|
|
file.close()
|
|
|
|
|
|
def write_pov_ini(filename_ini, filename_pov, filename_image):
|
|
scene = bpy.data.scenes[0]
|
|
render = scene.render_data
|
|
|
|
x= int(render.resolution_x*render.resolution_percentage*0.01)
|
|
y= int(render.resolution_y*render.resolution_percentage*0.01)
|
|
|
|
file = open(filename_ini, 'w')
|
|
|
|
file.write('Input_File_Name="%s"\n' % filename_pov)
|
|
file.write('Output_File_Name="%s"\n' % filename_image)
|
|
|
|
file.write('Width=%d\n' % x)
|
|
file.write('Height=%d\n' % y)
|
|
|
|
# Needed for border render.
|
|
'''
|
|
file.write('Start_Column=%d\n' % part.x)
|
|
file.write('End_Column=%d\n' % (part.x+part.w))
|
|
|
|
file.write('Start_Row=%d\n' % (part.y))
|
|
file.write('End_Row=%d\n' % (part.y+part.h))
|
|
'''
|
|
|
|
file.write('Display=0\n')
|
|
file.write('Pause_When_Done=0\n')
|
|
file.write('Output_File_Type=C\n') # TGA, best progressive loading
|
|
file.write('Output_Alpha=1\n')
|
|
|
|
if render.antialiasing:
|
|
aa_mapping = {'OVERSAMPLE_5':2, 'OVERSAMPLE_8':3, 'OVERSAMPLE_11':4, 'OVERSAMPLE_16':5} # method 1 assumed
|
|
file.write('Antialias=1\n')
|
|
file.write('Antialias_Depth=%d\n' % aa_mapping[render.antialiasing_samples])
|
|
else:
|
|
file.write('Antialias=0\n')
|
|
|
|
file.close()
|
|
|
|
|
|
class PovrayRenderEngine(bpy.types.RenderEngine):
|
|
__label__ = "Povray"
|
|
DELAY = 0.02
|
|
def _export(self, scene):
|
|
import tempfile
|
|
|
|
self.temp_file_in = tempfile.mktemp(suffix='.pov')
|
|
self.temp_file_out = tempfile.mktemp(suffix='.ppm')
|
|
self.temp_file_ini = tempfile.mktemp(suffix='.ini')
|
|
|
|
def info_callback(txt):
|
|
self.update_stats("", "POVRAY: " + txt)
|
|
|
|
write_pov(self.temp_file_in, scene, info_callback)
|
|
|
|
def _render(self):
|
|
|
|
try: os.remove(self.temp_file_out) # so as not to load the old file
|
|
except: pass
|
|
|
|
write_pov_ini(self.temp_file_ini, self.temp_file_in, self.temp_file_out)
|
|
|
|
print ("***-STARTING-***")
|
|
# This works too but means we have to wait until its done
|
|
# os.system('povray %s' % self.temp_file_ini)
|
|
|
|
self.process = subprocess.Popen(["povray", self.temp_file_ini]) # stdout=subprocess.PIPE, stderr=subprocess.PIPE
|
|
print ("***-DONE-***")
|
|
|
|
def _cleanup(self):
|
|
for f in (self.temp_file_in, self.temp_file_ini, self.temp_file_out):
|
|
try: os.remove(f)
|
|
except: pass
|
|
|
|
self.update_stats("", "")
|
|
|
|
def render(self, scene):
|
|
|
|
self.update_stats("", "POVRAY: Exporting data from Blender")
|
|
self._export(scene)
|
|
self.update_stats("", "POVRAY: Parsing File")
|
|
self._render()
|
|
|
|
r = scene.render_data
|
|
|
|
# compute resolution
|
|
x= int(r.resolution_x*r.resolution_percentage*0.01)
|
|
y= int(r.resolution_y*r.resolution_percentage*0.01)
|
|
|
|
|
|
|
|
# Wait for the file to be created
|
|
while not os.path.exists(self.temp_file_out):
|
|
time.sleep(self.DELAY)
|
|
|
|
self.update_stats("", "POVRAY: Rendering")
|
|
|
|
prev_size = -1
|
|
|
|
def update_image():
|
|
result = self.begin_result(0, 0, x, y)
|
|
lay = result.layers[0]
|
|
# possible the image wont load early on.
|
|
try: lay.rect_from_file(self.temp_file_out, 0, 0)
|
|
except: pass
|
|
self.end_result(result)
|
|
|
|
# Update while povray renders
|
|
while True:
|
|
|
|
# test if povray exists
|
|
if self.process.poll() != None:
|
|
update_image();
|
|
break
|
|
|
|
# user exit
|
|
if self.test_break():
|
|
try: # It might not be running
|
|
self.process.terminate()
|
|
except:
|
|
pass
|
|
|
|
break
|
|
|
|
# Would be nice to redirect the output
|
|
# stdout_value, stderr_value = self.process.communicate() # locks
|
|
|
|
|
|
# check if the file updated
|
|
new_size = os.path.getsize(self.temp_file_out)
|
|
|
|
if new_size != prev_size:
|
|
update_image()
|
|
prev_size = new_size
|
|
|
|
time.sleep(self.DELAY)
|
|
|
|
self._cleanup()
|
|
|
|
|
|
bpy.types.register(PovrayRenderEngine)
|