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,
}
|