485 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
		
		
			
		
	
	
			485 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| 
								 | 
							
								from contextlib import contextmanager
							 | 
						||
| 
								 | 
							
								import math
							 | 
						||
| 
								 | 
							
								import multiprocessing
							 | 
						||
| 
								 | 
							
								import os
							 | 
						||
| 
								 | 
							
								import re
							 | 
						||
| 
								 | 
							
								import subprocess
							 | 
						||
| 
								 | 
							
								import time
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								import bpy
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								import lnx.assets as assets
							 | 
						||
| 
								 | 
							
								import lnx.log as log
							 | 
						||
| 
								 | 
							
								import lnx.utils
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								if lnx.is_reload(__name__):
							 | 
						||
| 
								 | 
							
								    import lnx
							 | 
						||
| 
								 | 
							
								    assets = lnx.reload_module(assets)
							 | 
						||
| 
								 | 
							
								    log = lnx.reload_module(log)
							 | 
						||
| 
								 | 
							
								    lnx.utils = lnx.reload_module(lnx.utils)
							 | 
						||
| 
								 | 
							
								else:
							 | 
						||
| 
								 | 
							
								    lnx.enable_reload(__name__)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# The format used for rendering the environment. Choose HDR or JPEG.
							 | 
						||
| 
								 | 
							
								ENVMAP_FORMAT = 'JPEG'
							 | 
						||
| 
								 | 
							
								ENVMAP_EXT = 'hdr' if ENVMAP_FORMAT == 'HDR' else 'jpg'
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								__cmft_start_time_seconds = 0.0
							 | 
						||
| 
								 | 
							
								__cmft_end_time_seconds = 0.0
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def add_irr_assets(output_file_irr):
							 | 
						||
| 
								 | 
							
								    assets.add(output_file_irr + '.lnx')
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def add_rad_assets(output_file_rad, rad_format, num_mips):
							 | 
						||
| 
								 | 
							
								    assets.add(output_file_rad + '.' + rad_format)
							 | 
						||
| 
								 | 
							
								    for i in range(0, num_mips):
							 | 
						||
| 
								 | 
							
								        assets.add(output_file_rad + '_' + str(i) + '.' + rad_format)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								@contextmanager
							 | 
						||
| 
								 | 
							
								def setup_envmap_render():
							 | 
						||
