import json import struct import numpy as np import DracoPy class DracoGLBParser: """使用 DracoPy 解析包含 Draco 压缩的 GLB 文件""" def __init__(self, glb_file_path): self.glb_file_path = glb_file_path self.json_data = None self.binary_data = None self.decoded_meshes = [] # 缓存解码后的网格数据 def parse_glb_structure(self): """解析 GLB 文件结构""" with open(self.glb_file_path, 'rb') as f: # 读取 GLB 头部 magic = f.read(4) version = struct.unpack(' 0: if isinstance(faces_data[0], list) or isinstance(faces_data[0], tuple): # 如果是列表的列表 result['faces'] = np.array(faces_data, dtype=np.uint32) else: # 如果是扁平化的数组 result['faces'] = np.array(faces_data, dtype=np.uint32).reshape(-1, 3) print(f" 面数量: {len(result['faces']) if result['faces'] is not None else 0}") # 获取属性数据 if hasattr(draco_decoder, 'attributes'): attrs = draco_decoder.attributes # 根据属性映射查找数据 for gltf_attr_name, draco_attr_id in attributes.items(): if draco_attr_id in attrs: attr_data = attrs[draco_attr_id] if gltf_attr_name == 'POSITION': result['vertices'] = np.array(attr_data, dtype=np.float32) elif gltf_attr_name == 'TEXCOORD_0': result['texcoords'] = np.array(attr_data, dtype=np.float32) elif gltf_attr_name == '_BATCHID': result['batch_ids'] = np.array(attr_data, dtype=np.uint32) elif gltf_attr_name == 'NORMAL': result['normals'] = np.array(attr_data, dtype=np.float32) elif gltf_attr_name == 'COLOR_0': result['colors'] = np.array(attr_data, dtype=np.float32) print(f" 已提取属性: {gltf_attr_name} (ID: {draco_attr_id})") # 如果没有通过attributes获取到顶点,尝试其他方式 if result['vertices'] is None and hasattr(draco_decoder, 'get_points'): try: result['vertices'] = np.array(draco_decoder.get_points(), dtype=np.float32) except: pass return result def save_decoded_meshes(self, meshes, output_format='obj'): """保存解码后的网格""" import os base_name = os.path.splitext(os.path.basename(self.glb_file_path))[0] output_dir = f"{base_name}_decoded" if not os.path.exists(output_dir): os.makedirs(output_dir) for mesh in meshes: filename = f"{output_dir}/{mesh['name']}_p{mesh['primitive_idx']}.{output_format}" if output_format == 'obj': self._save_as_obj(mesh, filename) elif output_format == 'ply': self._save_as_ply(mesh, filename) elif output_format == 'npz': self._save_as_npz(mesh, filename) else: print(f"不支持的格式: {output_format}") continue print(f"已保存: {filename}") def _save_as_obj(self, mesh, filename): """保存为 OBJ 格式""" with open(filename, 'w') as f: # 写入顶点 if mesh['vertices'] is not None: for v in mesh['vertices']: f.write(f"v {v[0]} {v[1]} {v[2]}\n") # 写入纹理坐标 if mesh['texcoords'] is not None: for uv in mesh['texcoords']: f.write(f"vt {uv[0]} {uv[1]}\n") # 写入法线 if mesh['normals'] is not None: for n in mesh['normals']: f.write(f"vn {n[0]} {n[1]} {n[2]}\n") # 写入面 if mesh['faces'] is not None: for face in mesh['faces']: # OBJ 索引从1开始 face_indices = [str(idx + 1) for idx in face] f.write(f"f {' '.join(face_indices)}\n") def _save_as_ply(self, mesh, filename): """保存为 PLY 格式""" from plyfile import PlyData, PlyElement import numpy as np vertices = mesh['vertices'] faces = mesh['faces'] if vertices is None: return # 创建顶点数据 vertex_data = np.zeros(len(vertices), dtype=[ ('x', 'f4'), ('y', 'f4'), ('z', 'f4') ]) vertex_data['x'] = vertices[:, 0] vertex_data['y'] = vertices[:, 1] vertex_data['z'] = vertices[:, 2] # 创建面数据 if faces is not None: face_data = np.zeros(len(faces), dtype=[('vertex_indices', 'i4', (3,))]) face_data['vertex_indices'] = faces # 写入文件 vertex_element = PlyElement.describe(vertex_data, 'vertex') if faces is not None: face_element = PlyElement.describe(face_data, 'face') PlyData([vertex_element, face_element], text=False).write(filename) else: PlyData([vertex_element], text=False).write(filename) def _save_as_npz(self, mesh, filename): """保存为 NPZ 格式""" np.savez( filename, vertices=mesh['vertices'], faces=mesh['faces'], texcoords=mesh['texcoords'], batch_ids=mesh['batch_ids'], normals=mesh['normals'], colors=mesh['colors'] ) # 使用示例 def main(): # 初始化解析器 parser = DracoGLBParser(r"D:\devForBdzlWork\ai_project_v1\b3dm\test\temp_glb\temp_6e895637.glb") # 解析 GLB 结构 parser.parse_glb_structure() # 分析结构 parser.analyze_structure() # 解码 Draco 网格 meshes = parser.decode_draco_meshes() # 使用新增的顶点获取方法 print("\n" + "=" * 60) print("顶点获取方法演示:") print("=" * 60) # 1. 获取第一个网格的第一个图元的顶点 vertices = parser.get_vertices(mesh_idx=0, primitive_idx=0) if vertices is not None: print(f"1. 获取第一个网格顶点:") print(f" 形状: {vertices.shape}") print(f" 数据类型: {vertices.dtype}") print(f" 前5个顶点: \n{vertices[:5]}") # 2. 获取所有顶点(合并) all_vertices = parser.get_all_vertices() if all_vertices is not None: print(f"\n2. 获取所有网格顶点(合并):") print(f" 总顶点数: {len(all_vertices)}") print(f" 形状: {all_vertices.shape}") # 3. 获取总顶点数 total_vertices = parser.get_vertex_count() print(f"\n3. 总顶点数: {total_vertices}") # 4. 根据网格名称获取顶点 if meshes: mesh_name = meshes[0]['name'] vertices_list = parser.get_vertices_by_mesh_name(mesh_name) print(f"\n4. 根据名称 '{mesh_name}' 获取的顶点:") for i, verts in enumerate(vertices_list): print(f" 图元 {i}: {verts.shape if verts is not None else 'None'}") # 保存解码后的网格 parser.save_decoded_meshes(meshes, output_format='obj') if __name__ == "__main__": main()