#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ 3D Tiles Tileset to PLY Converter 将整个3D Tiles tileset转换为单个PLY文件 """ import json import struct import os import sys from pathlib import Path import numpy as np try: import DracoPy except ImportError: print("警告: DracoPy库未安装,无法处理Draco压缩的数据") print("请运行: pip install DracoPy") DracoPy = None class TilesetToPLYConverter: def __init__(self): self.all_vertices = [] self.vertex_count = 0 def multiply_matrix_vector(self, matrix, vector): """4x4矩阵与4D向量相乘""" # matrix是16个元素的列表,按列主序排列 # 转换为4x4矩阵(行主序) m = [ [matrix[0], matrix[4], matrix[8], matrix[12]], [matrix[1], matrix[5], matrix[9], matrix[13]], [matrix[2], matrix[6], matrix[10], matrix[14]], [matrix[3], matrix[7], matrix[11], matrix[15]] ] # 向量扩展为齐次坐标 [x, y, z, 1] v = [vector[0], vector[1], vector[2], 1.0] # 矩阵乘法 result = [ m[0][0]*v[0] + m[0][1]*v[1] + m[0][2]*v[2] + m[0][3]*v[3], m[1][0]*v[0] + m[1][1]*v[1] + m[1][2]*v[2] + m[1][3]*v[3], m[2][0]*v[0] + m[2][1]*v[1] + m[2][2]*v[2] + m[2][3]*v[3] ] return result def multiply_matrices(self, m1, m2): """两个4x4矩阵相乘""" # 将16元素列表转换为4x4矩阵 def list_to_matrix(lst): return [ [lst[0], lst[4], lst[8], lst[12]], [lst[1], lst[5], lst[9], lst[13]], [lst[2], lst[6], lst[10], lst[14]], [lst[3], lst[7], lst[11], lst[15]] ] def matrix_to_list(mat): return [ mat[0][0], mat[1][0], mat[2][0], mat[3][0], mat[0][1], mat[1][1], mat[2][1], mat[3][1], mat[0][2], mat[1][2], mat[2][2], mat[3][2], mat[0][3], mat[1][3], mat[2][3], mat[3][3] ] a = list_to_matrix(m1) b = list_to_matrix(m2) result = [[0 for _ in range(4)] for _ in range(4)] for i in range(4): for j in range(4): for k in range(4): result[i][j] += a[i][k] * b[k][j] return matrix_to_list(result) def apply_transform_to_vertices(self, vertices, transform_matrix): """对顶点应用变换矩阵""" if not transform_matrix: return vertices transformed_vertices = [] for vertex in vertices: transformed = self.multiply_matrix_vector(transform_matrix, vertex) transformed_vertices.append(transformed) return transformed_vertices def parse_tileset_json(self, tileset_path, parent_transform=None): """解析tileset.json文件,收集B3DM文件和变换矩阵""" try: with open(tileset_path, 'r', encoding='utf-8') as f: tileset_data = json.load(f) b3dm_files = [] def process_node(node, base_path, accumulated_transform): # 获取当前节点的变换矩阵 current_transform = node.get('transform') # 计算累积变换矩阵 if current_transform and accumulated_transform: # 矩阵相乘:accumulated_transform * current_transform final_transform = self.multiply_matrices(accumulated_transform, current_transform) elif current_transform: final_transform = current_transform else: final_transform = accumulated_transform if 'content' in node and 'uri' in node['content']: uri = node['content']['uri'] if uri.endswith('.b3dm'): full_path = os.path.join(base_path, uri) if os.path.exists(full_path): b3dm_files.append((full_path, final_transform)) elif uri.endswith('.json'): # 递归处理子tileset sub_tileset_path = os.path.join(base_path, uri) if os.path.exists(sub_tileset_path): sub_files = self.parse_tileset_json(sub_tileset_path, final_transform) b3dm_files.extend(sub_files) if 'children' in node: for child in node['children']: process_node(child, base_path, final_transform) base_path = os.path.dirname(tileset_path) if 'root' in tileset_data: process_node(tileset_data['root'], base_path, parent_transform) return b3dm_files except Exception as e: print(f"解析tileset.json时出错: {e}") return [] def parse_b3dm_file(self, file_path): """解析B3DM文件""" try: with open(file_path, 'rb') as f: # 读取B3DM头部 magic = f.read(4) if magic != b'b3dm': print(f"警告: {file_path} 不是有效的B3DM文件") return None version = struct.unpack(' len(glb_data): break chunk_length = struct.unpack(' [输出PLY文件路径]") print("示例: python tileset_to_ply.py tileset.json output.ply") return tileset_path = sys.argv[1] output_path = sys.argv[2] if len(sys.argv) > 2 else "merged_tileset.ply" if not os.path.exists(tileset_path): print(f"错误: 文件不存在 {tileset_path}") return converter = TilesetToPLYConverter() success = converter.convert_tileset_to_ply(tileset_path, output_path) if success: print("\n转换完成!") else: print("\n转换失败!") if __name__ == "__main__": main()