| 
								 | 
							
								    """Creates a background scene for rendering environment textures.
							 | 
						||
| 
								 | 
							
								    Use it as a context manager to automatically clean up on errors.
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    rpdat = lnx.utils.get_rp()
							 | 
						||
| 
								 | 
							
								    radiance_size = int(rpdat.lnx_radiance_size)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # Render worlds in a different scene so that there are no other
							 | 
						||
| 
								 | 
							
								    # objects. The actual scene might be called differently if the name
							 | 
						||
| 
								 | 
							
								    # is already taken
							 | 
						||
| 
								 | 
							
								    scene = bpy.data.scenes.new("_lnx_envmap_render")
							 | 
						||
| 
								 | 
							
								    scene.render.engine = "CYCLES"
							 | 
						||
| 
								 | 
							
								    scene.render.image_settings.file_format = ENVMAP_FORMAT
							 | 
						||
| 
								 | 
							
								    if ENVMAP_FORMAT == 'HDR':
							 | 
						||
| 
								 | 
							
								        scene.render.image_settings.color_depth = '32'
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # Export in linear space and with default color management settings
							 | 
						||
| 
								 | 
							
								    if bpy.app.version < (4, 1, 0):
							 | 
						||
| 
								 | 
							
								        scene.display_settings.display_device = "None"
							 | 
						||
| 
								 | 
							
								    else:
							 | 
						||
| 
								 | 
							
								        scene.display_settings.display_device = "sRGB"
							 | 
						||
| 
								 | 
							
								    scene.view_settings.view_transform = "Standard"
							 | 
						||
| 
								 | 
							
								    scene.view_settings.look = "None"
							 | 
						||
| 
								 | 
							
								    scene.view_settings.exposure = 0
							 | 
						||
| 
								 | 
							
								    scene.view_settings.gamma = 1
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    scene.render.image_settings.quality = 100
							 | 
						||
| 
								 | 
							
								    scene.render.resolution_x = radiance_size
							 | 
						||
| 
								 | 
							
								    scene.render.resolution_y = radiance_size // 2
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # Set GPU as rendering device if the user enabled it
							 | 
						||
| 
								 | 
							
								    if bpy.context.preferences.addons["cycles"].preferences.compute_device_type != "NONE":
							 | 
						||
| 
								 | 
							
								        scene.cycles.device = "GPU"
							 | 
						||
| 
								 | 
							
								    else:
							 | 
						||
| 
								 | 
							
								        log.info('Using CPU for environment render (might be slow). If possible, configure GPU rendering in Blender preferences (System > Cycles Render Devices).')
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # Those settings are sufficient for rendering only the world background
							 | 
						||
| 
								 | 
							
								    scene.cycles.samples = 1
							 | 
						||
| 
								 | 
							
								    scene.cycles.max_bounces = 1
							 | 
						||
| 
								 | 
							
								    scene.cycles.diffuse_bounces = 1
							 | 
						||
| 
								 | 
							
								    scene.cycles.glossy_bounces = 1
							 | 
						||
| 
								 | 
							
								    scene.cycles.transmission_bounces = 1
							 | 
						||
| 
								 | 
							
								    scene.cycles.volume_bounces = 1
							 | 
						||
| 
								 | 
							
								    scene.cycles.transparent_max_bounces = 1
							 | 
						||
| 
								 | 
							
								    scene.cycles.caustics_reflective = False
							 | 
						||
| 
								 | 
							
								    scene.cycles.caustics_refractive = False
							 | 
						||
| 
								 | 
							
								    scene.cycles.use_denoising = False
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # Setup scene
							 | 
						||
| 
								 | 
							
								    cam = bpy.data.cameras.new("_lnx_cam_envmap_render")
							 | 
						||
| 
								 | 
							
								    cam_obj = bpy.data.objects.new("_lnx_cam_envmap_render", cam)
							 | 
						||
| 
								 | 
							
								    scene.collection.objects.link(cam_obj)
							 | 
						||
| 
								 | 
							
								    scene.camera = cam_obj
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    cam_obj.location = [0.0, 0.0, 0.0]
							 | 
						||
| 
								 | 
							
								    cam.type = "PANO"
							 | 
						||
| 
								 | 
							
								    if bpy.app.version < (4, 1, 0):
							 | 
						||
| 
								 | 
							
								        cam.cycles.panorama_type = "EQUIRECTANGULAR"
							 | 
						||
| 
								 | 
							
								    else:
							 | 
						||
| 
								 | 
							
								        cam.panorama_type = "EQUIRECTANGULAR"
							 | 
						||
| 
								 | 
							
								    cam_obj.rotation_euler = [math.radians(90), 0, math.radians(-90)]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    try:
							 | 
						||
| 
								 | 
							
								        yield
							 | 
						||
| 
								 | 
							
								    finally:
							 | 
						||
| 
								 | 
							
								        bpy.data.objects.remove(cam_obj)
							 | 
						||
| 
								 | 
							
								        bpy.data.cameras.remove(cam)
							 | 
						||
| 
								 | 
							
								        bpy.data.scenes.remove(scene)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def render_envmap(target_dir: str, world: bpy.types.World) -> str:
							 | 
						||
| 
								 | 
							
								    """Render an environment texture for the given world into the
							 | 
						||
| 
								 | 
							
								    target_dir and return the filename of the rendered image. Use in
							 | 
						||
| 
								 | 
							
								    combination with setup_envmap_render().
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    scene = bpy.data.scenes['_lnx_envmap_render']
							 | 
						||
| 
								 | 
							
								    scene.world = world
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    image_name = f'env_{lnx.utils.safesrc(world.name)}.{ENVMAP_EXT}'
							 | 
						||
| 
								 | 
							
								    render_path = os.path.join(target_dir, image_name)
							 | 
						||
| 
								 | 
							
								    scene.render.filepath = render_path
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    bpy.ops.render.render(write_still=True, scene=scene.name)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    return image_name
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def write_probes(image_filepath: str, disable_hdr: bool, from_srgb: bool, cached_num_mips: int, lnx_radiance=True) -> int:
							 | 
						||
| 
								 | 
							
								    """Generate probes from environment map and return the mipmap count."""
							 | 
						||
