There are still some things to do to avoid light leakage and other weird shading in some situations, but the basics are working!
285 lines
10 KiB
Python
285 lines
10 KiB
Python
import bpy
|
|
|
|
from .util import escape_name, mat2str, needs_def_mb, needs_xform_mb, ExportCancelled
|
|
|
|
class Assembly:
|
|
def __init__(self, render_engine, objects, visible_layers, group_prefix="", translation_offset=(0,0,0)):
|
|
self.name = group_prefix
|
|
self.translation_offset = translation_offset
|
|
self.render_engine = render_engine
|
|
|
|
self.materials = []
|
|
self.objects = []
|
|
self.instances = []
|
|
|
|
self.mesh_names = set()
|
|
self.assembly_names = set()
|
|
|
|
# Collect all the objects, materials, instances, etc.
|
|
for ob in objects:
|
|
# Check if render is cancelled
|
|
if render_engine.test_break():
|
|
raise ExportCancelled()
|
|
|
|
# Check if the object is visible for rendering
|
|
vis_layer = False
|
|
for i in range(len(ob.layers)):
|
|
vis_layer = vis_layer or (ob.layers[i] and visible_layers[i])
|
|
if ob.hide_render or not vis_layer:
|
|
continue
|
|
|
|
# Store object data
|
|
name = None
|
|
|
|
if ob.type == 'EMPTY':
|
|
if ob.dupli_type == 'GROUP':
|
|
name = group_prefix + "__" + escape_name(ob.dupli_group.name)
|
|
if name not in self.assembly_names:
|
|
self.assembly_names.add(name)
|
|
self.objects += [Assembly(self.render_engine, ob.dupli_group.objects, ob.dupli_group.layers, name, ob.dupli_group.dupli_offset*-1)]
|
|
elif ob.type == 'MESH':
|
|
name = self.get_mesh(ob, group_prefix)
|
|
elif ob.type == 'LAMP' and ob.data.type == 'POINT':
|
|
name = self.get_sphere_lamp(ob, group_prefix)
|
|
elif ob.type == 'LAMP' and ob.data.type == 'AREA':
|
|
name = self.get_rect_lamp(ob, group_prefix)
|
|
|
|
# Store instance
|
|
if name != None:
|
|
self.instances += [Instance(render_engine, ob, name)]
|
|
|
|
def export(self, render_engine, w):
|
|
if self.name == "":
|
|
w.write("Assembly {\n")
|
|
else:
|
|
w.write("Assembly $%s {\n" % self.name)
|
|
w.indent()
|
|
|
|
for mat in self.materials:
|
|
# Check if render is cancelled
|
|
if render_engine.test_break():
|
|
raise ExportCancelled()
|
|
mat.export(render_engine, w)
|
|
|
|
for ob in self.objects:
|
|
# Check if render is cancelled
|
|
if render_engine.test_break():
|
|
raise ExportCancelled()
|
|
ob.export(render_engine, w)
|
|
|
|
for inst in self.instances:
|
|
# Check if render is cancelled
|
|
if render_engine.test_break():
|
|
raise ExportCancelled()
|
|
inst.export(render_engine, w)
|
|
|
|
w.unindent()
|
|
w.write("}\n")
|
|
|
|
#----------------
|
|
|
|
def take_sample(self, render_engine, scene, time):
|
|
for mat in self.materials:
|
|
# Check if render is cancelled
|
|
if render_engine.test_break():
|
|
raise ExportCancelled()
|
|
mat.take_sample(render_engine, scene, time)
|
|
|
|
for ob in self.objects:
|
|
# Check if render is cancelled
|
|
if render_engine.test_break():
|
|
raise ExportCancelled()
|
|
ob.take_sample(render_engine, scene, time)
|
|
|
|
for inst in self.instances:
|
|
# Check if render is cancelled
|
|
if render_engine.test_break():
|
|
raise ExportCancelled()
|
|
inst.take_sample(render_engine, time, self.translation_offset)
|
|
|
|
def cleanup(self):
|
|
for mat in self.materials:
|
|
mat.cleanup()
|
|
for ob in self.objects:
|
|
ob.cleanup()
|
|
|
|
def get_mesh(self, ob, group_prefix):
|
|
# Figure out if we need to export or not and figure out what name to
|
|
# export with.
|
|
has_modifiers = len(ob.modifiers) > 0
|
|
deform_mb = needs_def_mb(ob)
|
|
if has_modifiers or deform_mb:
|
|
mesh_name = group_prefix + escape_name("__" + ob.name + "__" + ob.data.name + "_")
|
|
else:
|
|
mesh_name = group_prefix + escape_name("__" + ob.data.name + "_")
|
|
should_export_mesh = mesh_name not in self.mesh_names
|
|
|
|
# Get mesh
|
|
if should_export_mesh:
|
|
self.mesh_names.add(mesh_name)
|
|
self.objects += [Mesh(self.render_engine, ob, mesh_name)]
|
|
return mesh_name
|
|
else:
|
|
return None
|
|
|
|
def get_sphere_lamp(self, ob, group_prefix):
|
|
name = group_prefix + "__" + escape_name(ob.name)
|
|
self.objects += [SphereLamp(self.render_engine, ob, name)]
|
|
return name
|
|
|
|
def get_rect_lamp(self, ob, group_prefix):
|
|
name = group_prefix + "__" + escape_name(ob.name)
|
|
self.objects += [RectLamp(self.render_engine, ob, name)]
|
|
return name
|
|
|
|
|
|
#=========================================================================
|
|
|
|
|
|
class Mesh:
|
|
""" Holds data for a mesh to be exported.
|
|
"""
|
|
def __init__(self, render_engine, ob, name):
|
|
self.ob = ob
|
|
self.name = name
|
|
self.needs_mb = needs_def_mb(self.ob)
|
|
self.time_meshes = []
|
|
|
|
def take_sample(self, render_engine, scene, time):
|
|
if len(self.time_meshes) == 0 or self.needs_mb:
|
|
render_engine.update_stats("", "Psychopath: Collecting '{}' at time {}".format(self.ob.name, time))
|
|
self.time_meshes += [self.ob.to_mesh(scene, True, 'RENDER')]
|
|
|
|
def cleanup(self):
|
|
for mesh in self.time_meshes:
|
|
bpy.data.meshes.remove(mesh)
|
|
|
|
def export(self, render_engine, w):
|
|
render_engine.update_stats("", "Psychopath: Exporting %s" % self.ob.name)
|
|
|
|
if self.ob.data.psychopath.is_subdivision_surface == False:
|
|
# Exporting normal mesh
|
|
w.write("MeshSurface $%s {\n" % self.name)
|
|
w.indent()
|
|
else:
|
|
# Exporting subdivision surface cage
|
|
w.write("SubdivisionSurface $%s {\n" % self.name)
|
|
w.indent()
|
|
|
|
# Write vertices and (if it's smooth shaded) normals
|
|
for ti in range(len(self.time_meshes)):
|
|
w.write("Vertices [")
|
|
w.write(" ".join([("%f" % i) for vert in self.time_meshes[ti].vertices for i in vert.co]), False)
|
|
w.write("]\n", False)
|
|
if self.time_meshes[0].polygons[0].use_smooth and self.ob.data.psychopath.is_subdivision_surface == False:
|
|
w.write("Normals [")
|
|
w.write(" ".join([("%f" % i) for vert in self.time_meshes[ti].vertices for i in vert.normal]), False)
|
|
w.write("]\n", False)
|
|
|
|
# Write face vertex counts
|
|
w.write("FaceVertCounts [")
|
|
w.write(" ".join([("%d" % len(p.vertices)) for p in self.time_meshes[0].polygons]), False)
|
|
w.write("]\n", False)
|
|
|
|
# Write face vertex indices
|
|
w.write("FaceVertIndices [")
|
|
w.write(" ".join([("%d"%v) for p in self.time_meshes[0].polygons for v in p.vertices]), False)
|
|
w.write("]\n", False)
|
|
|
|
# MeshSurface/SubdivisionSurface section end
|
|
w.unindent()
|
|
w.write("}\n")
|
|
|
|
|
|
class SphereLamp:
|
|
""" Holds data for a sphere light to be exported.
|
|
"""
|
|
def __init__(self, render_engine, ob, name):
|
|
self.ob = ob
|
|
self.name = name
|
|
self.time_col = []
|
|
self.time_rad = []
|
|
|
|
def take_sample(self, render_engine, scene, time):
|
|
render_engine.update_stats("", "Psychopath: Collecting '{}' at time {}".format(self.ob.name, time))
|
|
self.time_col += [self.ob.data.color * self.ob.data.energy]
|
|
self.time_rad += [self.ob.data.shadow_soft_size]
|
|
|
|
def cleanup(self):
|
|
pass
|
|
|
|
def export(self, render_engine, w):
|
|
render_engine.update_stats("", "Psychopath: Exporting %s" % self.ob.name)
|
|
|
|
w.write("SphereLight $%s {\n" % self.name)
|
|
w.indent()
|
|
for col in self.time_col:
|
|
w.write("Color [%f %f %f]\n" % (col[0], col[1], col[2]))
|
|
for rad in self.time_rad:
|
|
w.write("Radius [%f]\n" % rad)
|
|
|
|
w.unindent()
|
|
w.write("}\n")
|
|
|
|
|
|
class RectLamp:
|
|
""" Holds data for a rectangular light to be exported.
|
|
"""
|
|
def __init__(self, render_engine, ob, name):
|
|
self.ob = ob
|
|
self.name = name
|
|
self.time_col = []
|
|
self.time_dim = []
|
|
|
|
def take_sample(self, render_engine, scene, time):
|
|
render_engine.update_stats("", "Psychopath: Collecting '{}' at time {}".format(self.ob.name, time))
|
|
self.time_col += [self.ob.data.color * self.ob.data.energy]
|
|
if self.ob.data.shape == 'RECTANGLE':
|
|
self.time_dim += [(self.ob.data.size, self.ob.data.size_y)]
|
|
else:
|
|
self.time_dim += [(self.ob.data.size, self.ob.data.size)]
|
|
|
|
def cleanup(self):
|
|
pass
|
|
|
|
def export(self, render_engine, w):
|
|
render_engine.update_stats("", "Psychopath: Exporting %s" % self.ob.name)
|
|
|
|
w.write("RectangleLight $%s {\n" % self.name)
|
|
w.indent()
|
|
for col in self.time_col:
|
|
w.write("Color [%f %f %f]\n" % (col[0], col[1], col[2]))
|
|
for dim in self.time_dim:
|
|
w.write("Dimensions [%f %f]\n" % dim)
|
|
|
|
w.unindent()
|
|
w.write("}\n")
|
|
|
|
|
|
class Instance:
|
|
def __init__(self, render_engine, ob, data_name):
|
|
self.ob = ob
|
|
self.data_name = data_name
|
|
self.needs_mb = needs_xform_mb(self.ob)
|
|
self.time_xforms = []
|
|
|
|
def take_sample(self, render_engine, time, translation_offset):
|
|
if len(self.time_xforms) == 0 or self.needs_mb:
|
|
render_engine.update_stats("", "Psychopath: Collecting '{}' xforms at time {}".format(self.ob.name, time))
|
|
mat = self.ob.matrix_world.copy()
|
|
mat[0][3] += translation_offset[0]
|
|
mat[1][3] += translation_offset[1]
|
|
mat[2][3] += translation_offset[2]
|
|
self.time_xforms += [mat]
|
|
|
|
def export(self, render_engine, w):
|
|
render_engine.update_stats("", "Psychopath: Exporting %s" % self.ob.name)
|
|
|
|
w.write("Instance {\n")
|
|
w.indent()
|
|
w.write("Data [$%s]\n" % self.data_name)
|
|
for mat in self.time_xforms:
|
|
w.write("Transform [%s]\n" % mat2str(mat.inverted()))
|
|
w.unindent()
|
|
w.write("}\n")
|