forked from LeenkxTeam/LNXSDK
Merge pull request 'main' (#111) from Onek8/LNXSDK:main into main
Reviewed-on: LeenkxTeam/LNXSDK#111
This commit is contained in:
@ -53,9 +53,69 @@ else:
|
|||||||
# Particle info export
|
# Particle info export
|
||||||
particle_info: Dict[str, bool] = {}
|
particle_info: Dict[str, bool] = {}
|
||||||
|
|
||||||
|
texture_resize_cache = {}
|
||||||
|
|
||||||
state: Optional[ParserState]
|
state: Optional[ParserState]
|
||||||
|
|
||||||
|
|
||||||
|
def resize_texture_if_needed(image: bpy.types.Image, filepath: str, max_size: int) -> str:
|
||||||
|
"""
|
||||||
|
Resize texture if it exceeds max_size. Returns path to resized texture or original.
|
||||||
|
Caches results to avoid re-processing unchanged textures.
|
||||||
|
"""
|
||||||
|
if max_size <= 0:
|
||||||
|
return filepath
|
||||||
|
|
||||||
|
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]
|
||||||
|
if os.path.exists(cached_path):
|
||||||
|
return cached_path
|
||||||
|
|
||||||
|
width, height = image.size[0], image.size[1]
|
||||||
|
if width > height:
|
||||||
|
new_width = max_size
|
||||||
|
new_height = int((height / width) * max_size)
|
||||||
|
else:
|
||||||
|
new_height = max_size
|
||||||
|
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')
|
||||||
|
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}")
|
||||||
|
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
|
||||||
|
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}")
|
||||||
|
return filepath
|
||||||
|
except Exception as e:
|
||||||
|
print(f"WARNING: Failed to resize {basename}: {e}")
|
||||||
|
return filepath
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def parse(nodes, con: ShaderContext,
|
def parse(nodes, con: ShaderContext,
|
||||||
vert: Shader, frag: Shader, geom: Shader, tesc: Shader, tese: Shader,
|
vert: Shader, frag: Shader, geom: Shader, tesc: Shader, tese: Shader,
|
||||||
parse_surface=True, parse_opacity=True, parse_displacement=True, basecol_only=False):
|
parse_surface=True, parse_opacity=True, parse_displacement=True, basecol_only=False):
|
||||||
@ -897,6 +957,11 @@ def make_texture(
|
|||||||
lnx.utils.convert_image(image, converted_path, file_format=fmt)
|
lnx.utils.convert_image(image, converted_path, file_format=fmt)
|
||||||
lnx.assets.add(converted_path)
|
lnx.assets.add(converted_path)
|
||||||
else:
|
else:
|
||||||
|
wrd = bpy.data.worlds['Lnx']
|
||||||
|
max_size = int(wrd.lnx_max_texture_size)
|
||||||
|
if max_size > 0 and image is not None:
|
||||||
|
filepath = resize_texture_if_needed(image, filepath, max_size)
|
||||||
|
|
||||||
# Link image path to assets
|
# Link image path to assets
|
||||||
# TODO: Khamake converts .PNG to .jpg? Convert ext to lowercase on windows
|
# TODO: Khamake converts .PNG to .jpg? Convert ext to lowercase on windows
|
||||||
if lnx.utils.get_os() == 'win':
|
if lnx.utils.get_os() == 'win':
|
||||||
|
|||||||
@ -283,6 +283,21 @@ def init_properties():
|
|||||||
)
|
)
|
||||||
bpy.types.World.lnx_texture_quality = FloatProperty(name="Texture Quality", default=1.0, min=0.0, max=1.0, subtype='FACTOR', update=assets.invalidate_compiler_cache)
|
bpy.types.World.lnx_texture_quality = FloatProperty(name="Texture Quality", default=1.0, min=0.0, max=1.0, subtype='FACTOR', update=assets.invalidate_compiler_cache)
|
||||||
bpy.types.World.lnx_sound_quality = FloatProperty(name="Sound Quality", default=0.9, min=0.0, max=1.0, subtype='FACTOR', update=assets.invalidate_compiler_cache)
|
bpy.types.World.lnx_sound_quality = FloatProperty(name="Sound Quality", default=0.9, min=0.0, max=1.0, subtype='FACTOR', update=assets.invalidate_compiler_cache)
|
||||||
|
bpy.types.World.lnx_max_texture_size = EnumProperty(
|
||||||
|
name="Max Texture Size",
|
||||||
|
description="Maximum texture resolution for runtime. Larger textures will be automatically downscaled.",
|
||||||
|
items=[
|
||||||
|
('0', 'Unlimited', 'No size limit (may cause WebGL texture unit errors with many textures)'),
|
||||||
|
('256', '0.25K (256px)', 'Maximum 0.25K resolution - Best for mobile'),
|
||||||
|
('512', '0.5K (512px)', 'Maximum 0.5K resolution - Best for mobile'),
|
||||||
|
('1024', '1K (1024px)', 'Maximum 1K resolution - Best for mobile'),
|
||||||
|
('2048', '2K (2048px)', 'Maximum 2K resolution - Recommended for WebGL'),
|
||||||
|
('4096', '4K (4096px)', 'Maximum 4K resolution - Desktop only'),
|
||||||
|
('8192', '8K (8192px)', 'Maximum 8K resolution - High-end desktop only'),
|
||||||
|
],
|
||||||
|
default='2048',
|
||||||
|
update=assets.invalidate_compiler_cache
|
||||||
|
)
|
||||||
bpy.types.World.lnx_copy_override = BoolProperty(name="Copy Override", description="Overrides any existing files when copying", default=False, update=assets.invalidate_compiled_data)
|
bpy.types.World.lnx_copy_override = BoolProperty(name="Copy Override", description="Overrides any existing files when copying", default=False, update=assets.invalidate_compiled_data)
|
||||||
bpy.types.World.lnx_minimize = BoolProperty(name="Binary Scene Data", description="Export scene data in binary", default=True, update=assets.invalidate_compiled_data)
|
bpy.types.World.lnx_minimize = BoolProperty(name="Binary Scene Data", description="Export scene data in binary", default=True, update=assets.invalidate_compiled_data)
|
||||||
bpy.types.World.lnx_minify_js = BoolProperty(name="Minify JS", description="Minimize JavaScript output when publishing", default=True)
|
bpy.types.World.lnx_minify_js = BoolProperty(name="Minify JS", description="Minimize JavaScript output when publishing", default=True)
|
||||||
|
|||||||
@ -1220,6 +1220,12 @@ class LNX_PT_ProjectFlagsPanel(bpy.types.Panel):
|
|||||||
row.prop(wrd, 'lnx_canvas_img_scaling_quality', expand=True)
|
row.prop(wrd, 'lnx_canvas_img_scaling_quality', expand=True)
|
||||||
col.prop(wrd, 'lnx_texture_quality')
|
col.prop(wrd, 'lnx_texture_quality')
|
||||||
col.prop(wrd, 'lnx_sound_quality')
|
col.prop(wrd, 'lnx_sound_quality')
|
||||||
|
|
||||||
|
col = column_with_heading(layout, 'Texture Optimization')
|
||||||
|
col.prop(wrd, 'lnx_max_texture_size')
|
||||||
|
if wrd.lnx_max_texture_size != '0':
|
||||||
|
box = col.box()
|
||||||
|
box.label(text=f"Textures larger than {wrd.lnx_max_texture_size}px will be automatically downscaled on export", icon='INFO')
|
||||||
|
|
||||||
col = column_with_heading(layout, 'External Assets')
|
col = column_with_heading(layout, 'External Assets')
|
||||||
col.prop(wrd, 'lnx_copy_override')
|
col.prop(wrd, 'lnx_copy_override')
|
||||||
|
|||||||
Reference in New Issue
Block a user