| 
								 | 
							
								    envpath = lnx.utils.get_fp_build() + '/compiled/Assets/envmaps'
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if not os.path.exists(envpath):
							 | 
						||
| 
								 | 
							
								        os.makedirs(envpath)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    base_name = lnx.utils.extract_filename(image_filepath).rsplit('.', 1)[0]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # Assets to be generated
							 | 
						||
| 
								 | 
							
								    output_file_irr = envpath + '/' + base_name + '_irradiance'
							 | 
						||
| 
								 | 
							
								    output_file_rad = envpath + '/' + base_name + '_radiance'
							 | 
						||
| 
								 | 
							
								    rad_format = 'jpg' if disable_hdr else 'hdr'
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # Radiance & irradiance exists, keep cache
							 | 
						||
| 
								 | 
							
								    basep = envpath + '/' + base_name
							 | 
						||
| 
								 | 
							
								    if os.path.exists(basep + '_irradiance.lnx'):
							 | 
						||
| 
								 | 
							
								        if not lnx_radiance or os.path.exists(basep + '_radiance_0.' + rad_format):
							 | 
						||
| 
								 | 
							
								            add_irr_assets(output_file_irr)
							 | 
						||
| 
								 | 
							
								            if lnx_radiance:
							 | 
						||
| 
								 | 
							
								                add_rad_assets(output_file_rad, rad_format, cached_num_mips)
							 | 
						||
| 
								 | 
							
								            return cached_num_mips
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # Get paths
							 | 
						||
| 
								 | 
							
								    sdk_path = lnx.utils.get_sdk_path()
							 | 
						||
| 
								 | 
							
								    kha_path = lnx.utils.get_kha_path()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if lnx.utils.get_os() == 'win':
							 | 
						||
| 
								 | 
							
								        cmft_path = sdk_path + '/lib/leenkx_tools/cmft/cmft.exe'
							 | 
						||
| 
								 | 
							
								        kraffiti_path = kha_path + '/Kinc/Tools/windows_x64/kraffiti.exe'
							 | 
						||
| 
								 | 
							
								    elif lnx.utils.get_os() == 'mac':
							 | 
						||
| 
								 | 
							
								        cmft_path = '"' + sdk_path + '/lib/leenkx_tools/cmft/cmft-osx"'
							 | 
						||
| 
								 | 
							
								        kraffiti_path = '"' + kha_path + '/Kinc/Tools/macos/kraffiti"'
							 | 
						||
| 
								 | 
							
								    else:
							 | 
						||
| 
								 | 
							
								        cmft_path = '"' + sdk_path + '/lib/leenkx_tools/cmft/cmft-linux64"'
							 | 
						||
| 
								 | 
							
								        kraffiti_path = '"' + kha_path + '/Kinc/Tools/linux_x64/kraffiti"'
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    input_file = lnx.utils.asset_path(image_filepath)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # Scale map, ensure 2:1 ratio (required by cmft)
							 | 
						||
| 
								 | 
							
								    rpdat = lnx.utils.get_rp()
							 | 
						||
| 
								 | 
							
								    target_w = int(rpdat.lnx_radiance_size)
							 | 
						||
| 
								 | 
							
								    target_h = int(target_w / 2)
							 | 
						||
| 
								 | 
							
								    scaled_file = output_file_rad + '.' + rad_format
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if lnx.utils.get_os() == 'win':
							 | 
						||
| 
								 | 
							
								        subprocess.check_output([
							 | 
						||
| 
								 | 
							
								            kraffiti_path,
							 | 
						||
| 
								 | 
							
								            'from=' + input_file,
							 | 
						||
| 
								 | 
							
								            'to=' + scaled_file,
							 | 
						||
| 
								 | 
							
								            'format=' + rad_format,
							 | 
						||
| 
								 | 
							
								            'width=' + str(target_w),
							 | 
						||
| 
								 | 
							
								            'height=' + str(target_h)])
							 | 
						||
| 
								 | 
							
								    else:
							 | 
						||
