Added PsychoBlend exporter to repo.

This commit is contained in:
Nathan Vegdahl 2016-07-10 17:03:50 -07:00
parent e8ee371423
commit 43f2a77264
5 changed files with 1046 additions and 0 deletions

4
.gitignore vendored
View File

@ -1,6 +1,10 @@
target
*.rs.bk
# Python Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
.zedstate
test_renders
perf.data*

154
psychoblend/__init__.py Normal file
View File

@ -0,0 +1,154 @@
bl_info = {
"name": "PsychoBlend",
"version": (0, 1),
"author": "Nathan Vegdahl",
"blender": (2, 70, 0),
"description": "Psychopath renderer integration",
"location": "",
"wiki_url": "https://github.com/cessen/psychopath/wiki",
"tracker_url": "https://github.com/cessen/psychopath/issues",
"category": "Render"}
if "bpy" in locals():
import imp
imp.reload(ui)
imp.reload(psy_export)
imp.reload(render)
else:
from . import ui, psy_export, render
import bpy
from bpy.types import (AddonPreferences,
PropertyGroup,
Operator,
)
from bpy.props import (StringProperty,
BoolProperty,
IntProperty,
FloatProperty,
FloatVectorProperty,
EnumProperty,
PointerProperty,
)
# Custom Scene settings
class RenderPsychopathSettingsScene(PropertyGroup):
spp = IntProperty(
name="Samples Per Pixel", description="Total number of samples to take per pixel",
min=1, max=65536, default=16
)
dicing_rate = FloatProperty(
name="Dicing Rate", description="The target microgeometry width in pixels",
min=0.0001, max=100.0, soft_min=0.125, soft_max=1.0, default=0.25
)
motion_blur_segments = IntProperty(
name="Motion Segments", description="The number of segments to use in motion blur. Zero means no motion blur. Will be rounded down to the nearest power of two.",
min=0, max=256, default=0
)
shutter_start = FloatProperty(
name="Shutter Open", description="The time during the frame that the shutter opens, for motion blur",
min=-1.0, max=1.0, soft_min=0.0, soft_max=1.0, default=0.0
)
shutter_end = FloatProperty(
name="Shutter Close", description="The time during the frame that the shutter closes, for motion blur",
min=-1.0, max=1.0, soft_min=0.0, soft_max=1.0, default=0.5
)
export_path = StringProperty(
name="Export Path", description="The path to where the .psy files should be exported when rendering. If left blank, /tmp or the equivalent is used.",
subtype='FILE_PATH'
)
# Custom Camera properties
class PsychopathCamera(bpy.types.PropertyGroup):
aperture_radius = FloatProperty(
name="Aperture Radius", description="Size of the camera's aperture, for DoF",
min=0.0, max=10000.0, soft_min=0.0, soft_max=2.0, default=0.0
)
# Custom Mesh properties
class PsychopathMesh(bpy.types.PropertyGroup):
is_subdivision_surface = BoolProperty(
name="Is Subdivision Surface", description="Whether this is a sibdivision surface or just a normal mesh",
default=False
)
# Psychopath material
class PsychopathMaterial(bpy.types.PropertyGroup):
surface_shader_type = EnumProperty(
name="Surface Shader Type", description="",
items=[('Emit', 'Emit', ""), ('Lambert', 'Lambert', ""), ('GTR', 'GTR', "")],
default="Lambert"
)
color = FloatVectorProperty(
name="Color", description="",
subtype='COLOR',
min=0.0, soft_min=0.0, soft_max = 1.0,
default=[0.8,0.8,0.8]
)
roughness = FloatProperty(
name="Roughness", description="",
min=-1.0, max=1.0, soft_min=0.0, soft_max=1.0, default=0.1
)
tail_shape = FloatProperty(
name="Tail Shape", description="",
min=0.0, max=8.0, soft_min=1.0, soft_max=3.0, default=2.0
)
fresnel = FloatProperty(
name="Fresnel", description="",
min=0.0, max=1.0, soft_min=0.0, soft_max=1.0, default=0.9
)
# Addon Preferences
class PsychopathPreferences(AddonPreferences):
bl_idname = __name__
filepath_psychopath = StringProperty(
name="Psychopath Location",
description="Path to renderer executable",
subtype='DIR_PATH',
)
def draw(self, context):
layout = self.layout
layout.prop(self, "filepath_psychopath")
##### REGISTER #####
def register():
bpy.utils.register_class(PsychopathPreferences)
bpy.utils.register_class(RenderPsychopathSettingsScene)
bpy.utils.register_class(PsychopathCamera)
bpy.utils.register_class(PsychopathMesh)
bpy.utils.register_class(PsychopathMaterial)
bpy.types.Scene.psychopath = PointerProperty(type=RenderPsychopathSettingsScene)
bpy.types.Camera.psychopath = PointerProperty(type=PsychopathCamera)
bpy.types.Mesh.psychopath = PointerProperty(type=PsychopathMesh)
bpy.types.Material.psychopath = PointerProperty(type=PsychopathMaterial)
render.register()
ui.register()
def unregister():
bpy.utils.unregister_class(PsychopathPreferences)
bpy.utils.unregister_class(RenderPsychopathSettingsScene)
bpy.utils.unregister_class(PsychopathCamera)
bpy.utils.unregister_class(PsychopathMesh)
bpy.utils.unregister_class(PsychopathMaterial)
del bpy.types.Scene.psychopath
del bpy.types.Camera.psychopath
del bpy.types.Mesh.psychopath
del bpy.types.Material.psychopath
render.unregister()
ui.unregister()

