stab at a Blender plugin that piggybacks on the collada exporter (based on the Pov-Ray exporter)

metadata
Wenzel Jakob 2010-11-10 12:21:12 +01:00
parent b28750df0a
commit 5a248f84cc
3 changed files with 426 additions and 0 deletions

View File

@ -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()

View File

@ -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('<adjustments>\n');
adjfile.write('</adjustments>\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()

View File

@ -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")