| 
								 | 
							
								        subprocess.check_output([
							 | 
						||
| 
								 | 
							
								            kraffiti_path
							 | 
						||
| 
								 | 
							
								            + ' from="' + input_file + '"'
							 | 
						||
| 
								 | 
							
								            + ' to="' + scaled_file + '"'
							 | 
						||
| 
								 | 
							
								            + ' format=' + rad_format
							 | 
						||
| 
								 | 
							
								            + ' width=' + str(target_w)
							 | 
						||
| 
								 | 
							
								            + ' height=' + str(target_h)], shell=True)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # Convert sRGB colors into linear color space first (approximately)
							 | 
						||
| 
								 | 
							
								    input_gamma_numerator = '2.2' if from_srgb else '1.0'
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # Irradiance spherical harmonics
							 | 
						||
| 
								 | 
							
								    if lnx.utils.get_os() == 'win':
							 | 
						||
| 
								 | 
							
								        subprocess.call([
							 | 
						||
| 
								 | 
							
								            cmft_path,
							 | 
						||
| 
								 | 
							
								            '--input', scaled_file,
							 | 
						||
| 
								 | 
							
								            '--filter', 'shcoeffs',
							 | 
						||
| 
								 | 
							
								            '--outputNum', '1',
							 | 
						||
| 
								 | 
							
								            '--output0', output_file_irr,
							 | 
						||
| 
								 | 
							
								            '--inputGammaNumerator', input_gamma_numerator,
							 | 
						||
| 
								 | 
							
								            '--inputGammaDenominator', '1.0',
							 | 
						||
| 
								 | 
							
								            '--outputGammaNumerator', '1.0',
							 | 
						||
| 
								 | 
							
								            '--outputGammaDenominator', '1.0'
							 | 
						||
| 
								 | 
							
								        ])
							 | 
						||
| 
								 | 
							
								    else:
							 | 
						||
| 
								 | 
							
								        subprocess.call([
							 | 
						||
| 
								 | 
							
								            cmft_path
							 | 
						||
| 
								 | 
							
								            + ' --input ' + '"' + scaled_file + '"'
							 | 
						||
| 
								 | 
							
								            + ' --filter shcoeffs'
							 | 
						||
| 
								 | 
							
								            + ' --outputNum 1'
							 | 
						||
| 
								 | 
							
								            + ' --output0 ' + '"' + output_file_irr + '"'
							 | 
						||
| 
								 | 
							
								            + ' --inputGammaNumerator' + input_gamma_numerator
							 | 
						||
| 
								 | 
							
								            + ' --inputGammaDenominator' + '1.0'
							 | 
						||
| 
								 | 
							
								            + ' --outputGammaNumerator' + '1.0'
							 | 
						||
| 
								 | 
							
								            + ' --outputGammaDenominator' + '1.0'
							 | 
						||
| 
								 | 
							
								        ], shell=True)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    sh_to_json(output_file_irr)
							 | 
						||
| 
								 | 
							
								    add_irr_assets(output_file_irr)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # Mip-mapped radiance
							 | 
						||
| 
								 | 
							
								    if not lnx_radiance:
							 | 
						||
| 
								 | 
							
								        return cached_num_mips
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # 4096 = 256 face
							 | 
						||
| 
								 | 
							
								    # 2048 = 128 face
							 | 
						||
| 
								 | 
							
								    # 1024 = 64 face
							 | 
						||
| 
								 | 
							
								    face_size = target_w / 8
							 | 
						||
| 
								 | 
							
								    if target_w == 2048:
							 | 
						||
| 
								 | 
							
								        mip_count = 9
							 | 
						||
| 
								 | 
							
								    elif target_w == 1024:
							 | 
						||
| 
								 | 
							
								        mip_count = 8
							 | 
						||
| 
								 | 
							
								    else:
							 | 
						||
| 
								 | 
							
								        mip_count = 7
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    wrd = bpy.data.worlds['Lnx']
							 | 
						||
| 
								 | 
							
								    use_opencl = 'true' if lnx.utils.get_pref_or_default("cmft_use_opencl", True) else 'false'
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # cmft doesn't work correctly when passing the number of logical
							 | 
						||
| 
								 | 
							
								    # CPUs if there are more logical than physical CPUs on a machine
							 | 
						||
| 
								 | 
							
								    cpu_count = lnx.utils.cpu_count(physical_only=True)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # CMFT might hang with OpenCl enabled, output warning in that case.
							 | 
						||
| 
								 | 
							
								    # See https://github.com/leenkx3d/leenkx/issues/2760 for details.
							 | 
						||
