diff --git a/src/films/pngfilm.cpp b/src/films/pngfilm.cpp index c9c42579..418196d8 100644 --- a/src/films/pngfilm.cpp +++ b/src/films/pngfilm.cpp @@ -49,6 +49,7 @@ protected: Float m_gamma, m_exposure; std::string m_toneMappingMethod; Float m_reinhardKey, m_reinhardBurn; + int m_compressionRate; public: PNGFilm(const Properties &props) : Film(props) { m_pixels = new Pixel[m_cropSize.x * m_cropSize.y]; @@ -68,6 +69,8 @@ public: m_reinhardBurn = props.getFloat("reinhardBurn", 0.0f); /* Reinhard "key" parameter */ m_reinhardKey = props.getFloat("reinhardKey", 0.18f); + /* Compression rate (1=low, 9=high) */ + m_compressionRate = props.getInteger("compressionRate", 1); if (m_toneMappingMethod != "gamma" && m_toneMappingMethod != "reinhard") Log(EError, "Unknown tone mapping method specified (must be 'gamma' or 'reinhard')"); @@ -95,6 +98,7 @@ public: m_reinhardKey = stream->readFloat(); m_reinhardBurn = stream->readFloat(); m_exposure = stream->readFloat(); + m_compressionRate = stream->readInt(); m_gamma = 1.0f / m_gamma; m_pixels = new Pixel[m_cropSize.x * m_cropSize.y]; } @@ -109,6 +113,7 @@ public: stream->writeFloat(m_reinhardKey); stream->writeFloat(m_reinhardBurn); stream->writeFloat(m_exposure); + stream->writeInt(m_compressionRate); } virtual ~PNGFilm() { @@ -403,7 +408,7 @@ public: Log(EInfo, "Writing image to \"%s\" ..", filename.leaf().c_str()); ref stream = new FileStream(filename, FileStream::ETruncWrite); bitmap->setGamma(m_gamma); - bitmap->save(Bitmap::EPNG, stream, 1); + bitmap->save(Bitmap::EPNG, stream, m_compressionRate); } bool destinationExists(const fs::path &baseName) const { diff --git a/tools/blender/mitsuba/__init__.py b/tools/blender/mitsuba/__init__.py index 609f52aa..d3ec24fd 100644 --- a/tools/blender/mitsuba/__init__.py +++ b/tools/blender/mitsuba/__init__.py @@ -16,6 +16,8 @@ # # ##### END GPL LICENSE BLOCK ##### +import os + bl_addon_info = { "name": "Mitsuba", "author": "Wenzel Jakob", @@ -30,6 +32,8 @@ bl_addon_info = { "tracker_url": "https://www.mitsuba-renderer.org/bugtracker/projects/mitsuba", "category": "Render"} +def plugin_path(): + return os.path.dirname(os.path.realpath(__file__)) from .core import RENDERENGINE_mitsuba diff --git a/tools/blender/mitsuba/core/__init__.py b/tools/blender/mitsuba/core/__init__.py index ba16e7ec..95e7181c 100644 --- a/tools/blender/mitsuba/core/__init__.py +++ b/tools/blender/mitsuba/core/__init__.py @@ -27,6 +27,7 @@ from extensions_framework.engine import engine_base from extensions_framework import util as efutil # Mitsuba-related classes +from mitsuba import plugin_path from mitsuba.properties.engine import mitsuba_engine from mitsuba.properties.lamp import mitsuba_lamp from mitsuba.properties.texture import mitsuba_texture, \ @@ -41,6 +42,7 @@ from mitsuba.properties.material import mitsuba_material, \ from mitsuba.operators import MITSUBA_OT_preset_engine_add, EXPORT_OT_mitsuba from mitsuba.outputs import MtsLog, MtsFilmDisplay from mitsuba.export.adjustments import MtsAdjustments +from mitsuba.export import translate_id from mitsuba.export.film import resolution from mitsuba.export import get_instance_materials from mitsuba.ui import render_panels @@ -133,13 +135,61 @@ class RENDERENGINE_mitsuba(bpy.types.RenderEngine, engine_base): return tempdir = efutil.temp_directory() - matpreview_file = os.path.join(tempdir, "matpreview_materials.xml") + matfile = os.path.join(tempdir, "matpreview_materials.xml") + output_file = os.path.join(tempdir, "matpreview.png") + scene_file = os.path.join(os.path.join(plugin_path(), + "matpreview"), "matpreview.xml") pm = likely_materials[0] - adj = MtsAdjustments(matpreview_file, tempdir, + adj = MtsAdjustments(matfile, tempdir, bpy.data.materials, bpy.data.textures) adj.writeHeader() adj.exportMaterial(pm) adj.writeFooter() + (mts_path, tail) = os.path.split(bpy.path.abspath(scene.mitsuba_engine.binary_path)) + mitsuba_binary = os.path.join(mts_path, "mitsuba") + env = copy.copy(os.environ) + 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['LD_LIBRARY_PATH'] = mts_core_libpath + ":" + mts_render_libpath + ":" + mts_hw_libpath + (width, height) = resolution(scene) + refresh_interval = 1 + mitsuba_process = subprocess.Popen( + [mitsuba_binary, '-r%i' % refresh_interval, + '-o', output_file, '-Dmatfile=%s' % matfile, + '-Dmatname=%s' % translate_id(pm.name), + '-Dwidth=%i' % width, + '-Dheight=%i' % height, + '-q', + '-o', output_file, scene_file], + env = env, + cwd = tempdir + ) + framebuffer_thread = MtsFilmDisplay({ + 'resolution': resolution(scene), + 'RE': self, + 'output_file': output_file + }) + framebuffer_thread.set_kick_period(refresh_interval) + framebuffer_thread.start() + while mitsuba_process.poll() == None and not self.test_break(): + self.render_update_timer = threading.Timer(1, self.process_wait_timer) + self.render_update_timer.start() + if self.render_update_timer.isAlive(): self.render_update_timer.join() + + # If we exit the wait loop (user cancelled) and mitsuba is still running, then send SIGINT + if mitsuba_process.poll() == None: + # Use SIGTERM because that's the only one supported on Windows + mitsuba_process.send_signal(subprocess.signal.SIGTERM) + + # Stop updating the render result and load the final image + framebuffer_thread.stop() + framebuffer_thread.join() + + if mitsuba_process.poll() != None and mitsuba_process.returncode != 0: + MtsLog("MtsBlend: Rendering failed -- check the console") + else: + framebuffer_thread.kick(render_end=True) def render(self, scene): @@ -196,17 +246,18 @@ class RENDERENGINE_mitsuba(bpy.types.RenderEngine, engine_base): cwd = self.output_dir ) elif scene.mitsuba_engine.render_mode == 'cli': - self.output_file = efutil.export_path[:-4] + ".png" + output_file = efutil.export_path[:-4] + ".png" mitsuba_process = subprocess.Popen( [mitsuba_binary, '-r', '%d' % scene.mitsuba_engine.refresh_interval, - '-o', self.output_file, efutil.export_path], + '-o', output_file, efutil.export_path], env = env, cwd = self.output_dir ) framebuffer_thread = MtsFilmDisplay({ 'resolution': resolution(scene), 'RE': self, + 'output_file': output_file }) framebuffer_thread.set_kick_period(scene.mitsuba_engine.refresh_interval) framebuffer_thread.start() diff --git a/tools/blender/mitsuba/matpreview/envmap.exr b/tools/blender/mitsuba/matpreview/envmap.exr new file mode 100644 index 00000000..64ee6403 Binary files /dev/null and b/tools/blender/mitsuba/matpreview/envmap.exr differ diff --git a/tools/blender/mitsuba/matpreview/matpreview.serialized b/tools/blender/mitsuba/matpreview/matpreview.serialized new file mode 100644 index 00000000..dc882376 Binary files /dev/null and b/tools/blender/mitsuba/matpreview/matpreview.serialized differ diff --git a/tools/blender/mitsuba/matpreview/matpreview.xml b/tools/blender/mitsuba/matpreview/matpreview.xml new file mode 100644 index 00000000..d9447f33 --- /dev/null +++ b/tools/blender/mitsuba/matpreview/matpreview.xml @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tools/blender/mitsuba/outputs/__init__.py b/tools/blender/mitsuba/outputs/__init__.py index 9339ff0e..73a6ae07 100644 --- a/tools/blender/mitsuba/outputs/__init__.py +++ b/tools/blender/mitsuba/outputs/__init__.py @@ -27,12 +27,12 @@ class MtsFilmDisplay(TimerThread): MtsLog('Updating render result %ix%i' % (xres,yres)) result = self.LocalStorage['RE'].begin_result(0, 0, int(xres), int(yres)) - if os.path.exists(self.LocalStorage['RE'].output_file): + if os.path.exists(self.LocalStorage['output_file']): bpy.ops.ef.msg(msg_text='Updating RenderResult') lay = result.layers[0] - lay.load_from_file(self.LocalStorage['RE'].output_file) + lay.load_from_file(self.LocalStorage['output_file']) else: - err_msg = 'ERROR: Could not load render result from %s' % self.LocalStorage['RE'].output_file + err_msg = 'ERROR: Could not load render result from %s' % self.LocalStorage['output_file'] MtsLog(err_msg) bpy.ops.ef.msg(msg_type='ERROR', msg_text=err_msg) self.LocalStorage['RE'].end_result(result)