465
psychoblend/psy_export.py Normal file
View File

@ -0,0 +1,465 @@
import bpy
from math import degrees, pi, log
from mathutils import Vector, Matrix
def mat2str(m):
""" Converts a matrix into a single-line string of values.
"""
s = ""
for j in range(4):
for i in range(4):
s += (" %f" % m[i][j])
return s[1:]
def needs_def_mb(ob):
""" Determines if the given object needs to be exported with
deformation motion blur or not.
"""
for mod in ob.modifiers:
if mod.type == 'SUBSURF':
pass
elif mod.type == 'MIRROR':
if mod.mirror_object == None:
pass
else:
return True
else:
return True
if ob.type == 'MESH':
if ob.data.shape_keys == None:
pass
else:
return True
return False
def escape_name(name):
name = name.replace("\\", "\\\\")
name = name.replace(" ", "\\ ")
name = name.replace("$", "\\$")
name = name.replace("[", "\\[")
name = name.replace("]", "\\]")
name = name.replace("{", "\\{")
name = name.replace("}", "\\}")
return name
def needs_xform_mb(ob):
""" Determines if the given object needs to be exported with
transformation motion blur or not.
"""
if ob.animation_data != None:
return True
if len(ob.constraints) > 0:
return True
if ob.parent != None:
return needs_xform_mb(ob.parent)
return False
class IndentedWriter:
def __init__(self, file_handle):
self.f = file_handle
self.indent_level = 0
self.indent_size = 4
def indent(self):
self.indent_level += self.indent_size
def unindent(self):
self.indent_level -= self.indent_size
if self.indent_level < 0:
self.indent_level = 0
def write(self, text, do_indent=True):
if do_indent:
self.f.write(' '*self.indent_level + text)
else:
self.f.write(text)
class PsychoExporter:
def __init__(self, scene):
self.scene = scene
self.mesh_names = {}
self.group_names = {}
# 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
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))
self.fr = scene.frame_current
def set_frame(self, frame, fraction):
if fraction >= 0:
self.scene.frame_set(frame, fraction)
else:
self.scene.frame_set(frame-1, 1.0+fraction)
def export_psy(self, export_path, render_image_path):
f = open(export_path, 'w')
self.w = IndentedWriter(f)
# Info
self.w.write("# Exported from Blender 2.7x\n")
# Scene begin
self.w.write("\n\nScene $%s_fr%d {\n" % (escape_name(self.scene.name), self.fr))
self.w.indent()
#######################
# Output section begin
self.w.write("Output {\n")
self.w.indent()
self.w.write('Path ["%s"]\n' % render_image_path)
# Output section end
self.w.unindent()
self.w.write("}\n")
###############################
# RenderSettings section begin
self.w.write("RenderSettings {\n")
self.w.indent()
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.w.write('Resolution [%d %d]\n' % (res_x, res_y))
self.w.write("SamplesPerPixel [%d]\n" % self.scene.psychopath.spp)
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")
#######################
# Camera section begin
self.w.write("Camera {\n")
self.w.indent()
cam = self.scene.camera
if cam.data.dof_object == None:
dof_distance = cam.data.dof_distance
else:
# TODO: implement DoF object tracking here
dof_distance = 0.0
print("WARNING: DoF object tracking not yet implemented.")
matz = Matrix()
matz[2][2] = -1
for i in range(self.time_samples):
if res_x >= res_y:
self.w.write("Fov [%f]\n" % degrees(cam.data.angle))
else:
self.w.write("Fov [%f]\n" % (degrees(cam.data.angle) * res_x / res_y))
self.w.write("FocalDistance [%f]\n" % dof_distance)
self.w.write("ApertureRadius [%f]\n" % (cam.data.psychopath.aperture_radius))
self.set_frame(self.fr, self.shutter_start + (self.shutter_diff*i))
mat = cam.matrix_world.copy()
mat = mat * matz
self.w.write("Transform [%s]\n" % mat2str(mat))
# Camera section end
self.w.unindent()
self.w.write("}\n")
#######################
# World section begin
self.w.write("World {\n")
self.w.indent()
world = self.scene.world
if world != None:
self.w.write("BackgroundShader {\n")
self.w.indent();
self.w.write("Type [Color]\n")
self.w.write("Color [%f %f %f]\n" % (world.horizon_color[0], world.horizon_color[1], world.horizon_color[2]))
self.w.unindent();
self.w.write("}\n")
# World section end
self.w.unindent()
self.w.write("}\n")
#######################
# Export objects and materials
# TODO: handle materials from linked files (as used in group
# instances) properly.
self.w.write("Assembly {\n")
self.w.indent()
self.export_materials(bpy.data.materials)
self.export_objects(self.scene.objects, self.scene.layers)
self.w.unindent()
self.w.write("}\n")
# Scene end
self.w.unindent()
self.w.write("}\n")
# Cleanup
f.close()
self.scene.frame_set(self.fr)
def export_materials(self, materials):
for m in materials:
self.w.write("SurfaceShader $%s {\n" % escape_name(m.name))
self.w.indent()
self.w.write("Type [%s]\n" % m.psychopath.surface_shader_type)
self.w.write("Color [%f %f %f]\n" % (m.psychopath.color[0], m.psychopath.color[1], m.psychopath.color[2]))
if m.psychopath.surface_shader_type == 'GTR':
self.w.write("Roughness [%f]\n" % m.psychopath.roughness)
self.w.write("TailShape [%f]\n" % m.psychopath.tail_shape)
self.w.write("Fresnel [%f]\n" % m.psychopath.fresnel)
self.w.unindent()
self.w.write("}\n")
def export_objects(self, objects, visible_layers, group_prefix="", translation_offset=(0,0,0)):
for ob in objects:
# 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
name = None
# Write object data
if ob.type == 'EMPTY':
if ob.dupli_type == 'GROUP':
name = group_prefix + "__" + escape_name(ob.dupli_group.name)
if name not in self.group_names:
self.group_names[name] = True
self.w.write("Assembly $%s {\n" % name)
self.w.indent()
self.export_objects(ob.dupli_group.objects, ob.dupli_group.layers, name, ob.dupli_group.dupli_offset*-1)
self.w.unindent()
self.w.write("}\n")
elif ob.type == 'MESH':
name = self.export_mesh_object(ob, group_prefix)
elif ob.type == 'SURFACE':
name = self.export_surface_object(ob, group_prefix)
elif ob.type == 'LAMP' and ob.data.type == 'POINT':
name = self.export_sphere_lamp(ob, group_prefix)
elif ob.type == 'LAMP' and ob.data.type == 'AREA':
name = self.export_area_lamp(ob, group_prefix)
# Write object instance, with transforms
if name != None:
time_mats = []
if needs_xform_mb(ob):
for i in range(self.time_samples):
self.set_frame(self.fr, self.shutter_start + (self.shutter_diff*i))
mat = ob.matrix_world.copy()
mat[0][3] += translation_offset[0]
mat[1][3] += translation_offset[1]
mat[2][3] += translation_offset[2]
time_mats += [mat]
else:
mat = ob.matrix_world.copy()
mat[0][3] += translation_offset[0]
mat[1][3] += translation_offset[1]
mat[2][3] += translation_offset[2]
time_mats += [mat]
self.w.write("Instance {\n")
self.w.indent()
self.w.write("Data [$%s]\n" % name)
if len(ob.material_slots) > 0 and ob.material_slots[0].material != None:
self.w.write("SurfaceShaderBind [$%s]\n" % escape_name(ob.material_slots[0].material.name))
for i in range(len(time_mats)):
mat = time_mats[i].inverted()
self.w.write("Transform [%s]\n" % mat2str(mat))
self.w.unindent()
self.w.write("}\n")
def export_mesh_object(self, ob, group_prefix):
# Determine if and how to export the mesh data
has_modifiers = len(ob.modifiers) > 0
deform_mb = needs_def_mb(ob)
if has_modifiers or deform_mb:
mesh_name = group_prefix + "__" + ob.name + "__" + ob.data.name + "_"
else:
mesh_name = group_prefix + "__" + ob.data.name + "_"
export_mesh = (mesh_name not in self.mesh_names) or has_modifiers or deform_mb
# Collect time samples
time_meshes = []
for i in range(self.time_samples):
self.set_frame(self.fr, self.shutter_start + (self.shutter_diff*i))
if export_mesh and (deform_mb or i == 0):
time_meshes += [ob.to_mesh(self.scene, True, 'RENDER')]
# Export mesh data if necessary
if export_mesh and ob.data.psychopath.is_subdivision_surface == False:
# Exporting normal mesh
self.mesh_names[mesh_name] = True
self.w.write("Assembly $%s {\n" % escape_name(mesh_name))
self.w.indent()
# Write patches
polys = time_meshes[0].polygons
face_count = 0
for poly in polys:
face_count += 1
if len(poly.vertices) == 4:
# Object
self.w.write("BilinearPatch $%s.%d {\n" % (escape_name(mesh_name), face_count))
self.w.indent()
for i in range(len(time_meshes)):
verts = time_meshes[i].vertices
vstr = ""
for vi in [poly.vertices[0], poly.vertices[1], poly.vertices[3], poly.vertices[2]]:
v = verts[vi].co
vstr += ("%f %f %f " % (v[0], v[1], v[2]))
self.w.write("Vertices [%s]\n" % vstr[:-1])
self.w.unindent()
self.w.write("}\n")
# Instance
self.w.write("Instance {\n")
self.w.indent()
self.w.write("Data [$%s.%d]\n" % (escape_name(mesh_name), face_count))
self.w.unindent()
self.w.write("}\n")
for m in time_meshes:
bpy.data.meshes.remove(m)
# Assembly section end
self.w.unindent()
self.w.write("}\n")
elif export_mesh and ob.data.psychopath.is_subdivision_surface == True:
# Exporting subdivision surface cage
self.mesh_names[mesh_name] = True
self.w.write("SubdivisionSurface $%s {\n" % escape_name(mesh_name))
self.w.indent()
# Write vertices
for ti in range(len(time_meshes)):
self.w.write("Vertices [")
for v in time_meshes[ti].vertices:
self.w.write("%f %f %f " % (v.co[0], v.co[1], v.co[2]), False)
self.w.write("]\n", False)
# Write face vertex counts
self.w.write("FaceVertCounts [")
for p in time_meshes[0].polygons:
self.w.write("%d " % len(p.vertices), False)
self.w.write("]\n", False)
# Write face vertex indices
self.w.write("FaceVertIndices [")
for p in time_meshes[0].polygons:
for v in p.vertices:
self.w.write("%d " % v, False)
self.w.write("]\n", False)
# SubdivisionSurface section end
self.w.unindent()
self.w.write("}\n")
return mesh_name
def export_surface_object(self, ob, group_prefix):
name = group_prefix + "__" + escape_name(ob.name)
# Collect time samples
time_surfaces = []
for i in range(self.time_samples):
self.set_frame(self.fr, self.shutter_start + (self.shutter_diff*i))
time_surfaces += [ob.data.copy()]
# Write patch
self.w.write("BicubicPatch $" + name + " {\n")
self.w.indent()
for i in range(self.time_samples):
verts = time_surfaces[i].splines[0].points
vstr = ""
for v in verts:
vstr += ("%f %f %f " % (v.co[0], v.co[1], v.co[2]))
self.w.write("Vertices [%s]\n" % vstr[:-1])
for s in time_surfaces:
bpy.data.curves.remove(s)
self.w.unindent()
self.w.write("}\n")
return name
def export_sphere_lamp(self, ob, group_prefix):
name = group_prefix + "__" + escape_name(ob.name)
# Collect data over time
time_col = []
time_rad = []
for i in range(self.time_samples):
self.set_frame(self.fr, self.shutter_start + (self.shutter_diff*i))
time_col += [ob.data.color * ob.data.energy]
time_rad += [ob.data.shadow_soft_size]
# Write out sphere light
self.w.write("SphereLight $%s {\n" % name)
self.w.indent()
for col in time_col:
self.w.write("Color [%f %f %f]\n" % (col[0], col[1], col[2]))
for rad in time_rad:
self.w.write("Radius [%f]\n" % rad)
self.w.unindent()
self.w.write("}\n")
return name
def export_area_lamp(self, ob, group_prefix):
name = group_prefix + "__" + escape_name(ob.name)
# Collect data over time
time_col = []
time_dim = []
for i in range(self.time_samples):
self.set_frame(self.fr, self.shutter_start + (self.shutter_diff*i))
time_col += [ob.data.color * ob.data.energy]
if ob.data.shape == 'RECTANGLE':
time_dim += [(ob.data.size, ob.data.size_y)]
else:
time_dim += [(ob.data.size, ob.data.size)]
# Write out sphere light
self.w.write("RectangleLight $%s {\n" % name)
self.w.indent()
for col in time_col:
self.w.write("Color [%f %f %f]\n" % (col[0], col[1], col[2]))
for dim in time_dim:
self.w.write("Dimensions [%f %f]\n" % dim)
self.w.unindent()
self.w.write("}\n")
return name