| 
								 | 
							
								    global __cmft_start_time_seconds, __cmft_end_time_seconds
							 | 
						||
| 
								 | 
							
								    __cmft_start_time_seconds = time.time()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    try:
							 | 
						||
| 
								 | 
							
								        if lnx.utils.get_os() == 'win':
							 | 
						||
| 
								 | 
							
								            cmd = [
							 | 
						||
| 
								 | 
							
								                cmft_path,
							 | 
						||
| 
								 | 
							
								                '--input', scaled_file,
							 | 
						||
| 
								 | 
							
								                '--filter', 'radiance',
							 | 
						||
| 
								 | 
							
								                '--dstFaceSize', str(face_size),
							 | 
						||
| 
								 | 
							
								                '--srcFaceSize', str(face_size),
							 | 
						||
| 
								 | 
							
								                '--excludeBase', 'false',
							 | 
						||
| 
								 | 
							
								                # '--mipCount', str(mip_count),
							 | 
						||
| 
								 | 
							
								                '--glossScale', '8',
							 | 
						||
| 
								 | 
							
								                '--glossBias', '3',
							 | 
						||
| 
								 | 
							
								                '--lightingModel', 'blinnbrdf',
							 | 
						||
| 
								 | 
							
								                '--edgeFixup', 'none',
							 | 
						||
| 
								 | 
							
								                '--numCpuProcessingThreads', str(cpu_count),
							 | 
						||
| 
								 | 
							
								                '--useOpenCL', use_opencl,
							 | 
						||
| 
								 | 
							
								                '--clVendor', 'anyGpuVendor',
							 | 
						||
| 
								 | 
							
								                '--deviceType', 'gpu',
							 | 
						||
| 
								 | 
							
								                '--deviceIndex', '0',
							 | 
						||
| 
								 | 
							
								                '--generateMipChain', 'true',
							 | 
						||
| 
								 | 
							
								                '--inputGammaNumerator', input_gamma_numerator,
							 | 
						||
| 
								 | 
							
								                '--inputGammaDenominator', '1.0',
							 | 
						||
| 
								 | 
							
								                '--outputGammaNumerator', '1.0',
							 | 
						||
| 
								 | 
							
								                '--outputGammaDenominator', '1.0',
							 | 
						||
| 
								 | 
							
								                '--outputNum', '1',
							 | 
						||
| 
								 | 
							
								                '--output0', output_file_rad,
							 | 
						||
| 
								 | 
							
								                '--output0params', 'hdr,rgbe,latlong'
							 | 
						||
| 
								 | 
							
								            ]
							 | 
						||
| 
								 | 
							
								            if not wrd.lnx_verbose_output:
							 | 
						||
| 
								 | 
							
								                cmd.append('--silent')
							 | 
						||
| 
								 | 
							
								            print(cmd)
							 | 
						||
| 
								 | 
							
								            subprocess.call(cmd)
							 | 
						||
| 
								 | 
							
								        else:
							 | 
						||
| 
								 | 
							
								            cmd = cmft_path + \
							 | 
						||
| 
								 | 
							
								                ' --input "' + scaled_file + '"' + \
							 | 
						||
| 
								 | 
							
								                ' --filter radiance' + \
							 | 
						||
| 
								 | 
							
								                ' --dstFaceSize ' + str(face_size) + \
							 | 
						||
| 
								 | 
							
								                ' --srcFaceSize ' + str(face_size) + \
							 | 
						||
| 
								 | 
							
								                ' --excludeBase false' + \
							 | 
						||
| 
								 | 
							
								                ' --glossScale 8' + \
							 | 
						||
| 
								 | 
							
								                ' --glossBias 3' + \
							 | 
						||
| 
								 | 
							
								                ' --lightingModel blinnbrdf' + \
							 | 
						||
| 
								 | 
							
								                ' --edgeFixup none' + \
							 | 
						||
| 
								 | 
							
								                ' --numCpuProcessingThreads ' + str(cpu_count) + \
							 | 
						||
| 
								 | 
							
								                ' --useOpenCL ' + use_opencl + \
							 | 
						||
| 
								 | 
							
								                ' --clVendor anyGpuVendor' + \
							 | 
						||
| 
								 | 
							
								                ' --deviceType gpu' + \
							 | 
						||
