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.material_names = set() 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 + "_") has_faces = len(ob.data.polygons) > 0 should_export_mesh = has_faces and (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)] # Get materials for ms in ob.material_slots: if ms != None: if ms.material.name not in self.material_names: self.material_names.add(ms.material.name) self.materials += [Material(self.render_engine, ms.material)] 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)) if self.ob.data.psychopath.color_type == 'Rec709': self.time_col += [('Rec709', self.ob.data.color * self.ob.data.energy)] elif self.ob.data.psychopath.color_type == 'Blackbody': self.time_col += [('Blackbody', self.ob.data.psychopath.color_blackbody_temp, self.ob.data.energy)] elif self.ob.data.psychopath.color_type == 'ColorTemperature': self.time_col += [('ColorTemperature', self.ob.data.psychopath.color_blackbody_temp, 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: if col[0] == 'Rec709': w.write("Color [rec709, %f %f %f]\n" % (col[1][0], col[1][1], col[1][2])) elif col[0] == 'Blackbody': w.write("Color [blackbody, %f %f]\n" % (col[1], col[2])) elif col[0] == 'ColorTemperature': w.write("Color [color_temperature, %f %f]\n" % (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)) if self.ob.data.psychopath.color_type == 'Rec709': self.time_col += [('Rec709', self.ob.data.color * self.ob.data.energy)] elif self.ob.data.psychopath.color_type == 'Blackbody': self.time_col += [('Blackbody', self.ob.data.psychopath.color_blackbody_temp, self.ob.data.energy)] elif self.ob.data.psychopath.color_type == 'ColorTemperature': self.time_col += [('ColorTemperature', self.ob.data.psychopath.color_blackbody_temp, 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: if col[0] == 'Rec709': w.write("Color [rec709, %f %f %f]\n" % (col[1][0], col[1][1], col[1][2])) elif col[0] == 'Blackbody': w.write("Color [blackbody, %f %f]\n" % (col[1], col[2])) elif col[0] == 'ColorTemperature': w.write("Color [color_temperature, %f %f]\n" % (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())) for ms in self.ob.material_slots: if ms != None: w.write("SurfaceShaderBind [$%s]\n" % escape_name(ms.material.name)) break w.unindent() w.write("}\n") class Material: def __init__(self, render_engine, material): self.mat = material def take_sample(self, render_engine, time, translation_offset): # TODO: motion blur of material settings pass def export(self, render_engine, w): render_engine.update_stats("", "Psychopath: Exporting %s" % self.mat.name) w.write("SurfaceShader $%s {\n" % escape_name(self.mat.name)) w.indent() if self.mat.psychopath.surface_shader_type == 'Emit': w.write("Type [Emit]\n") if self.mat.psychopath.color_type == 'Rec709': col = self.mat.psychopath.color w.write("Color [rec709, %f %f %f]\n" % ( col[0], col[1], col[2], )) elif self.mat.psychopath.color_type == 'Blackbody': w.write("Color [blackbody, %f %f]\n" % ( self.mat.psychopath.color_blackbody_temp, 1.0, )) elif self.mat.psychopath.color_type == 'ColorTemperature': w.write("Color [color_temperature, %f %f]\n" % ( self.mat.psychopath.color_blackbody_temp, 1.0, )) elif self.mat.psychopath.surface_shader_type == 'Lambert': w.write("Type [Lambert]\n") if self.mat.psychopath.color_type == 'Rec709': col = self.mat.psychopath.color w.write("Color [rec709, %f %f %f]\n" % ( col[0], col[1], col[2], )) elif self.mat.psychopath.color_type == 'Blackbody': w.write("Color [blackbody, %f %f]\n" % ( self.mat.psychopath.color_blackbody_temp, 1.0, )) elif self.mat.psychopath.color_type == 'ColorTemperature': w.write("Color [color_temperature, %f %f]\n" % ( self.mat.psychopath.color_blackbody_temp, 1.0, )) elif self.mat.psychopath.surface_shader_type == 'GGX': w.write("Type [GGX]\n") if self.mat.psychopath.color_type == 'Rec709': col = self.mat.psychopath.color w.write("Color [rec709, %f %f %f]\n" % ( col[0], col[1], col[2], )) elif self.mat.psychopath.color_type == 'Blackbody': w.write("Color [blackbody, %f %f]\n" % ( self.mat.psychopath.color_blackbody_temp, 1.0, )) elif self.mat.psychopath.color_type == 'ColorTemperature': w.write("Color [color_temperature, %f %f]\n" % ( self.mat.psychopath.color_blackbody_temp, 1.0, )) w.write("Roughness [%f]\n" % self.mat.psychopath.roughness) w.write("Fresnel [%f]\n" % self.mat.psychopath.fresnel) else: raise "Unsupported surface shader type '%s'" % self.mat.psychopath.surface_shader_type w.unindent() w.write("}\n") def cleanup(self): pass