153
psychoblend/render.py Normal file
View File

@ -0,0 +1,153 @@
import bpy
import time
import os
import subprocess
import tempfile
from . import psy_export
def get_temp_filename(suffix=""):
tmpf = tempfile.mkstemp(suffix=suffix, prefix='tmp')
os.close(tmpf[0])
return(tmpf[1])
class PsychopathRender(bpy.types.RenderEngine):
bl_idname = 'PSYCHOPATH_RENDER'
bl_label = "Psychopath"
DELAY = 1.0
@staticmethod
def _locate_binary():
addon_prefs = bpy.context.user_preferences.addons[__package__].preferences
# Use the system preference if its set.
psy_binary = addon_prefs.filepath_psychopath
if psy_binary:
if os.path.exists(psy_binary):
return psy_binary
else:
print("User Preference to psychopath %r NOT FOUND, checking $PATH" % psy_binary)
# search the path all os's
psy_binary_default = "psychopath"
os_path_ls = os.getenv("PATH").split(':') + [""]
for dir_name in os_path_ls:
psy_binary = os.path.join(dir_name, psy_binary_default)
if os.path.exists(psy_binary):
return psy_binary
return ""
def _export(self, scene, export_path, render_image_path):
exporter = psy_export.PsychoExporter(scene)
exporter.export_psy(export_path, render_image_path)
def _render(self, scene, psy_filepath):
psy_binary = PsychopathRender._locate_binary()
if not psy_binary:
print("Psychopath: could not execute psychopath, possibly Psychopath isn't installed")
return False
# TODO: figure out command line options
args = ["-i", psy_filepath]
# Start Rendering!
try:
self._process = subprocess.Popen([psy_binary] + args,
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
except OSError:
# TODO, report api
print("Psychopath: could not execute '%s'" % psy_binary)
import traceback
traceback.print_exc()
print ("***-DONE-***")
return False
return True
def _cleanup(self):
# for f in (self._temp_file_in, self._temp_file_ini, self._temp_file_out):
# for i in range(5):
# try:
# os.unlink(f)
# break
# except OSError:
# # Wait a bit before retrying file might be still in use by Blender,
# # and Windows does not know how to delete a file in use!
# time.sleep(self.DELAY)
# for i in unpacked_images:
# for c in range(5):
# try:
# os.unlink(i)
# break
# except OSError:
# # Wait a bit before retrying file might be still in use by Blender,
# # and Windows does not know how to delete a file in use!
# time.sleep(self.DELAY)
pass
def render(self, scene):
# has to be called to update the frame on exporting animations
scene.frame_set(scene.frame_current)
export_path = scene.psychopath.export_path
if export_path != "":
export_path += "_%d.psy" % scene.frame_current
else:
# Create a temporary file for exporting
export_path = get_temp_filename('.psy')
# Create a temporary file to render into
render_image_path = get_temp_filename('.png')
# start export
self.update_stats("", "Psychopath: Exporting data from Blender")
self._export(scene, export_path, render_image_path)
# Start rendering
self.update_stats("", "Psychopath: Rendering from exported file")
if not self._render(scene, export_path):
self.update_stats("", "Psychopath: Not found")
return
r = scene.render
# compute resolution
x = int(r.resolution_x * r.resolution_percentage)
y = int(r.resolution_y * r.resolution_percentage)
result = self.begin_result(0, 0, x, y)
lay = result.layers[0]
# TODO: Update viewport with render result while rendering
while self._process.poll() == None:
# Wait for self.DELAY seconds, but check for render cancels
# while waiting.
t = 0.0
while t < self.DELAY:
if self.test_break():
self._process.terminate()
break
time.sleep(0.05)
t += 0.05
# # Update viewport image with latest render output
# if os.path.exists(render_image_path):
# # This assumes the file has been fully written We wait a bit, just in case!
# try:
# lay.load_from_file(render_image_path)
# self.update_result(result)
# except RuntimeError:
# pass
# Load final image
lay.load_from_file(render_image_path)
self.end_result(result)
# Delete temporary image file
os.remove(render_image_path)
def register():
bpy.utils.register_class(PsychopathRender)
def unregister():
bpy.utils.unregister_class(PsychopathRender)

270
psychoblend/ui.py Normal file
View File

@ -0,0 +1,270 @@
import bpy
# Use some of the existing buttons.
from bl_ui import properties_render
properties_render.RENDER_PT_render.COMPAT_ENGINES.add('PSYCHOPATH_RENDER')
properties_render.RENDER_PT_dimensions.COMPAT_ENGINES.add('PSYCHOPATH_RENDER')
properties_render.RENDER_PT_output.COMPAT_ENGINES.add('PSYCHOPATH_RENDER')
del properties_render
from bl_ui import properties_data_camera
properties_data_camera.DATA_PT_lens.COMPAT_ENGINES.add('PSYCHOPATH_RENDER')
properties_data_camera.DATA_PT_camera.COMPAT_ENGINES.add('PSYCHOPATH_RENDER')
properties_data_camera.DATA_PT_camera_display.COMPAT_ENGINES.add('PSYCHOPATH_RENDER')
properties_data_camera.DATA_PT_custom_props_camera.COMPAT_ENGINES.add('PSYCHOPATH_RENDER')
del properties_data_camera
class PsychopathPanel():
COMPAT_ENGINES = {'PSYCHOPATH_RENDER'}
@classmethod
def poll(cls, context):
rd = context.scene.render
return (rd.use_game_engine is False) and (rd.engine in cls.COMPAT_ENGINES)
class RENDER_PT_psychopath_render_settings(PsychopathPanel, bpy.types.Panel):
bl_label = "Render Settings"
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
bl_context = "render"
def draw(self, context):
scene = context.scene
layout = self.layout
col = layout.column()
col.label(text="Sampling")
col.prop(scene.psychopath, "spp")
col.label(text="Dicing")
col.prop(scene.psychopath, "dicing_rate")
col.label(text="Motion Blur")
col.prop(scene.psychopath, "motion_blur_segments")
col.prop(scene.psychopath, "shutter_start")
col.prop(scene.psychopath, "shutter_end")
class RENDER_PT_psychopath_export_settings(PsychopathPanel, bpy.types.Panel):
bl_label = "Export Settings"
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
bl_context = "render"
def draw(self, context):
scene = context.scene
layout = self.layout
col = layout.column()
col.prop(scene.psychopath, "export_path")
class WORLD_PT_psychopath_background(PsychopathPanel, bpy.types.Panel):
bl_label = "Background"
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
bl_context = "world"
@classmethod
def poll(cls, context):
return context.world and PsychopathPanel.poll(context)
def draw(self, context):
layout = self.layout
world = context.world
layout.prop(world, "horizon_color", text="Color")
class DATA_PT_psychopath_camera_dof(PsychopathPanel, bpy.types.Panel):
bl_label = "Depth of Field"
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
bl_context = "data"
@classmethod
def poll(cls, context):
engine = context.scene.render.engine
return context.camera and PsychopathPanel.poll(context)
def draw(self, context):
ob = context.active_object
layout = self.layout
col = layout.column()
col.prop(ob.data, "dof_object")
col.prop(ob.data, "dof_distance")
col.prop(ob.data.psychopath, "aperture_radius")
class DATA_PT_psychopath_lamp(PsychopathPanel, bpy.types.Panel):
bl_label = "Lamp"
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
bl_context = "data"
@classmethod
def poll(cls, context):
engine = context.scene.render.engine
return context.lamp and PsychopathPanel.poll(context)
def draw(self, context):
ob = context.active_object
layout = self.layout
col = layout.column()
row = col.row()
row.prop(ob.data, "type", expand=True)
if ob.data.type != 'HEMI' and ob.data.type != 'AREA':
col.prop(ob.data, "shadow_soft_size")
col.prop(ob.data, "color")
col.prop(ob.data, "energy")
class DATA_PT_psychopath_area_lamp(PsychopathPanel, bpy.types.Panel):
bl_label = "Area Shape"
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
bl_context = "data"
@classmethod
def poll(cls, context):
lamp = context.lamp
engine = context.scene.render.engine
return (lamp and lamp.type == 'AREA') and (engine in cls.COMPAT_ENGINES)
def draw(self, context):
layout = self.layout
lamp = context.lamp
col = layout.column()
col.row().prop(lamp, "shape", expand=True)
sub = col.row(align=True)
if lamp.shape == 'SQUARE':
sub.prop(lamp, "size")
elif lamp.shape == 'RECTANGLE':
sub.prop(lamp, "size", text="Size X")
sub.prop(lamp, "size_y", text="Size Y")
class DATA_PT_psychopath_mesh(PsychopathPanel, bpy.types.Panel):
bl_label = "Psychopath Mesh Properties"
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
bl_context = "data"
@classmethod
def poll(cls, context):
engine = context.scene.render.engine
return context.mesh and (engine in cls.COMPAT_ENGINES)
def draw(self, context):
layout = self.layout
mesh = context.mesh
layout.row().prop(mesh.psychopath, "is_subdivision_surface")
class MATERIAL_PT_psychopath_context_material(PsychopathPanel, bpy.types.Panel):
bl_label = ""
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "material"
bl_options = {'HIDE_HEADER'}
@classmethod
def poll(cls, context):
return (context.material or context.object) and PsychopathPanel.poll(context)
def draw(self, context):
layout = self.layout
mat = context.material
ob = context.object
slot = context.material_slot
space = context.space_data
if ob:
row = layout.row()
row.template_list("MATERIAL_UL_matslots", "", ob, "material_slots", ob, "active_material_index", rows=1)
col = row.column(align=True)
col.operator("object.material_slot_add", icon='ZOOMIN', text="")
col.operator("object.material_slot_remove", icon='ZOOMOUT', text="")
col.menu("MATERIAL_MT_specials", icon='DOWNARROW_HLT', text="")
if ob.mode == 'EDIT':
row = layout.row(align=True)
row.operator("object.material_slot_assign", text="Assign")
row.operator("object.material_slot_select", text="Select")
row.operator("object.material_slot_deselect", text="Deselect")
split = layout.split(percentage=0.65)
if ob:
split.template_ID(ob, "active_material", new="material.new")
row = split.row()
if slot:
row.prop(slot, "link", text="")
else:
row.label()
elif mat:
split.template_ID(space, "pin_id")
split.separator()
class MATERIAL_PT_psychopath_surface(PsychopathPanel, bpy.types.Panel):
bl_label = "Surface"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "material"
@classmethod
def poll(cls, context):
return context.material and PsychopathPanel.poll(context)
def draw(self, context):
layout = self.layout
mat = context.material
layout.prop(mat.psychopath, "surface_shader_type")
layout.prop(mat.psychopath, "color")
if mat.psychopath.surface_shader_type == 'GTR':
layout.prop(mat.psychopath, "roughness")
layout.prop(mat.psychopath, "tail_shape")
layout.prop(mat.psychopath, "fresnel")
def register():
bpy.utils.register_class(RENDER_PT_psychopath_render_settings)
bpy.utils.register_class(RENDER_PT_psychopath_export_settings)
bpy.utils.register_class(WORLD_PT_psychopath_background)
bpy.utils.register_class(DATA_PT_psychopath_camera_dof)
bpy.utils.register_class(DATA_PT_psychopath_mesh)
bpy.utils.register_class(DATA_PT_psychopath_lamp)
bpy.utils.register_class(DATA_PT_psychopath_area_lamp)
bpy.utils.register_class(MATERIAL_PT_psychopath_context_material)
bpy.utils.register_class(MATERIAL_PT_psychopath_surface)
def unregister():
bpy.utils.unregister_class(RENDER_PT_psychopath_render_settings)
bpy.utils.unregister_class(RENDER_PT_psychopath_export_settings)
bpy.utils.unregister_class(WORLD_PT_psychopath_background)
bpy.utils.unregister_class(DATA_PT_psychopath_camera_dof)
bpy.utils.register_class(DATA_PT_psychopath_mesh)
bpy.utils.unregister_class(DATA_PT_psychopath_lamp)
bpy.utils.unregister_class(DATA_PT_psychopath_area_lamp)
bpy.utils.unregister_class(MATERIAL_PT_psychopath_context_material)
bpy.utils.unregister_class(MATERIAL_PT_psychopath_surface)