prmpt-ar-be / services /ar_material_service.py
Nikkon
Deploy PromptAR backend to HF Spaces
c840ad0
"""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