From 2684f1fd25022e1846e89f56565aaea18cf58df8 Mon Sep 17 00:00:00 2001 From: Odilkhan Yakubov Date: Tue, 14 Jan 2025 04:41:25 +0500 Subject: [PATCH 1/2] - Fix: Imagepipeline: Auto-brightness does not works Just backported original line of "def convert_defs()" from "for_blender_4.1" branch: https://github.com/LuxCoreRender/BlendLuxCore/blob/fc6ac860fbce55820b5d03b6890befa93f9db9ff/export/imagepipeline.py#L37 --- export/imagepipeline.py | 90 ++++++++++++++++++----------------------- 1 file changed, 40 insertions(+), 50 deletions(-) diff --git a/export/imagepipeline.py b/export/imagepipeline.py index 42626c39..1b0d3007 100644 --- a/export/imagepipeline.py +++ b/export/imagepipeline.py @@ -46,67 +46,57 @@ def convert_defs(context, scene, definitions, plugin_index, define_radiancescale definitions[str(index) + ".type"] = "NOP" index += 1 - # Use multiprocessing for CPU-bound tasks - with multiprocessing.Pool() as pool: - results = [] + if pipeline.tonemapper.enabled: + index = convert_tonemapper(definitions, index, pipeline.tonemapper) - # Parallel tasks - if pipeline.tonemapper.enabled: - results.append(pool.apply_async(convert_tonemapper, (definitions, index, pipeline.tonemapper))) - index += 1 - - if context and scene.luxcore.viewport.get_denoiser(context) == "OPTIX": - results.append(pool.apply_async(_denoise_effect, (definitions, index, pipeline.denoiser))) - index += 1 - - # Wait for all results to complete - for result in results: - result.wait() + if context and scene.luxcore.viewport.get_denoiser(context) == "OPTIX": + definitions[str(index) + ".type"] = "OPTIX_DENOISER" + definitions[str(index) + ".sharpness"] = 0 + definitions[str(index) + ".minspp"] = scene.luxcore.viewport.min_samples + index += 1 if use_backgroundimage(context, scene): - # Background image handling (I/O-bound operation, use threading) + # Note: Blender expects the alpha to be NOT premultiplied, so we only + # premultiply it when the backgroundimage plugin is used + index = _premul_alpha(definitions, index) index = _backgroundimage(definitions, index, pipeline.backgroundimage, scene) - # Handle other image effects using multithreading - with concurrent.futures.ThreadPoolExecutor() as executor: - futures = [] - if pipeline.mist.is_enabled(context): - futures.append(executor.submit(_mist, definitions, index, pipeline.mist)) - index += 1 - if pipeline.bloom.is_enabled(context): - futures.append(executor.submit(_bloom, definitions, index, pipeline.bloom)) - index += 1 - if pipeline.coloraberration.is_enabled(context): - futures.append(executor.submit(_coloraberration, definitions, index, pipeline.coloraberration)) - index += 1 - if pipeline.vignetting.is_enabled(context): - futures.append(executor.submit(_vignetting, definitions, index, pipeline.vignetting)) - index += 1 - if pipeline.white_balance.is_enabled(context): - futures.append(executor.submit(_white_balance, definitions, index, pipeline.white_balance)) - index += 1 - if pipeline.camera_response_func.is_enabled(context): - futures.append(executor.submit(_camera_response_func, definitions, index, pipeline.camera_response_func, scene)) - index += 1 - if pipeline.color_LUT.is_enabled(context): - futures.append(executor.submit(_color_LUT, definitions, index, pipeline.color_LUT, scene)) - index += 1 - if pipeline.contour_lines.is_enabled(context): - futures.append(executor.submit(_contour_lines, definitions, index, pipeline.contour_lines)) - index += 1 - if using_filesaver and not pipeline.color_LUT.is_enabled(context): - futures.append(executor.submit(_gamma, definitions, index)) - index += 1 - - # Wait for all futures to complete - for future in futures: - future.result() + if pipeline.mist.is_enabled(context): + index = _mist(definitions, index, pipeline.mist) + + if pipeline.bloom.is_enabled(context): + index = _bloom(definitions, index, pipeline.bloom) + + if pipeline.coloraberration.is_enabled(context): + index = _coloraberration(definitions, index, pipeline.coloraberration) + + if pipeline.vignetting.is_enabled(context): + index = _vignetting(definitions, index, pipeline.vignetting) + + if pipeline.white_balance.is_enabled(context): + index = _white_balance(definitions, index, pipeline.white_balance) + + if pipeline.camera_response_func.is_enabled(context): + index = _camera_response_func(definitions, index, pipeline.camera_response_func, scene) + + gamma_corrected = False + if pipeline.color_LUT.is_enabled(context): + index, gamma_corrected = _color_LUT(definitions, index, pipeline.color_LUT, scene) + + if pipeline.contour_lines.is_enabled(context): + index = _contour_lines(definitions, index, pipeline.contour_lines) + + if using_filesaver and not gamma_corrected: + # Needs gamma correction (Blender applies it for us, + # but now we export for luxcoreui) + index = _gamma(definitions, index) if define_radiancescales: _lightgroups(definitions, scene) return index + def use_backgroundimage(context, scene): viewport_in_camera_view = context and context.region_data.view_perspective == "CAMERA" final_render = not context From 96038b99c75ebe6cc42878e2b7c41dd46b07e73f Mon Sep 17 00:00:00 2001 From: Odilkhan Yakubov Date: Tue, 14 Jan 2025 19:46:25 +0500 Subject: [PATCH 2/2] - Backport "Imagepipline" from original "for_blender_4.1" branch Just copy/pasted code from imagepipline.py file, not added anything except replacing: "import pyluxcore" instead of "from ..bin import pyluxcore" due to wheels format changed --- export/imagepipeline.py | 112 ++++++++++++++++++++++++++-------------- 1 file changed, 73 insertions(+), 39 deletions(-) diff --git a/export/imagepipeline.py b/export/imagepipeline.py index 1b0d3007..316df500 100644 --- a/export/imagepipeline.py +++ b/export/imagepipeline.py @@ -3,9 +3,7 @@ from .. import utils from .image import ImageExporter from ..utils.errorlog import LuxCoreErrorLog -import concurrent.futures -import multiprocessing -import numpy as np + def convert(scene, context=None, index=0): try: @@ -26,7 +24,6 @@ def convert(scene, context=None, index=0): _fallback(definitions) return utils.create_props(prefix, definitions) - # Convert definitions in parallel using multiprocessing convert_defs(context, scene, definitions, 0) return utils.create_props(prefix, definitions) @@ -36,6 +33,7 @@ def convert(scene, context=None, index=0): LuxCoreErrorLog.add_warning('Imagepipeline: %s' % error) return pyluxcore.Properties() + def convert_defs(context, scene, definitions, plugin_index, define_radiancescales=True): pipeline = scene.camera.data.luxcore.imagepipeline using_filesaver = utils.using_filesaver(context, scene) @@ -103,6 +101,7 @@ def use_backgroundimage(context, scene): pipeline = scene.camera.data.luxcore.imagepipeline return pipeline.backgroundimage.is_enabled(context) and (final_render or viewport_in_camera_view) + def _fallback(definitions): """ Fallback imagepipeline if no camera is in the scene @@ -111,17 +110,23 @@ def _fallback(definitions): definitions[str(index) + ".type"] = "TONEMAP_LINEAR" definitions[str(index) + ".scale"] = 1 + def _exposure_compensated_tonemapper(definitions, index, scene): definitions[str(index) + ".type"] = "TONEMAP_LINEAR" definitions[str(index) + ".scale"] = 1 / pow(2, (scene.view_settings.exposure)) return index + 1 + def convert_tonemapper(definitions, index, tonemapper): + # If "Auto Brightness" is enabled, put an autolinear tonemapper + # in front of the linear tonemapper if tonemapper.type == "TONEMAP_LINEAR" and tonemapper.use_autolinear: definitions[str(index) + ".type"] = "TONEMAP_AUTOLINEAR" index += 1 + # Main tonemapper definitions[str(index) + ".type"] = tonemapper.type + if tonemapper.type == "TONEMAP_LINEAR": definitions[str(index) + ".scale"] = tonemapper.linear_scale elif tonemapper.type == "TONEMAP_REINHARD02": @@ -135,22 +140,23 @@ def convert_tonemapper(definitions, index, tonemapper): return index + 1 -def _denoise_effect(definitions, index, denoiser): - definitions[str(index) + ".type"] = "OPTIX_DENOISER" - definitions[str(index) + ".sharpness"] = 0 - definitions[str(index) + ".minspp"] = denoiser.min_samples + +def _premul_alpha(definitions, index): + definitions[str(index) + ".type"] = "PREMULTIPLY_ALPHA" return index + 1 + def _backgroundimage(definitions, index, backgroundimage, scene): if backgroundimage.image is None: return index try: - with concurrent.futures.ThreadPoolExecutor() as executor: - future = executor.submit(ImageExporter.export, backgroundimage.image, backgroundimage.image_user, scene) - filepath = future.result() + filepath = ImageExporter.export(backgroundimage.image, + backgroundimage.image_user, + scene) except OSError as error: - LuxCoreErrorLog.add_warning(f"Imagepipeline: {error}") + LuxCoreErrorLog.add_warning("Imagepipeline: %s" % error) + # Skip this plugin return index definitions[str(index) + ".type"] = "BACKGROUND_IMG" @@ -159,30 +165,6 @@ def _backgroundimage(definitions, index, backgroundimage, scene): definitions[str(index) + ".storage"] = backgroundimage.storage return index + 1 -def _gamma(definitions, index): - definitions[str(index) + ".type"] = "GAMMA_CORRECTION" - definitions[str(index) + ".value"] = 2.2 - return index + 1 - -def _color_LUT(definitions, index, color_LUT, scene): - try: - library = scene.camera.data.library - filepath = utils.get_abspath(color_LUT.file, library, must_exist=True, must_be_existing_file=True) - except OSError as error: - LuxCoreErrorLog.add_warning(f'Could not find .cube file at path "{color_LUT.file}" ({error})') - filepath = None - - if filepath: - gamma_corrected = color_LUT.input_colorspace == "SRGB_GAMMA_CORRECTED" - if gamma_corrected: - index = _gamma(definitions, index) - - definitions[str(index) + ".type"] = "COLOR_LUT" - definitions[str(index) + ".file"] = filepath - definitions[str(index) + ".strength"] = color_LUT.strength / 100 - return index + 1, gamma_corrected - else: - return index, False def _mist(definitions, index, mist): definitions[str(index) + ".type"] = "MIST" @@ -193,12 +175,14 @@ def _mist(definitions, index, mist): definitions[str(index) + ".excludebackground"] = mist.exclude_background return index + 1 + def _bloom(definitions, index, bloom): definitions[str(index) + ".type"] = "BLOOM" definitions[str(index) + ".radius"] = bloom.radius / 100 definitions[str(index) + ".weight"] = bloom.weight / 100 return index + 1 + def _coloraberration(definitions, index, coloraberration): definitions[str(index) + ".type"] = "COLOR_ABERRATION" amount_x = coloraberration.amount / 100 @@ -209,11 +193,13 @@ def _coloraberration(definitions, index, coloraberration): definitions[str(index) + ".amount"] = [amount_x, amount_y] return index + 1 + def _vignetting(definitions, index, vignetting): definitions[str(index) + ".type"] = "VIGNETTING" definitions[str(index) + ".scale"] = vignetting.scale / 100 return index + 1 + def _white_balance(definitions, index, white_balance): definitions[str(index) + ".type"] = "WHITE_BALANCE" definitions[str(index) + ".temperature"] = white_balance.temperature @@ -221,19 +207,24 @@ def _white_balance(definitions, index, white_balance): definitions[str(index) + ".normalize"] = True return index + 1 + def _camera_response_func(definitions, index, camera_response_func, scene): if camera_response_func.type == "PRESET": name = camera_response_func.preset elif camera_response_func.type == "FILE": try: library = scene.camera.data.library - name = utils.get_abspath(camera_response_func.file, library, must_exist=True, must_be_existing_file=True) + name = utils.get_abspath(camera_response_func.file, library, + must_exist=True, must_be_existing_file=True) except OSError as error: - LuxCoreErrorLog.add_warning(f'Could not find .crf file at path "{camera_response_func.file}" ({error})') + # Make the error message more precise + LuxCoreErrorLog.add_warning('Could not find .crf file at path "%s" (%s)' + % (camera_response_func.file, error)) name = None else: raise NotImplementedError("Unknown crf type: " + camera_response_func.type) + # Note: preset or file are empty strings until the user selects something if name: definitions[str(index) + ".type"] = "CAMERA_RESPONSE_FUNC" definitions[str(index) + ".name"] = name @@ -241,6 +232,31 @@ def _camera_response_func(definitions, index, camera_response_func, scene): else: return index + +def _color_LUT(definitions, index, color_LUT, scene): + try: + library = scene.camera.data.library + filepath = utils.get_abspath(color_LUT.file, library, + must_exist=True, must_be_existing_file=True) + except OSError as error: + # Make the error message more precise + LuxCoreErrorLog.add_warning('Could not find .cube file at path "%s" (%s)' + % (color_LUT.file, error)) + filepath = None + + if filepath: + gamma_corrected = color_LUT.input_colorspace == "SRGB_GAMMA_CORRECTED" + if gamma_corrected: + index = _gamma(definitions, index) + + definitions[str(index) + ".type"] = "COLOR_LUT" + definitions[str(index) + ".file"] = filepath + definitions[str(index) + ".strength"] = color_LUT.strength / 100 + return index + 1, gamma_corrected + else: + return index, False + + def _contour_lines(definitions, index, contour_lines): definitions[str(index) + ".type"] = "CONTOUR_LINES" definitions[str(index) + ".range"] = contour_lines.contour_range @@ -249,19 +265,37 @@ def _contour_lines(definitions, index, contour_lines): definitions[str(index) + ".zerogridsize"] = contour_lines.zero_grid_size return index + 1 + +def _gamma(definitions, index): + definitions[str(index) + ".type"] = "GAMMA_CORRECTION" + definitions[str(index) + ".value"] = 2.2 + return index + 1 + + +def _output_switcher(definitions, index, channel): + definitions[str(index) + ".type"] = "OUTPUT_SWITCHER" + definitions[str(index) + ".channel"] = channel + return index + 1 + + def _lightgroups(definitions, scene): lightgroups = scene.luxcore.lightgroups + _lightgroup(definitions, lightgroups.default, 0) for i, group in enumerate(lightgroups.custom): + # +1 to group_id because default group is id 0, but not in the list group_id = i + 1 _lightgroup(definitions, group, group_id) + def _lightgroup(definitions, group, group_id): prefix = "radiancescales." + str(group_id) + "." definitions[prefix + "enabled"] = group.enabled definitions[prefix + "globalscale"] = group.gain + if group.use_rgb_gain: definitions[prefix + "rgbscale"] = list(group.rgb_gain) + if group.use_temperature: - definitions[prefix + "temperature"] = group.temperature + definitions[prefix + "temperature"] = group.temperature \ No newline at end of file