Spaces:
Sleeping
Sleeping
| """AR Material Processing Service for normalizing 3D model materials for AR visibility.""" | |
| import logging | |
| from pathlib import Path | |
| from typing import TYPE_CHECKING | |
| if TYPE_CHECKING: | |
| from pygltflib import GLTF2 | |
| logger = logging.getLogger(__name__) | |
| # ============================================================================ | |
| # Configuration | |
| # ============================================================================ | |
| # Material brightness configuration for AR visibility | |
| # Adjust these values to control model brightness: | |
| # - Higher emissive = brighter glow (0.0-1.0, typical: 0.2-0.5) | |
| # - Higher baseColor boost = brighter colors (1.0 = no change, 1.5 = 50% brighter) | |
| # - Lower metallic threshold = more materials become non-metallic (0.0-1.0) | |
| # - Higher roughness min = more matte surface (0.0-1.0, typical: 0.7-1.0) | |
| BRIGHTNESS_CONFIG = { | |
| "metallic_threshold": 0.1, # Any metallicFactor > this becomes 0.0 | |
| "metallic_target": 0.0, # Target metallicFactor for bright materials | |
| "roughness_min": 0.9, # Minimum roughnessFactor (higher = more matte) | |
| "base_color_boost": 1.5, # Multiplier for baseColorFactor (1.5 = 50% brighter) | |
| "emissive_base": 0.3, # Base emissive glow (0.0-1.0) | |
| "emissive_max": 0.5, # Maximum emissive glow (0.0-1.0) | |
| } | |
| # ============================================================================ | |
| # Core Material Normalization Functions | |
| # ============================================================================ | |
| def normalize_gltf_materials(gltf: "GLTF2") -> None: | |
| """Normalize materials in a GLTF object (in-memory) for AR visibility. | |
| This function works on a GLTF object that's already loaded in memory. | |
| It adjusts material properties to ensure models appear bright in AR. | |
| Args: | |
| gltf: GLTF2 object to normalize | |
| """ | |
| # Normalize materials to ensure they render correctly in AR | |
| # High metallicFactor can cause models to appear dark/black | |
| # BoxTextured works because it has metallicFactor=0.0 (non-metallic) | |
| if gltf.materials: | |
| for i, material in enumerate(gltf.materials): | |
| if ( | |
| hasattr(material, "pbrMetallicRoughness") | |
| and material.pbrMetallicRoughness | |
| ): | |
| pbr = material.pbrMetallicRoughness | |
| # If metallicFactor is too high, reduce it to target value | |
| # High metallic = mirrors environment, needs strong lighting | |
| # Non-metallic (0.0) = uses base color/texture, works better in AR | |
| if hasattr(pbr, "metallicFactor") and pbr.metallicFactor is not None: | |
| original_metallic = pbr.metallicFactor | |
| if original_metallic > BRIGHTNESS_CONFIG["metallic_threshold"]: | |
| pbr.metallicFactor = BRIGHTNESS_CONFIG["metallic_target"] | |
| logger.info( | |
| f"Normalized material {i}: metallicFactor {original_metallic} -> {pbr.metallicFactor} " | |
| f"(threshold: {BRIGHTNESS_CONFIG['metallic_threshold']}, target: {BRIGHTNESS_CONFIG['metallic_target']})" | |
| ) | |
| # Ensure roughnessFactor is reasonable (0.0-1.0) | |
| # Lower roughness = more shiny, but can also appear darker | |
| # Higher roughness = more matte, better visibility | |
| if hasattr(pbr, "roughnessFactor") and pbr.roughnessFactor is not None: | |
| if pbr.roughnessFactor < BRIGHTNESS_CONFIG["roughness_min"]: | |
| # Increase roughness to minimum for maximum visibility | |
| pbr.roughnessFactor = max( | |
| pbr.roughnessFactor, | |
| BRIGHTNESS_CONFIG["roughness_min"], | |
| ) | |
| logger.info( | |
| f"Normalized material {i}: roughnessFactor -> {pbr.roughnessFactor} " | |
| f"(min: {BRIGHTNESS_CONFIG['roughness_min']})" | |
| ) | |
| # Ensure baseColorFactor is set (white if missing) | |
| if not hasattr(pbr, "baseColorFactor") or pbr.baseColorFactor is None: | |
| pbr.baseColorFactor = [1.0, 1.0, 1.0, 1.0] | |
| logger.info(f"Added baseColorFactor to material {i} (white)") | |
| else: | |
| # Boost baseColorFactor to increase brightness | |
| base_color = pbr.baseColorFactor | |
| if isinstance(base_color, list) and len(base_color) >= 3: | |
| # Apply brightness boost multiplier | |
| boost = BRIGHTNESS_CONFIG["base_color_boost"] | |
| boosted_color = [ | |
| min(1.0, base_color[0] * boost), | |
| min(1.0, base_color[1] * boost), | |
| min(1.0, base_color[2] * boost), | |
| base_color[3] if len(base_color) > 3 else 1.0, | |
| ] | |
| if boosted_color != base_color: | |
| pbr.baseColorFactor = boosted_color | |
| boost_percent = int((boost - 1.0) * 100) | |
| logger.info( | |
| f"Boosted baseColorFactor for material {i} " | |
| f"({boost_percent}% brightness increase, multiplier: {boost})" | |
| ) | |
| # Add emissive factor to make models super bright and visible | |
| if ( | |
| not hasattr(material, "emissiveFactor") | |
| or material.emissiveFactor is None | |
| ): | |
| emissive_value = BRIGHTNESS_CONFIG["emissive_base"] | |
| material.emissiveFactor = [ | |
| emissive_value, | |
| emissive_value, | |
| emissive_value, | |
| ] | |
| logger.info( | |
| f"Added emissiveFactor to material {i} " | |
| f"(glow: {emissive_value}, config: {BRIGHTNESS_CONFIG['emissive_base']})" | |
| ) | |
| else: | |
| # Boost existing emissive | |
| emissive = material.emissiveFactor | |
| if isinstance(emissive, list) and len(emissive) >= 3: | |
| emissive_max = BRIGHTNESS_CONFIG["emissive_max"] | |
| emissive_base = BRIGHTNESS_CONFIG["emissive_base"] | |
| boosted_emissive = [ | |
| min(emissive_max, emissive[0] + emissive_base), | |
| min(emissive_max, emissive[1] + emissive_base), | |
| min(emissive_max, emissive[2] + emissive_base), | |
| ] | |
| material.emissiveFactor = boosted_emissive | |
| logger.info( | |
| f"Boosted emissiveFactor for material {i} " | |
| f"(glow: {boosted_emissive}, max: {emissive_max})" | |
| ) | |
| # ============================================================================ | |
| # File-based Material Normalization | |
| # ============================================================================ | |
| def normalize_materials_for_ar(glb_path: Path) -> None: | |
| """Normalize materials in GLB/GLTF file for AR visibility. | |
| This function can be applied to both GLB and GLTF files directly. | |
| It adjusts material properties to ensure models appear bright in AR. | |
| Args: | |
| glb_path: Path to the GLB or GLTF file to modify | |
| """ | |
| try: | |
| from pygltflib import GLTF2 | |
| logger.info(f"Loading GLB/GLTF for brightness normalization: {glb_path}") | |
| gltf = GLTF2.load(str(glb_path)) | |
| # Normalize materials using the shared function | |
| normalize_gltf_materials(gltf) | |
| # Save the modified GLB/GLTF file | |
| gltf.save(str(glb_path)) | |
| logger.info(f"✓ Brightness normalization saved to: {glb_path}") | |
| except ImportError: | |
| logger.error("pygltflib not available - cannot normalize materials") | |
| raise RuntimeError("pygltflib required for material normalization") | |
| except Exception as e: | |
| logger.error(f"Failed to normalize materials: {e}") | |
| import traceback | |
| logger.error(f"Material normalization traceback: {traceback.format_exc()}") | |
| raise RuntimeError(f"Failed to normalize materials: {e}") from e | |