diff --git a/leenkx/blender/lnx/material/cycles.py b/leenkx/blender/lnx/material/cycles.py index 2b7ec819..f74fcf82 100644 --- a/leenkx/blender/lnx/material/cycles.py +++ b/leenkx/blender/lnx/material/cycles.py @@ -69,9 +69,12 @@ def resize_texture_if_needed(image: bpy.types.Image, filepath: str, max_size: in if image.size[0] <= max_size and image.size[1] <= max_size: return filepath - cache_key = (filepath, max_size, os.path.getmtime(filepath) if os.path.exists(filepath) else 0) - if cache_key in texture_resize_cache: - cached_path = texture_resize_cache[cache_key] + wrd = bpy.data.worlds['Lnx'] + texture_quality = wrd.lnx_texture_quality + + cache_key = (filepath, max_size, texture_quality, os.path.getmtime(filepath) if os.path.exists(filepath) else 0) + if cache_key in _texture_resize_cache: + cached_path = _texture_resize_cache[cache_key] if os.path.exists(cached_path): return cached_path @@ -84,38 +87,78 @@ def resize_texture_if_needed(image: bpy.types.Image, filepath: str, max_size: in new_width = int((width / height) * max_size) build_dir = lnx.utils.get_fp_build() - resized_dir = os.path.join(build_dir, 'compiled', 'Assets', 'resized_textures') + resized_dir = os.path.join(build_dir, 'compiled', 'Assets', 'unpacked') os.makedirs(resized_dir, exist_ok=True) basename = os.path.basename(filepath) name, ext = os.path.splitext(basename) - resized_path = os.path.join(resized_dir, f"{name}_{max_size}px{ext}") + quality_suffix = f"q{int(texture_quality * 100)}" + resized_path = os.path.join(resized_dir, f"{name}_{max_size}px_{quality_suffix}{ext}") + if os.path.exists(resized_path): src_mtime = os.path.getmtime(filepath) dst_mtime = os.path.getmtime(resized_path) if dst_mtime >= src_mtime: - print(f"Using cached: {basename} -> {new_width}x{new_height}") - texture_resize_cache[cache_key] = resized_path + _texture_resize_cache[cache_key] = resized_path return resized_path + try: - from PIL import Image as PILImage - img = PILImage.open(filepath) - img_resized = img.resize((new_width, new_height), PILImage.Resampling.LANCZOS) - img_resized.save(resized_path, quality=95, optimize=True) - print(f"Resized: {basename} {width}x{height} -> {new_width}x{new_height}") - texture_resize_cache[cache_key] = resized_path - return resized_path - except ImportError: - print(f"WARNING: PIL/Pillow not installed. Install with: pip install Pillow") - print(f"Skipping resize for: {basename}") + ffmpeg_path = lnx.utils.get_ffmpeg_path() + + if ffmpeg_path is None or ffmpeg_path == '': + print(f"[Texture Optimizer] WARNING: FFmpeg not found. Please set FFmpeg path in addon preferences.") + print(f"[Texture Optimizer] Skipping resize for: {basename}") + return filepath + + file_ext = os.path.splitext(filepath)[1].lower().lstrip('.') + + cmd = [ + ffmpeg_path, + '-y', + '-i', filepath, + '-vf', f'scale={new_width}:{new_height}:flags=lanczos', + ] + + if file_ext in ('png', 'tga', 'bmp'): + compression_level = round((1.0 - texture_quality) * 9) + cmd.extend(['-compression_level', str(compression_level)]) + else: + qscale = round(2 + (1.0 - texture_quality) * 29) + cmd.extend(['-q:v', str(qscale)]) + + cmd.append(resized_path) + + startupinfo = None + if os.name == 'nt': + startupinfo = subprocess.STARTUPINFO() + startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW + startupinfo.wShowWindow = subprocess.SW_HIDE + + result = subprocess.run( + cmd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + startupinfo=startupinfo, + timeout=60 + ) + + if result.returncode == 0 and os.path.exists(resized_path): + print(f"[Texture Optimizer] Resized: {basename} {width}x{height} -> {new_width}x{new_height}") + _texture_resize_cache[cache_key] = resized_path + return resized_path + else: + error_msg = result.stderr.decode('utf-8', errors='ignore') if result.stderr else 'Unknown error' + print(f"[Texture Optimizer] WARNING: FFmpeg failed to resize {basename}: {error_msg}") + return filepath + + except subprocess.TimeoutExpired: + print(f"[Texture Optimizer] WARNING: FFmpeg timeout while resizing {basename}") return filepath except Exception as e: - print(f"WARNING: Failed to resize {basename}: {e}") + print(f"[Texture Optimizer] WARNING: Failed to resize {basename}: {e}") return filepath - - def parse(nodes, con: ShaderContext, vert: Shader, frag: Shader, geom: Shader, tesc: Shader, tese: Shader, parse_surface=True, parse_opacity=True, parse_displacement=True, basecol_only=False):