| 
								 | 
							
								                ' --deviceIndex 0' + \
							 | 
						||
| 
								 | 
							
								                ' --generateMipChain true' + \
							 | 
						||
| 
								 | 
							
								                ' --inputGammaNumerator ' + input_gamma_numerator + \
							 | 
						||
| 
								 | 
							
								                ' --inputGammaDenominator 1.0' + \
							 | 
						||
| 
								 | 
							
								                ' --outputGammaNumerator 1.0' + \
							 | 
						||
| 
								 | 
							
								                ' --outputGammaDenominator 1.0' + \
							 | 
						||
| 
								 | 
							
								                ' --outputNum 1' + \
							 | 
						||
| 
								 | 
							
								                ' --output0 "' + output_file_rad + '"' + \
							 | 
						||
| 
								 | 
							
								                ' --output0params hdr,rgbe,latlong'
							 | 
						||
| 
								 | 
							
								            if not wrd.lnx_verbose_output:
							 | 
						||
| 
								 | 
							
								                cmd += ' --silent'
							 | 
						||
| 
								 | 
							
								            print(cmd)
							 | 
						||
| 
								 | 
							
								            subprocess.call([cmd], shell=True)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    except KeyboardInterrupt as e:
							 | 
						||
| 
								 | 
							
								        __cmft_end_time_seconds = time.time()
							 | 
						||
| 
								 | 
							
								        check_last_cmft_time()
							 | 
						||
| 
								 | 
							
								        raise e
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    __cmft_end_time_seconds = time.time()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # Remove size extensions in file name
							 | 
						||
| 
								 | 
							
								    mip_w = int(face_size * 4)
							 | 
						||
| 
								 | 
							
								    mip_base = output_file_rad + '_'
							 | 
						||
| 
								 | 
							
								    mip_num = 0
							 | 
						||
| 
								 | 
							
								    while mip_w >= 4:
							 | 
						||
| 
								 | 
							
								        mip_name = mip_base + str(mip_num)
							 | 
						||
| 
								 | 
							
								        os.rename(
							 | 
						||
| 
								 | 
							
								            mip_name + '_' + str(mip_w) + 'x' + str(int(mip_w / 2)) + '.hdr',
							 | 
						||
| 
								 | 
							
								            mip_name + '.hdr')
							 | 
						||
| 
								 | 
							
								        mip_w = int(mip_w / 2)
							 | 
						||
| 
								 | 
							
								        mip_num += 1
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # Append mips
							 | 
						||
| 
								 | 
							
								    generated_files = []
							 | 
						||
| 
								 | 
							
								    for i in range(0, mip_count):
							 | 
						||
| 
								 | 
							
								        generated_files.append(output_file_rad + '_' + str(i))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # Convert to jpgs
							 | 
						||
| 
								 | 
							
								    if disable_hdr is True:
							 | 
						||
| 
								 | 
							
								        for f in generated_files:
							 | 
						||
| 
								 | 
							
								            if lnx.utils.get_os() == 'win':
							 | 
						||
| 
								 | 
							
								                subprocess.call([
							 | 
						||
| 
								 | 
							
								                    kraffiti_path,
							 | 
						||
| 
								 | 
							
								                    'from=' + f + '.hdr',
							 | 
						||
| 
								 | 
							
								                    'to=' + f + '.jpg',
							 | 
						||
| 
								 | 
							
								                    'format=jpg'])
							 | 
						||
| 
								 | 
							
								            else:
							 | 
						||
| 
								 | 
							
								                subprocess.call([
							 | 
						||
| 
								 | 
							
								                    kraffiti_path
							 | 
						||
| 
								 | 
							
								                    + ' from="' + f + '.hdr"'
							 | 
						||
| 
								 | 
							
								                    + ' to="' + f + '.jpg"'
							 | 
						||
| 
								 | 
							
								                    + ' format=jpg'], shell=True)
							 | 
						||
| 
								 | 
							
								            os.remove(f + '.hdr')
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # Scale from (4x2 to 1x1>
							 | 
						||
| 
								 | 
							
								    for i in range(0, 2):
							 | 
						||
| 
								 | 
							
								        last = generated_files[-1]
							 | 
						||
| 
								 | 
							
								        out = output_file_rad + '_' + str(mip_count + i)
							 | 
						||
