diff --git a/tools/blender/render_mitsuba/__init__.py b/tools/blender/render_mitsuba/__init__.py new file mode 100644 index 00000000..cdd68650 --- /dev/null +++ b/tools/blender/render_mitsuba/__init__.py @@ -0,0 +1,130 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +bl_addon_info = { + "name": "Mitsuba", + "author": "Wenzel Jakob", + "version": (0,1), + "blender": (2, 5, 5), + "api": 31667, + "location": "Info Header (engine dropdown)", + "description": "Basic Mitsuba integration for blender", + "warning": "", + "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.5/Py/"\ + "Scripts/Render/Mitsuba", + "tracker_url": "Unavailable", + "category": "Render"} + + +if "bpy" in locals(): + reload(render) + reload(ui) + +else: + import bpy + from bpy.props import * + from render_mitsuba import render + from render_mitsuba import ui + + +def register(): + Scene = bpy.types.Scene + + # Not a real pov option, just to know if we should write + Scene.mts_radio_enable = BoolProperty( + name="Enable Radiosity", + description="Enable mitsubas radiosity calculation", + default=False) + Scene.mts_radio_display_advanced = BoolProperty( + name="Advanced Options", + description="Show advanced options", + default=False) + + # Real pov options + Scene.mts_radio_adc_bailout = FloatProperty( + name="ADC Bailout", description="The adc_bailout for radiosity rays. Use adc_bailout = 0.01 / brightest_ambient_object for good results", + min=0.0, max=1000.0, soft_min=0.0, soft_max=1.0, default=0.01) + + Scene.mts_radio_always_sample = BoolProperty( + name="Always Sample", description="Only use the data from the pretrace step and not gather any new samples during the final radiosity pass", + default=True) + + Scene.mts_radio_brightness = FloatProperty( + name="Brightness", description="Amount objects are brightened before being returned upwards to the rest of the system", + min=0.0, max=1000.0, soft_min=0.0, soft_max=10.0, default=1.0) + + Scene.mts_radio_count = IntProperty( + name="Ray Count", description="Number of rays that are sent out whenever a new radiosity value has to be calculated", + min=1, max=1600, default=35) + + Scene.mts_radio_error_bound = FloatProperty( + name="Error Bound", description="One of the two main speed/quality tuning values, lower values are more accurate", + min=0.0, max=1000.0, soft_min=0.1, soft_max=10.0, default=1.8) + + Scene.mts_radio_gray_threshold = FloatProperty( + name="Gray Threshold", description="One of the two main speed/quality tuning values, lower values are more accurate", + min=0.0, max=1.0, soft_min=0, soft_max=1, default=0.0) + + Scene.mts_radio_low_error_factor = FloatProperty( + name="Low Error Factor", description="If you calculate just enough samples, but no more, you will get an image which has slightly blotchy lighting", + min=0.0, max=1.0, soft_min=0.0, soft_max=1.0, default=0.5) + + # max_sample - not available yet + Scene.mts_radio_media = BoolProperty( + name="Media", description="Radiosity estimation can be affected by media", + default=False) + + Scene.mts_radio_minimum_reuse = FloatProperty( + name="Minimum Reuse", description="Fraction of the screen width which sets the minimum radius of reuse for each sample point (At values higher than 2% expect errors)", + min=0.0, max=1.0, soft_min=0.1, soft_max=0.1, default=0.015) + + Scene.mts_radio_nearest_count = IntProperty( + name="Nearest Count", description="Number of old ambient values blended together to create a new interpolated value", + min=1, max=20, default=5) + + Scene.mts_radio_normal = BoolProperty( + name="Normals", description="Radiosity estimation can be affected by normals", + default=False) + + Scene.mts_radio_recursion_limit = IntProperty( + name="Recursion Limit", description="how many recursion levels are used to calculate the diffuse inter-reflection", + min=1, max=20, default=3) + + +def unregister(): + import bpy + Scene = bpy.types.Scene + + del Scene.mts_radio_enable + del Scene.mts_radio_display_advanced + del Scene.mts_radio_adc_bailout + del Scene.mts_radio_always_sample + del Scene.mts_radio_brightness + del Scene.mts_radio_count + del Scene.mts_radio_error_bound + del Scene.mts_radio_gray_threshold + del Scene.mts_radio_low_error_factor + del Scene.mts_radio_media + del Scene.mts_radio_minimum_reuse + del Scene.mts_radio_nearest_count + del Scene.mts_radio_normal + del Scene.mts_radio_recursion_limit + + +if __name__ == "__main__": + register() diff --git a/tools/blender/render_mitsuba/render.py b/tools/blender/render_mitsuba/render.py new file mode 100644 index 00000000..f334c99c --- /dev/null +++ b/tools/blender/render_mitsuba/render.py @@ -0,0 +1,160 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +import bpy +import subprocess +import os +import sys +import math +import copy +import shutil +import time + +# Basic Mitsuba integration based on the POV-Ray add-on +# Piggybacks on the COLLADA exporter to get most things done +class MitsubaRender(bpy.types.RenderEngine): + bl_idname = 'MITSUBA_RENDER' + bl_label = "Mitsuba" + + def _export(self, scene): + import tempfile + self._temp_dir = tempfile.mkdtemp(prefix='mitsuba_'); + self._temp_dae = os.path.join(self._temp_dir, 'scene.dae') + self._temp_xml = os.path.join(self._temp_dir, 'scene.xml') + self._temp_adj = os.path.join(self._temp_dir, 'scene_adjustments.xml') + self._temp_out = os.path.join(self._temp_dir, 'scene.png') + print("MtsBlend: Writing COLLADA file") + while True: + try: + bpy.ops.wm.collada_export(filepath=self._temp_dae, check_existing=False) + break + except SystemError: + # Weird SystemError (Operator bpy.ops.wm.collada_export.poll() + # failed, context is incorrect) -> try again + print("MtsBlend: Retrying") + time.sleep(0.1) + print("MtsBlend: Writing adjustments file") + adjfile = open(self._temp_adj, 'w') + adjfile.write('\n'); + adjfile.write('\n'); + adjfile.close() + + def _render(self): + mts_path = '/home/wenzel/mitsuba' + mtsimport_binary = os.path.join(mts_path, "mtsimport") + mitsuba_binary = os.path.join(mts_path, "mitsuba") + mts_render_libpath = os.path.join(mts_path, "src/librender") + mts_core_libpath = os.path.join(mts_path, "src/libcore") + mts_hw_libpath = os.path.join(mts_path, "src/libhw") + env = copy.copy(os.environ) + env['LD_LIBRARY_PATH'] = mts_core_libpath + ":" + mts_render_libpath + ":" + mts_hw_libpath + scene = bpy.data.scenes[0] + render = scene.render + width = int(render.resolution_x * render.resolution_percentage * 0.01) + height = int(render.resolution_y * render.resolution_percentage * 0.01) + + try: + print("MtsBlend: Launching mtsimport") + process = subprocess.Popen( + [mtsimport_binary, '-s', '-r', '%dx%d' % (width, height), + '-l', 'pngfilm', + self._temp_dae, self._temp_xml, + self._temp_adj], + env = env, + cwd = self._temp_dir + ) + if process.wait() != 0: + print("MtsBlend: mtsimport returned with a nonzero status") + return False + + self._process = subprocess.Popen( + [mitsuba_binary, self._temp_xml, '-o', self._temp_out], + env = env, + cwd = self._temp_dir + ) + except OSError: + print("MtsBlend: Could not execute '%s', possibly Mitsuba isn't installed" % mtsimport_binary) + return False + + return True + + def _cleanup(self): + #shutil.rmtree(self._temp_dir) + print("Not cleaning up") + + def render(self, scene): + self._export(scene) + if not self._render(): + self.update_stats("", "MtsBlend: Unable to render (please check the console)") + return + + r = scene.render + x = int(r.resolution_x * r.resolution_percentage * 0.01) + y = int(r.resolution_y * r.resolution_percentage * 0.01) + DELAY = 0.02 + + # Wait for the file to be created + while not os.path.exists(self._temp_out): + if self.test_break(): + try: + self._process.terminate() + except: + pass + break + + if self._process.poll() != None: + self.update_stats("", "MtsBlend: Failed to render (check console)") + break + + time.sleep(DELAY) + + if os.path.exists(self._temp_out): + self.update_stats("", "MtsBlend: Rendering") + prev_size = -1 + + def update_image(): + result = self.begin_result(0, 0, x, y) + layer = result.layers[0] + try: + print("Loading %s" % self._temp_out) + layer.load_from_file(self._temp_out) + except: + pass + self.end_result(result) + + while True: + if self._process.poll() is not None: + update_image() + break + + if self.test_break(): + try: + self._process.terminate() + except: + pass + + break + + new_size = os.path.getsize(self._temp_out) + if new_size != prev_size: + update_image() + prev_size = new_size + + time.sleep(DELAY) + + self._cleanup() diff --git a/tools/blender/render_mitsuba/ui.py b/tools/blender/render_mitsuba/ui.py new file mode 100644 index 00000000..07d143e7 --- /dev/null +++ b/tools/blender/render_mitsuba/ui.py @@ -0,0 +1,136 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +import bpy + +# Use some of the existing buttons. +import properties_render +properties_render.RENDER_PT_render.COMPAT_ENGINES.add('MITSUBA_RENDER') +properties_render.RENDER_PT_dimensions.COMPAT_ENGINES.add('MITSUBA_RENDER') +properties_render.RENDER_PT_antialiasing.COMPAT_ENGINES.add('MITSUBA_RENDER') +properties_render.RENDER_PT_output.COMPAT_ENGINES.add('MITSUBA_RENDER') +del properties_render + +# Use only a subset of the world panels +import properties_world +properties_world.WORLD_PT_preview.COMPAT_ENGINES.add('MITSUBA_RENDER') +properties_world.WORLD_PT_context_world.COMPAT_ENGINES.add('MITSUBA_RENDER') +properties_world.WORLD_PT_world.COMPAT_ENGINES.add('MITSUBA_RENDER') +properties_world.WORLD_PT_mist.COMPAT_ENGINES.add('MITSUBA_RENDER') +del properties_world + +# Example of wrapping every class 'as is' +import properties_material +for member in dir(properties_material): + subclass = getattr(properties_material, member) + try: + subclass.COMPAT_ENGINES.add('MITSUBA_RENDER') + except: + pass +del properties_material + +import properties_data_mesh +for member in dir(properties_data_mesh): + subclass = getattr(properties_data_mesh, member) + try: + subclass.COMPAT_ENGINES.add('MITSUBA_RENDER') + except: + pass +del properties_data_mesh + +import properties_texture +for member in dir(properties_texture): + subclass = getattr(properties_texture, member) + try: + subclass.COMPAT_ENGINES.add('MITSUBA_RENDER') + except: + pass +del properties_texture + +import properties_data_camera +for member in dir(properties_data_camera): + subclass = getattr(properties_data_camera, member) + try: + subclass.COMPAT_ENGINES.add('MITSUBA_RENDER') + except: + pass +del properties_data_camera + + + +class RenderButtonsPanel(): + bl_space_type = 'PROPERTIES' + bl_region_type = 'WINDOW' + bl_context = "render" + # COMPAT_ENGINES must be defined in each subclass, external engines can add themselves here + + @classmethod + def poll(cls, context): + rd = context.scene.render + return (rd.use_game_engine == False) and (rd.engine in cls.COMPAT_ENGINES) + + +class RENDER_PT_mitsuba_radiosity(RenderButtonsPanel, bpy.types.Panel): + bl_label = "Radiosity" + COMPAT_ENGINES = {'MITSUBA_RENDER'} + + def draw_header(self, context): + scene = context.scene + + self.layout.prop(scene, "mts_radio_enable", text="") + + def draw(self, context): + layout = self.layout + + scene = context.scene + rd = scene.render + + layout.active = scene.mts_radio_enable + + split = layout.split() + + col = split.column() + col.prop(scene, "mts_radio_count", text="Rays") + col.prop(scene, "mts_radio_recursion_limit", text="Recursions") + col = split.column() + col.prop(scene, "mts_radio_error_bound", text="Error") + + layout.prop(scene, "mts_radio_display_advanced") + + if scene.mts_radio_display_advanced: + split = layout.split() + + col = split.column() + col.prop(scene, "mts_radio_adc_bailout", slider=True) + col.prop(scene, "mts_radio_gray_threshold", slider=True) + col.prop(scene, "mts_radio_low_error_factor", slider=True) + + col = split.column() + col.prop(scene, "mts_radio_brightness") + col.prop(scene, "mts_radio_minimum_reuse", text="Min Reuse") + col.prop(scene, "mts_radio_nearest_count") + + split = layout.split() + + col = split.column() + col.label(text="Estimation Influence:") + col.prop(scene, "mts_radio_media") + col.prop(scene, "mts_radio_normal") + + col = split.column() + col.prop(scene, "mts_radio_always_sample")