From 43f2a77264ebb56c874e07d71d297266eba04ade Mon Sep 17 00:00:00 2001 From: Nathan Vegdahl Date: Sun, 10 Jul 2016 17:03:50 -0700 Subject: [PATCH] Added PsychoBlend exporter to repo. --- .gitignore | 4 + psychoblend/__init__.py | 154 +++++++++++++ psychoblend/psy_export.py | 465 ++++++++++++++++++++++++++++++++++++++ psychoblend/render.py | 153 +++++++++++++ psychoblend/ui.py | 270 ++++++++++++++++++++++ 5 files changed, 1046 insertions(+) create mode 100644 psychoblend/__init__.py create mode 100644 psychoblend/psy_export.py create mode 100644 psychoblend/render.py create mode 100644 psychoblend/ui.py diff --git a/.gitignore b/.gitignore index 1734597..476d4d7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,10 @@ target *.rs.bk +# Python Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] + .zedstate test_renders perf.data* diff --git a/psychoblend/__init__.py b/psychoblend/__init__.py new file mode 100644 index 0000000..45666c3 --- /dev/null +++ b/psychoblend/__init__.py @@ -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() diff --git a/psychoblend/psy_export.py b/psychoblend/psy_export.py new file mode 100644 index 0000000..2cd8bcb --- /dev/null +++ b/psychoblend/psy_export.py @@ -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 + diff --git a/psychoblend/render.py b/psychoblend/render.py new file mode 100644 index 0000000..4addb22 --- /dev/null +++ b/psychoblend/render.py @@ -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) diff --git a/psychoblend/ui.py b/psychoblend/ui.py new file mode 100644 index 0000000..0b0c452 --- /dev/null +++ b/psychoblend/ui.py @@ -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)