| 
								 | 
							
								        if lnx.utils.get_os() == 'win':
							 | 
						||
| 
								 | 
							
								            subprocess.call([
							 | 
						||
| 
								 | 
							
								                kraffiti_path,
							 | 
						||
| 
								 | 
							
								                'from=' + last + '.' + rad_format,
							 | 
						||
| 
								 | 
							
								                'to=' + out + '.' + rad_format,
							 | 
						||
| 
								 | 
							
								                'scale=0.5',
							 | 
						||
| 
								 | 
							
								                'format=' + rad_format], shell=True)
							 | 
						||
| 
								 | 
							
								        else:
							 | 
						||
| 
								 | 
							
								            subprocess.call([
							 | 
						||
| 
								 | 
							
								                kraffiti_path
							 | 
						||
| 
								 | 
							
								                + ' from=' + '"' + last + '.' + rad_format + '"'
							 | 
						||
| 
								 | 
							
								                + ' to=' + '"' + out + '.' + rad_format + '"'
							 | 
						||
| 
								 | 
							
								                + ' scale=0.5'
							 | 
						||
| 
								 | 
							
								                + ' format=' + rad_format], shell=True)
							 | 
						||
| 
								 | 
							
								        generated_files.append(out)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    mip_count += 2
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    add_rad_assets(output_file_rad, rad_format, mip_count)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    return mip_count
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def sh_to_json(sh_file):
							 | 
						||
| 
								 | 
							
								    """Parse sh coefs produced by cmft into json array"""
							 | 
						||
| 
								 | 
							
								    with open(sh_file + '.c') as f:
							 | 
						||
| 
								 | 
							
								        sh_lines = f.read().splitlines()
							 | 
						||
| 
								 | 
							
								    band0_line = sh_lines[5]
							 | 
						||
| 
								 | 
							
								    band1_line = sh_lines[6]
							 | 
						||
| 
								 | 
							
								    band2_line = sh_lines[7]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    irradiance_floats = []
							 | 
						||
| 
								 | 
							
								    parse_band_floats(irradiance_floats, band0_line)
							 | 
						||
| 
								 | 
							
								    parse_band_floats(irradiance_floats, band1_line)
							 | 
						||
| 
								 | 
							
								    parse_band_floats(irradiance_floats, band2_line)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # Lower exposure to adjust to Eevee and Cycles
							 | 
						||
| 
								 | 
							
								    for i in range(0, len(irradiance_floats)):
							 | 
						||
| 
								 | 
							
								        irradiance_floats[i] /= 2
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    sh_json = {'irradiance': irradiance_floats}
							 | 
						||
| 
								 | 
							
								    ext = '.lnx' if bpy.data.worlds['Lnx'].lnx_minimize else ''
							 | 
						||
| 
								 | 
							
								    lnx.utils.write_lnx(sh_file + ext, sh_json)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # Clean up .c
							 | 
						||
| 
								 | 
							
								    os.remove(sh_file + '.c')
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def parse_band_floats(irradiance_floats, band_line):
							 | 
						||
| 
								 | 
							
								    string_floats = re.findall(r'[-+]?\d*\.\d+|\d+', band_line)
							 | 
						||
| 
								 | 
							
								    string_floats = string_floats[1:]  # Remove 'Band 0/1/2' number
							 | 
						||
| 
								 | 
							
								    for s in string_floats:
							 | 
						||
| 
								 | 
							
								        irradiance_floats.append(float(s))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def write_sky_irradiance(base_name):
							 | 
						||
| 
								 | 
							
								    # Hosek spherical harmonics
							 | 
						||
| 
								 | 
							
								    irradiance_floats = [
							 | 
						||
| 
								 | 
							
								        1.5519331988822218, 2.3352207154503266, 2.997277451988076,
							 | 
						||
| 
								 | 
							
								        0.2673894962434794, 0.4305630474135794, 0.11331825259716752,
							 | 
						||
| 
								 | 
							
								        -0.04453633521758638, -0.038753175134160295, -0.021302768541875794,
							 | 
						||
| 
								 | 
							
								        0.00055858020486499, 0.000371654770334503, 0.000126606145406403,
							 | 
						||
| 
								 | 
							
								        -0.000135708721978705, -0.000787399554583089, -0.001550090690860059,
							 | 
						||
| 
								 | 
							
								        0.021947399048903773, 0.05453650591711572, 0.08783641266630278,
							 | 
						||
| 
								 | 
							
								        0.17053593578630663, 0.14734127083304463, 0.07775404698816404,
							 | 
						||
| 
								 | 
							
								        -2.6924363189795e-05, -7.9350169701934e-05, -7.559914435231e-05,
							 | 
						||
| 
								 | 
							
								        0.27035455385870993, 0.23122918445556914, 0.12158817295211832]
							 | 
						||
