File size: 5,581 Bytes
4921374
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
177
178
179
180
181
"""Mesh utilities for export and manipulation."""

import os
from pathlib import Path
from typing import Dict, List, Optional, Union

import numpy as np


def export_mesh(
    mesh,
    output_path: Union[str, Path],
    format: str = "glb",
    materials: Optional[List[Dict]] = None,
) -> str:
    """
    Export mesh to various 3D formats.
    
    Supported formats: glb, fbx, obj, usdz, ply
    """
    output_path = Path(output_path)
    output_path.parent.mkdir(parents=True, exist_ok=True)
    
    try:
        import trimesh
    except ImportError:
        raise ImportError("trimesh is required for mesh export")
    
    format = format.lower()
    
    if format == "glb" or format == "gltf":
        # Export as GLB (GL Transmission Format)
        mesh.export(str(output_path))
        
    elif format == "fbx":
        # Export as FBX
        # trimesh can export some formats; for FBX we may need assimp
        mesh.export(str(output_path))
        
    elif format == "obj":
        # Export as OBJ with MTL
        mesh.export(str(output_path))
        
    elif format == "usdz":
        # USDZ requires USD libraries
        # For now, export as GLB and note conversion needed
        glb_path = output_path.with_suffix(".glb")
        mesh.export(str(glb_path))
        print(f"USDZ export: converted to GLB at {glb_path}. Use Apple's usdzconvert.")
        
    elif format == "ply":
        # Export as PLY (Stanford Polygon Format)
        mesh.export(str(output_path))
        
    else:
        raise ValueError(f"Unsupported format: {format}")
    
    return str(output_path)


def export_gaussian_splatting(
    gaussian_cloud: np.ndarray,
    output_path: Union[str, Path],
) -> str:
    """
    Export Gaussian Splatting representation to PLY format.
    
    Args:
        gaussian_cloud: [N, 14] array with columns:
            [x, y, z, scale_x, scale_y, scale_z,
             rot_qx, rot_qy, rot_qz, rot_qw,
             r, g, b, opacity]
    """
    output_path = Path(output_path)
    output_path.parent.mkdir(parents=True, exist_ok=True)
    
    try:
        from plyfile import PlyData, PlyElement
    except ImportError:
        # Fallback: write simple ASCII PLY
        _write_ascii_ply(gaussian_cloud, output_path)
        return str(output_path)
    
    # Write binary PLY with Gaussian attributes
    num_points = len(gaussian_cloud)
    
    # Define dtype for Gaussian splatting format
    dtype = [
        ('x', 'f4'), ('y', 'f4'), ('z', 'f4'),
        ('scale_0', 'f4'), ('scale_1', 'f4'), ('scale_2', 'f4'),
        ('rot_0', 'f4'), ('rot_1', 'f4'), ('rot_2', 'f4'), ('rot_3', 'f4'),
        ('f_dc_0', 'f4'), ('f_dc_1', 'f4'), ('f_dc_2', 'f4'),
        ('opacity', 'f4'),
    ]
    
    # Convert RGB to spherical harmonics DC term (simplified)
    # In production, would also include SH bands 1, 2, 3
    
    vertices = np.zeros(num_points, dtype=dtype)
    vertices['x'] = gaussian_cloud[:, 0]
    vertices['y'] = gaussian_cloud[:, 1]
    vertices['z'] = gaussian_cloud[:, 2]
    vertices['scale_0'] = gaussian_cloud[:, 3]
    vertices['scale_1'] = gaussian_cloud[:, 4]
    vertices['scale_2'] = gaussian_cloud[:, 5]
    vertices['rot_0'] = gaussian_cloud[:, 6]
    vertices['rot_1'] = gaussian_cloud[:, 7]
    vertices['rot_2'] = gaussian_cloud[:, 8]
    vertices['rot_3'] = gaussian_cloud[:, 9]
    vertices['f_dc_0'] = gaussian_cloud[:, 10]
    vertices['f_dc_1'] = gaussian_cloud[:, 11]
    vertices['f_dc_2'] = gaussian_cloud[:, 12]
    vertices['opacity'] = gaussian_cloud[:, 13]
    
    el = PlyElement.describe(vertices, 'vertex')
    PlyData([el], text=True).write(str(output_path))
    
    return str(output_path)


def _write_ascii_ply(gaussian_cloud: np.ndarray, output_path: Path):
    """Write simple ASCII PLY fallback."""
    num_points = len(gaussian_cloud)
    
    with open(output_path, 'w') as f:
        f.write("ply\n")
        f.write("format ascii 1.0\n")
        f.write(f"element vertex {num_points}\n")
        f.write("property float x\n")
        f.write("property float y\n")
        f.write("property float z\n")
        f.write("property float scale_0\n")
        f.write("property float scale_1\n")
        f.write("property float scale_2\n")
        f.write("property float rot_0\n")
        f.write("property float rot_1\n")
        f.write("property float rot_2\n")
        f.write("property float rot_3\n")
        f.write("property float f_dc_0\n")
        f.write("property float f_dc_1\n")
        f.write("property float f_dc_2\n")
        f.write("property float opacity\n")
        f.write("end_header\n")
        
        for point in gaussian_cloud:
            f.write(" ".join(f"{v:.6f}" for v in point) + "\n")


def decimate_mesh(mesh, target_faces: int = 10000):
    """Reduce mesh complexity for mobile/VR."""
    try:
        import trimesh
        if hasattr(mesh, 'simplify'):
            return mesh.simplify(target_faces)
    except Exception:
        pass
    return mesh


def compute_uv_atlas(mesh):
    """Compute UV atlas for mesh texture mapping."""
    try:
        import xatlas
        # Placeholder: would use xatlas for UV unwrapping
        return mesh
    except ImportError:
        return mesh


def merge_materials(materials: List[Dict]) -> Dict:
    """Merge multiple PBR materials into a single material atlas."""
    # In production: create texture atlas
    # For now, return first material
    if materials:
        return materials[0]
    return {
        "albedo": [0.7, 0.7, 0.7],
        "metallic": 0.0,
        "roughness": 0.5,
    }