File size: 8,350 Bytes
c840ad0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
"""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