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