psychopath/psychoblend/assembly.py

281 lines
9.7 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
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)
# 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 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")