Continue WIP update PsychoBlend for Blender 3.x.

It exports and renders successfully... except there are no objects.
Just a blank background.
This commit is contained in:
Nathan Vegdahl 2022-08-06 21:40:13 -07:00
parent 6d7b8b280f
commit 69ace90689
7 changed files with 479 additions and 558 deletions

View File

@ -1,6 +1,6 @@
bl_info = {
"name": "PsychoBlend",
"version": (0, 1),
"version": (0, 1, 0),
"author": "Nathan Vegdahl",
"blender": (3, 1, 0),
"description": "Psychopath renderer integration",

View File

@ -1,398 +0,0 @@
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))
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

77
psychoblend/material.py Normal file
View File

@ -0,0 +1,77 @@
import bpy
from .util import escape_name, mat2str, needs_def_mb, needs_xform_mb, ExportCancelled
class Material:
def __init__(self, render_engine, depsgraph, material):
self.mat = material
def take_sample(self, render_engine, depsgraph, time):
# 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

227
psychoblend/objects.py Normal file
View File

@ -0,0 +1,227 @@
import bpy
from .util import escape_name, mat2str, needs_def_mb, needs_xform_mb, ExportCancelled
def make_object_data_cache(render_engine, depsgraph, ob, name):
if ob.type == 'MESH':
return Mesh(render_engine, depsgraph, ob, name)
elif ob.type == 'LIGHT':
if ob.data.type == 'POINT':
return SphereLamp(render_engine, depsgraph, ob, name)
elif ob.data.type == 'AREA':
return RectLamp(render_engine, depsgraph, ob, name)
elif ob.data.type == 'AREA':
return RectLamp(render_engine, depsgraph, ob, name)
class Mesh:
""" Holds data for a mesh to be exported.
"""
def __init__(self, render_engine, depsgraph, ob, name):
self.name = name
self.is_subdiv = ob.data.psychopath.is_subdivision_surface
self.needs_mb = needs_def_mb(ob)
self.time_meshes = []
def take_sample(self, render_engine, depsgraph, ob, time):
if len(self.time_meshes) == 0 or self.needs_mb:
render_engine.update_stats("", "Psychopath: Collecting '{}' at time {}".format(self.name, time))
self.time_meshes += [ob.to_mesh(depsgraph=depsgraph).copy()]
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.name)
if self.is_subdiv == 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.is_subdiv == 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, depsgraph, ob, name):
self.name = name
self.time_col = []
self.time_rad = []
def take_sample(self, render_engine, depsgraph, ob, time):
render_engine.update_stats("", "Psychopath: Collecting '{}' at time {}".format(ob.name, time))
if ob.data.psychopath.color_type == 'Rec709':
self.time_col += [('Rec709', ob.data.color * ob.data.energy)]
elif ob.data.psychopath.color_type == 'Blackbody':
self.time_col += [('Blackbody', ob.data.psychopath.color_blackbody_temp, ob.data.energy)]
elif ob.data.psychopath.color_type == 'ColorTemperature':
self.time_col += [('ColorTemperature', ob.data.psychopath.color_blackbody_temp, ob.data.energy)]
self.time_rad += [ob.data.shadow_soft_size]
def cleanup(self):
pass
def export(self, render_engine, w):
render_engine.update_stats("", "Psychopath: Exporting %s" % self.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, depsgraph, ob, name):
self.name = name
self.time_col = []
self.time_dim = []
def take_sample(self, render_engine, depsgraph, ob, time):
render_engine.update_stats("", "Psychopath: Collecting '{}' at time {}".format(self.name, time))
if ob.data.psychopath.color_type == 'Rec709':
self.time_col += [('Rec709', ob.data.color * ob.data.energy)]
elif ob.data.psychopath.color_type == 'Blackbody':
self.time_col += [('Blackbody', ob.data.psychopath.color_blackbody_temp, ob.data.energy)]
elif ob.data.psychopath.color_type == 'ColorTemperature':
self.time_col += [('ColorTemperature', ob.data.psychopath.color_blackbody_temp, ob.data.energy)]
if ob.data.shape == 'RECTANGLE':
self.time_dim += [(ob.data.size, ob.data.size_y)]
else:
self.time_dim += [(ob.data.size, 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 DistantDiskLamp:
def __init__(self, render_engine, depsgraph, ob, name):
self.name = name
self.time_col = []
self.time_dir = []
self.time_rad = []
def take_sample(self, render_engine, depsgraph, ob, time):
render_engine.update_stats("", "Psychopath: Collecting '{}' at time {}".format(self.name, time))
self.time_dir += [tuple(ob.matrix_world.to_3x3() @ Vector((0, 0, -1)))]
if ob.data.psychopath.color_type == 'Rec709':
self.time_col += [('Rec709', ob.data.color * ob.data.energy)]
elif ob.data.psychopath.color_type == 'Blackbody':
self.time_col += [('Blackbody', ob.data.psychopath.color_blackbody_temp, ob.data.energy)]
elif ob.data.psychopath.color_type == 'ColorTemperature':
self.time_col += [('ColorTemperature', ob.data.psychopath.color_blackbody_temp, ob.data.energy)]
self.time_rad += [ob.data.shadow_soft_size]
def export(self, render_engine, w):
render_engine.update_stats("", "Psychopath: Exporting %s" % self.ob.name)
w.write("DistantDiskLight $%s {\n" % self.name)
w.indent()
for direc in self.time_dir:
w.write("Direction [%f %f %f]\n" % (direc[0], direc[1], direc[2]))
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 Instance:
# def __init__(self, render_engine, depsgraph, 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))
# 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")

View File

@ -2,9 +2,10 @@ import bpy
from math import log
from .assembly import Assembly
from .objects import make_object_data_cache, Mesh, DistantDiskLamp
from .util import escape_name, mat2str, ExportCancelled
from .world import World
from .world import World, Camera
from . import bl_info
class IndentedWriter:
@ -29,25 +30,40 @@ class IndentedWriter:
class PsychoExporter:
def __init__(self, f, render_engine, scene):
def __init__(self, f, render_engine, depsgraph):
self.w = IndentedWriter(f)
self.render_engine = render_engine
self.scene = scene
self.depsgraph = depsgraph
self.scene = depsgraph.scene
self.view_layer = depsgraph.view_layer
self.mesh_names = {}
self.group_names = {}
# For camera data.
res_x = int(self.scene.render.resolution_x * (self.scene.render.resolution_percentage / 100))
res_y = int(self.scene.render.resolution_y * (self.scene.render.resolution_percentage / 100))
self.camera = Camera(render_engine, depsgraph.scene.camera, float(res_x) / float(res_y))
# Motion blur segments are rounded down to a power of two
if scene.psychopath.motion_blur_segments > 0:
self.time_samples = (2**int(log(scene.psychopath.motion_blur_segments, 2))) + 1
# For world data.
self.world = World(render_engine, depsgraph)
# For all objects except sun lamps.
self.object_data = {} # name -> cached_data
self.instances = {} # instance_id -> [object_data_name, transform_list]
# For all sun lamps.
self.sun_lamp_data = {} # name -> cached_data
self.sun_lamp_instances = {} # instance_id -> [sun_lamp_data_name, transform_list]
# Motion blur segments are rounded down to a power of two.
if self.scene.psychopath.motion_blur_segments > 0:
self.time_samples = (2**int(log(self.scene.psychopath.motion_blur_segments, 2))) + 1
else:
self.time_samples = 1
# pre-calculate useful values for exporting motion blur
self.shutter_start = scene.psychopath.shutter_start
self.shutter_diff = (scene.psychopath.shutter_end - scene.psychopath.shutter_start) / max(1, (self.time_samples-1))
# pre-calculate useful values for exporting motion blur.
self.shutter_start = self.scene.psychopath.shutter_start
self.shutter_diff = (self.scene.psychopath.shutter_end - self.scene.psychopath.shutter_start) / max(1, (self.time_samples-1))
self.fr = scene.frame_current
self.fr = self.scene.frame_current
def set_frame(self, frame, fraction):
@ -70,25 +86,31 @@ class PsychoExporter:
def _export_psy(self):
# Info
self.w.write("# Exported from Blender 2.7x\n")
self.w.write("# Exported from Blender {} with PsychoBlend {}.{}.{}\n".format(
bpy.app.version_string,
bl_info["version"][0],
bl_info["version"][1],
bl_info["version"][2],
))
# Scene begin
self.w.write("\n\nScene $%s_fr%d {\n" % (escape_name(self.scene.name), self.fr))
self.w.indent()
#######################
# Output section begin
#------------------------------------------------------
# Output section.
self.w.write("Output {\n")
self.w.indent()
self.w.write('Path [""]\n')
# Output section end
self.w.unindent()
self.w.write("}\n")
###############################
# RenderSettings section begin
#------------------------------------------------------
# RenderSettings section.
self.w.write("RenderSettings {\n")
self.w.indent()
@ -99,34 +121,94 @@ class PsychoExporter:
self.w.write("DicingRate [%f]\n" % self.scene.psychopath.dicing_rate)
self.w.write('Seed [%d]\n' % self.fr)
# RenderSettings section end
self.w.unindent()
self.w.write("}\n")
###############################
# Export world and object data
world = None
root_assembly = None
#------------------------------------------------------
# Collect world and object data.
try:
# Prep for data collection
world = World(self.render_engine, self.scene, self.scene.layers, float(res_x) / float(res_y))
root_assembly = Assembly(self.render_engine, self.scene.objects, self.scene.layers)
# Collect data for each time sample
for i in range(self.time_samples):
time = self.fr + self.shutter_start + (self.shutter_diff*i)
self.set_frame(self.fr, self.shutter_start + (self.shutter_diff*i))
world.take_sample(self.render_engine, self.scene, time)
root_assembly.take_sample(self.render_engine, self.scene, time)
# Check if render is cancelled
if self.render_engine.test_break():
raise ExportCancelled()
# Export collected data
world.export(self.render_engine, self.w)
root_assembly.export(self.render_engine, self.w)
finally:
if world != None:
world.cleanup()
if root_assembly != None:
root_assembly.cleanup()
time = self.fr + self.shutter_start + (self.shutter_diff*i)
# Collect camera and world data.
self.camera.take_sample(self.render_engine, self.depsgraph, time)
self.world.take_sample(self.render_engine, self.depsgraph, time)
# Collect renderable objects.
collected_objs = set() # Names of the objects whose data has already been collected.
for inst in self.depsgraph.object_instances:
# Check if render is cancelled
if self.render_engine.test_break():
raise ExportCancelled()
if inst.object.type not in ['MESH', 'LIGHT']:
continue
# We use this a couple of times, so make a shorthand.
is_sun_lamp = inst.object.type == 'LIGHT' and inst.object.data.type == 'SUN'
# Get a unique id for the instance. This is surprisingly
# tricky, because the instance's "persistent_id" property
# isn't globally unique, as I would have expected from
# the documentation.
id = None
if inst.is_instance:
id = (hash((inst.object.name, inst.parent.name)), inst.persistent_id)
else:
id = inst.object.name
# Save the instance transforms.
if is_sun_lamp:
if id not in self.sun_lamp_instances:
self.sun_lamp_instances[id] = [inst.object.name, [inst.matrix_world.copy()]]
else:
self.sun_lamp_instances[id][1] += [inst.matrix_world.copy()]
else:
if id not in self.instances:
self.instances[id] = [inst.object.name, [inst.matrix_world.copy()]]
else:
self.instances[id][1] += [inst.matrix_world.copy()]
# Save the object data if it hasn't already been saved.
if inst.object.name not in collected_objs:
collected_objs.add(inst.object.name)
if is_sun_lamp:
if inst.object.name not in self.sun_lamp_data:
self.sun_lamp_data[inst.object.name] = DistantDiskLamp(self.render_engine, self.depsgraph, inst.object, inst.object.name)
self.sun_lamp_data[inst.object.name].take_sample(self.render_engine, self.depsgraph, inst.object, time)
else:
if inst.object.name not in self.object_data:
self.object_data[inst.object.name] = make_object_data_cache(self.render_engine, self.depsgraph, inst.object, inst.object.name)
self.object_data[inst.object.name].take_sample(self.render_engine, self.depsgraph, inst.object, time)
#------------------------------------------------------
# Export world and object data.
self.camera.export(self.render_engine, self.w)
self.world.export(self.render_engine, self.w)
self.w.write("Assembly {\n")
self.w.indent()
self.w.unindent()
self.w.write("}\n")
finally:
#------------------------------------------------------
# Cleanup collected data.
self.camera.cleanup()
self.world.cleanup()
for data in self.sun_lamp_data:
self.sun_lamp_data[data].cleanup()
for data in self.object_data:
self.object_data[data].cleanup()
# Scene end
self.w.unindent()

View File

@ -18,31 +18,16 @@ class PsychopathRender(bpy.types.RenderEngine):
pass
def update(self, data, depsgraph):
print("Psychopath scene update!")
pass
def render(self, depsgraph):
print("Psychopath render!")
scene = depsgraph.scene
scale = scene.render.resolution_percentage / 100.0
self.size_x = int(scene.render.resolution_x * scale)
self.size_y = int(scene.render.resolution_y * scale)
# Fill the render result with a flat color. The framebuffer is
# defined as a list of pixels, each pixel itself being a list of
# R,G,B,A values.
if self.is_preview:
color = [0.1, 0.2, 0.1, 1.0]
else:
color = [0.2, 0.1, 0.1, 1.0]
pixel_count = self.size_x * self.size_y
rect = [color] * pixel_count
# Here we write the pixel values to the RenderResult
result = self.begin_result(0, 0, self.size_x, self.size_y)
layer = result.layers[0].passes["Combined"]
layer.rect = rect
self.end_result(result)
self._process = None
try:
self._render(depsgraph)
except:
if self._process != None:
self._process.terminate()
raise
def view_update(self, context, depsgraph):
pass
@ -54,7 +39,7 @@ class PsychopathRender(bpy.types.RenderEngine):
@staticmethod
def _locate_binary():
addon_prefs = bpy.context.user_preferences.addons[__package__].preferences
addon_prefs = bpy.context.preferences.addons[__package__].preferences
# Use the system preference if its set.
psy_binary = addon_prefs.filepath_psychopath
@ -126,16 +111,9 @@ class PsychopathRender(bpy.types.RenderEngine):
lay.rect = pixels_flipped
self.end_result(result)
def render(self, scene):
self._process = None
try:
self._render(scene)
except:
if self._process != None:
self._process.terminate()
raise
def _render(self, depsgraph):
scene = depsgraph.scene
def _render(self, scene):
# has to be called to update the frame on exporting animations
scene.frame_set(scene.frame_current)
@ -171,8 +149,8 @@ class PsychopathRender(bpy.types.RenderEngine):
return
self.update_stats("", "Psychopath: Collecting...")
# Export to Psychopath's stdin
if not psy_export.PsychoExporter(self._process.stdin, self, scene).export_psy():
# Export to Psychopath's stdin.
if not psy_export.PsychoExporter(self._process.stdin, self, depsgraph).export_psy():
# Render cancelled in the middle of exporting,
# so just return.
self._process.terminate()
@ -183,7 +161,7 @@ class PsychopathRender(bpy.types.RenderEngine):
# Export to file
self.update_stats("", "Psychopath: Exporting data from Blender")
with open(export_path, 'w+b') as f:
if not psy_export.PsychoExporter(f, self, scene).export_psy():
if not psy_export.PsychoExporter(f, self, depsgraph).export_psy():
# Render cancelled in the middle of exporting,
# so just return.
return
@ -224,7 +202,7 @@ class PsychopathRender(bpy.types.RenderEngine):
# Get render output from stdin
tmp = self._process.stdout.read1(2**16)
if len(tmp) == 0:
time.sleep(0.0001) # Don't spin on the CPU
time.sleep(0.001) # Don't spin on the CPU
if render_process_finished:
all_output_consumed = True
continue

View File

@ -6,47 +6,28 @@ from mathutils import Vector, Matrix
from .util import escape_name, mat2str, ExportCancelled
class World:
def __init__(self, render_engine, scene, visible_layers, aspect_ratio):
def __init__(self, render_engine, depsgraph):
scene = depsgraph.scene
self.background_shader = BackgroundShader(render_engine, scene.world)
self.camera = Camera(render_engine, scene.camera, aspect_ratio)
self.lights = []
# Collect infinite-extent light sources.
# TODO: also get sun lamps inside group instances.
for ob in scene.objects:
if ob.type == 'LAMP' and ob.data.type == 'SUN':
name = escape_name(ob.name)
self.lights += [DistantDiskLamp(ob, name)]
def take_sample(self, render_engine, scene, time):
self.camera.take_sample(render_engine, scene, time)
def take_sample(self, render_engine, depsgraph, time):
if render_engine.test_break():
raise ExportCancelled()
self.background_shader.take_sample(render_engine, depsgraph, time)
for light in self.lights:
# Check if render is cancelled
if render_engine.test_break():
raise ExportCancelled()
light.take_sample(render_engine, scene, time)
def cleanup(self):
pass
def export(self, render_engine, w):
self.camera.export(render_engine, w)
w.write("World {\n")
w.indent()
self.background_shader.export(render_engine, w)
for light in self.lights:
light.export(render_engine, w)
w.unindent()
w.write("}\n")
def cleanup(self):
# For future use. This is run by the calling code when finished,
# even if export did not succeed.
pass
#================================================================
class Camera:
def __init__(self, render_engine, ob, aspect_ratio):
@ -58,25 +39,27 @@ class Camera:
self.focal_distances = []
self.xforms = []
def take_sample(self, render_engine, scene, time):
def take_sample(self, render_engine, depsgraph, time):
render_engine.update_stats("", "Psychopath: Collecting '{}' at time {}".format(self.ob.name, time))
# Fov
if self.aspect_ratio >= 1.0:
self.fovs += [degrees(self.ob.data.angle)]
else:
self.fovs += [degrees(2.0 * atan(tan(self.ob.data.angle * 0.5) * self.aspect_ratio))]
self.fovs += [degrees(self.ob.data.angle_x)]
# Aperture radius
self.aperture_radii += [self.ob.data.psychopath.aperture_radius]
if self.ob.data.dof.use_dof:
# TODO
# # Aperture radius
# self.aperture_radii += [self.ob.data.psychopath.aperture_radius]
# Dof distance
if self.ob.data.dof_object == None:
self.focal_distances += [self.ob.data.dof_distance]
# Dof distance
if self.ob.data.dof_object == None:
self.focal_distances += [self.ob.data.dof_distance]
else:
# TODO: implement DoF object tracking here
self.focal_distances += [0.0]
print("WARNING: DoF object tracking not yet implemented.")
else:
# TODO: implement DoF object tracking here
self.focal_distances += [0.0]
print("WARNING: DoF object tracking not yet implemented.")
self.aperture_radii += [0.0]
self.focal_distances += [1.0]
# Transform
mat = self.ob.matrix_world.copy()
@ -84,6 +67,9 @@ class Camera:
matz[2][2] = -1
self.xforms += [(mat * matz).inverted()]
def cleanup(self):
pass
def export(self, render_engine, w):
render_engine.update_stats("", "Psychopath: Exporting %s" % self.ob.name)
w.write("Camera {\n")
@ -108,55 +94,24 @@ class Camera:
class BackgroundShader:
def __init__(self, render_engine, world):
self.world = world
self.color = []
def take_sample(self, render_engine, depsgraph, time):
if self.world != None:
self.color = (world.horizon_color[0], world.horizon_color[1], world.horizon_color[2])
self.color += [(
self.world.psychopath.background_color[0],
self.world.psychopath.background_color[1],
self.world.psychopath.background_color[2],
)]
def export(self, render_engine, w):
if self.world != None:
w.write("BackgroundShader {\n")
w.indent();
w.write("Type [Color]\n")
w.write("Color [rec709, %f %f %f]\n" % self.color)
for c in self.color:
w.write("Color [rec709, %f %f %f]\n" % c)
w.unindent()
w.write("}\n")
class DistantDiskLamp:
def __init__(self, ob, name):
self.ob = ob
self.name = name
self.time_col = []
self.time_dir = []
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_dir += [tuple(self.ob.matrix_world.to_3x3() * Vector((0, 0, -1)))]
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 export(self, render_engine, w):
render_engine.update_stats("", "Psychopath: Exporting %s" % self.ob.name)
w.write("DistantDiskLight $%s {\n" % self.name)
w.indent()
for direc in self.time_dir:
w.write("Direction [%f %f %f]\n" % (direc[0], direc[1], direc[2]))
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")