| 
								 | 
							
								    for i in range(0, len(irradiance_floats)):
							 | 
						||
| 
								 | 
							
								        irradiance_floats[i] /= 2
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    envpath = os.path.join(lnx.utils.get_fp_build(), 'compiled', 'Assets', 'envmaps')
							 | 
						||
| 
								 | 
							
								    if not os.path.exists(envpath):
							 | 
						||
| 
								 | 
							
								        os.makedirs(envpath)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    output_file = os.path.join(envpath, base_name + '_irradiance')
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    sh_json = {'irradiance': irradiance_floats}
							 | 
						||
| 
								 | 
							
								    lnx.utils.write_lnx(output_file + '.lnx', sh_json)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    assets.add(output_file + '.lnx')
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def write_color_irradiance(base_name, col):
							 | 
						||
| 
								 | 
							
								    """Constant color irradiance"""
							 | 
						||
| 
								 | 
							
								    # Adjust to Cycles
							 | 
						||
| 
								 | 
							
								    irradiance_floats = [col[0] * 1.13, col[1] * 1.13, col[2] * 1.13]
							 | 
						||
| 
								 | 
							
								    for i in range(0, 24):
							 | 
						||
| 
								 | 
							
								        irradiance_floats.append(0.0)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    envpath = os.path.join(lnx.utils.get_fp_build(), 'compiled', 'Assets', 'envmaps')
							 | 
						||
| 
								 | 
							
								    if not os.path.exists(envpath):
							 | 
						||
| 
								 | 
							
								        os.makedirs(envpath)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    output_file = os.path.join(envpath, base_name + '_irradiance')
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    sh_json = {'irradiance': irradiance_floats}
							 | 
						||
| 
								 | 
							
								    lnx.utils.write_lnx(output_file + '.lnx', sh_json)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    assets.add(output_file + '.lnx')
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def check_last_cmft_time():
							 | 
						||
| 
								 | 
							
								    global __cmft_start_time_seconds, __cmft_end_time_seconds
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if __cmft_start_time_seconds <= 0.0:
							 | 
						||
| 
								 | 
							
								        # CMFT was not called
							 | 
						||
| 
								 | 
							
								        return
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if __cmft_end_time_seconds <= 0.0:
							 | 
						||
| 
								 | 
							
								        # Build was aborted and CMFT didn't finish
							 | 
						||
| 
								 | 
							
								        __cmft_end_time_seconds = time.time()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    cmft_duration_seconds = __cmft_end_time_seconds - __cmft_start_time_seconds
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # We could also check here if the user already disabled OpenCL and
							 | 
						||
| 
								 | 
							
								    # then don't show the warning, but this might trick users into
							 | 
						||
| 
								 | 
							
								    # thinking they have fixed their issue even in the case that the
							 | 
						||
| 
								 | 
							
								    # slow runtime isn't caused by using OpenCL
							 | 
						||
| 
								 | 
							
								    if cmft_duration_seconds > 20:
							 | 
						||
| 
								 | 
							
								        log.warn(
							 | 
						||
| 
								 | 
							
								            "Generating the radiance map with CMFT took an unusual amount"
							 | 
						||
| 
								 | 
							
								            f" of time ({cmft_duration_seconds:.2f} s). If the issue persists,"
							 | 
						||
| 
								 | 
							
								            " try disabling \"CMFT: Use OpenCL\" in the Leenkx add-on preferences."
							 | 
						||
| 
								 | 
							
								            " For more information see https://github.com/leenkx3d/leenkx/issues/2760."
							 | 
						||
| 
								 | 
							
								        )
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    __cmft_start_time_seconds = 0.0
							 | 
						||
| 
								 | 
							
								    __cmft_end_time_seconds = 0.0
							 |