import numpy as np import trimesh from tqdm import tqdm import os import sys import glob import multiprocessing import random from collections import Counter sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from pathlib import Path from utils.data_utils import undiscretize, discretize, normalize, clean_mesh from utils.mesh_metric import compute_mesh_metrics import itertools from dataclasses import dataclass # 预计算 24 个节点中选 3 个的所有可能组合 (共 2024 种) _TRI_INDICES = np.array(list(itertools.combinations(range(24), 3))) _IDX_I = _TRI_INDICES[:, 0] _IDX_J = _TRI_INDICES[:, 1] _IDX_K = _TRI_INDICES[:, 2] # ========================================== # 新增:动态网格 Tokenizer # ========================================== class DynamicMeshTokenizer: def __init__(self, resolution, max_children): self.resolution = resolution self.max_children = max_children self.m_bits = max_children * max_children # 1. 特殊控制 Token self.pad_id = 0 # 2. 计算各特征在词表中的偏移量 (Offsets) self.coord_offset = 1 self.f_offset = self.coord_offset + self.resolution self.m_intra_offset = self.f_offset + 256 # 1x8 对应 256 种可能 self.m_vocab_size = 2 ** self.m_bits self.m_inter_offset = self.m_intra_offset + self.m_vocab_size # 最终 Transformer 的总词表大小 self.vocab_size = self.m_inter_offset + self.m_vocab_size def _list_to_int(self, bit_list): res = 0 for i, bit in enumerate(bit_list): if bit: res |= (1 << i) return res def _int_to_list(self, val, length): return [(val >> i) & 1 for i in range(length)] def _m_to_int(self, M): flat = [bit for row in M for bit in row] return self._list_to_int(flat) def _int_to_m(self, val): flat = self._int_to_list(val, self.m_bits) return [flat[i*self.max_children : (i+1)*self.max_children] for i in range(self.max_children)] # ------------------------------------ # 细分网格 (Fine Mesh) Tokenizer # ------------------------------------ def encode(self, faces_data, use_hourglass=True): """将细分几何特征列表编码为 1D Token 序列 (18 Tokens / Face)""" seq = [] for face in faces_data: v0, v1, v2, F0, F1, F2, M00, M11, M22, M01, M02, M12 = face seq.extend([v0[0] + self.coord_offset, v0[1] + self.coord_offset, v0[2] + self.coord_offset]) seq.extend([v1[0] + self.coord_offset, v1[1] + self.coord_offset, v1[2] + self.coord_offset]) seq.extend([v2[0] + self.coord_offset, v2[1] + self.coord_offset, v2[2] + self.coord_offset]) seq.extend([self._list_to_int(F0) + self.f_offset, self._list_to_int(F1) + self.f_offset, self._list_to_int(F2) + self.f_offset]) seq.extend([self._m_to_int(M00) + self.m_intra_offset, self._m_to_int(M11) + self.m_intra_offset, self._m_to_int(M22) + self.m_intra_offset]) seq.extend([self._m_to_int(M01) + self.m_inter_offset, self._m_to_int(M02) + self.m_inter_offset, self._m_to_int(M12) + self.m_inter_offset]) return seq def decode(self, seq): """将细分网格的 1D Token 序列解码回几何特征列表""" faces_data = [] clean_seq = [t for t in seq if t not in ([self.pad_id])] for i in range(0, len(clean_seq), 18): chunk = clean_seq[i:i+18] if len(chunk) < 18: break v0 = (chunk[0] - self.coord_offset, chunk[1] - self.coord_offset, chunk[2] - self.coord_offset) v1 = (chunk[3] - self.coord_offset, chunk[4] - self.coord_offset, chunk[5] - self.coord_offset) v2 = (chunk[6] - self.coord_offset, chunk[7] - self.coord_offset, chunk[8] - self.coord_offset) F0 = self._int_to_list(chunk[9] - self.f_offset, 8) F1 = self._int_to_list(chunk[10] - self.f_offset, 8) F2 = self._int_to_list(chunk[11] - self.f_offset, 8) M00 = self._int_to_m(chunk[12] - self.m_intra_offset) M11 = self._int_to_m(chunk[13] - self.m_intra_offset) M22 = self._int_to_m(chunk[14] - self.m_intra_offset) M01 = self._int_to_m(chunk[15] - self.m_inter_offset) M02 = self._int_to_m(chunk[16] - self.m_inter_offset) M12 = self._int_to_m(chunk[17] - self.m_inter_offset) faces_data.append((v0, v1, v2, F0, F1, F2, M00, M11, M22, M01, M02, M12)) return faces_data # ------------------------------------ # [新增] 基础/粗糙网格 (Raw Mesh) Tokenizer # ------------------------------------ def encode_raw(self, raw_faces_data, use_hourglass=True): """将粗糙层级特征(仅含顶点)编码为 1D Token 序列 (9 Tokens / Face)""" seq = [] for face in raw_faces_data: v0, v1, v2 = face seq.extend([v0[0] + self.coord_offset, v0[1] + self.coord_offset, v0[2] + self.coord_offset]) seq.extend([v1[0] + self.coord_offset, v1[1] + self.coord_offset, v1[2] + self.coord_offset]) seq.extend([v2[0] + self.coord_offset, v2[1] + self.coord_offset, v2[2] + self.coord_offset]) return seq def decode_raw(self, seq): """将粗糙层级的 1D Token 序列解码回几何特征列表 [(v0, v1, v2), ...]""" faces_data = [] clean_seq = [t for t in seq if t not in ([self.pad_id])] # 每个面片固定转化为 9 个 Token for i in range(0, len(clean_seq), 9): chunk = clean_seq[i:i+9] if len(chunk) < 9: break v0 = (chunk[0] - self.coord_offset, chunk[1] - self.coord_offset, chunk[2] - self.coord_offset) v1 = (chunk[3] - self.coord_offset, chunk[4] - self.coord_offset, chunk[5] - self.coord_offset) v2 = (chunk[6] - self.coord_offset, chunk[7] - self.coord_offset, chunk[8] - self.coord_offset) faces_data.append((v0, v1, v2)) return faces_data # --- 以下为原有的类声明 (OctreeFaceTokenBuilder, OctreeNode, MatrixOctree 等保持不变) --- class OctreeFaceTokenBuilder: def __init__(self, tree, max_children=4): self.tree = tree self.max_children = max_children def get_level_info(self, depth): nodes_at_depth = {} def collect_nodes(current, target_depth): if current.depth == target_depth: nodes_at_depth[current.coord] = current return for child in current.children: if child: collect_nodes(child, target_depth) collect_nodes(self.tree.root, depth) coords = sorted(nodes_at_depth.keys(), key=lambda x: (x[2], x[1], x[0])) coord_to_idx = {c: i for i, c in enumerate(coords)} adj = {i: set() for i in range(len(coords))} for i, c in enumerate(coords): node = nodes_at_depth[c] all_neighbors = node.intra_neighbors | node.inter_neighbors for neighbor_coord in all_neighbors: if neighbor_coord in coord_to_idx: j = coord_to_idx[neighbor_coord] if i < j: adj[i].add(j) raw_faces_set = set() covered_edges = set() # 1. 提取所有互相连接的 3-clique (正常的三角形面片) for u in range(len(coords)): for v in adj[u]: common_neighbors = adj[u] & adj[v] for w in common_neighbors: # 排序坐标点以确保集合去重时的唯一性 face_coords = tuple(sorted([coords[u], coords[v], coords[w]])) raw_faces_set.add(face_coords) # 记录已经被三角形覆盖的边 covered_edges.add(tuple(sorted([face_coords[0], face_coords[1]]))) covered_edges.add(tuple(sorted([face_coords[1], face_coords[2]]))) covered_edges.add(tuple(sorted([face_coords[0], face_coords[2]]))) # 2. 提取未形成三角形的独立边 (线段结构,例如细长桌腿) for u in range(len(coords)): for v in adj[u]: edge = tuple(sorted([coords[u], coords[v]])) if edge not in covered_edges: # 将 1D 的线段转换为退化面片 (A, B, B) 以适配 Tokenizer 格式 degenerate_face = (edge[0], edge[1], edge[1]) raw_faces_set.add(degenerate_face) # 3. (可选) 极端情况下处理没有任何连接的孤立顶点 (A, A, A) for u in range(len(coords)): has_edges = len(adj[u]) > 0 or any(u in adj[i] for i in range(u)) if not has_edges: isolated_face = (coords[u], coords[u], coords[u]) raw_faces_set.add(isolated_face) raw_faces_at_depth = list(raw_faces_set) # 保持原始排序逻辑以确保输出的 Determinism raw_faces_at_depth.sort(key=lambda f: (f[0][2], f[0][1], f[0][0], f[1][2], f[1][1], f[1][0], f[2][2])) return raw_faces_at_depth, nodes_at_depth def reduce_node_children_to_max(self, nodes_map): def bit_dist(a, b): return bin(a ^ b).count('1') # Pass 1: 基于原始图结构为所有节点一次性计算 merge plan,消除顺序依赖。 merge_plan = {} for coord, n in nodes_map.items(): active_indices = set() has_intra_row = n.intra_matrix.any(axis=1) has_intra_col = n.intra_matrix.any(axis=0) for i in range(8): if has_intra_row[i] or has_intra_col[i]: active_indices.add(i) for mat in n.inter_matrices.values(): has_inter_row = mat.any(axis=1) for i in range(8): if has_inter_row[i]: active_indices.add(i) for n_inter_coord in n.inter_neighbors: if n_inter_coord in nodes_map: neighbor = nodes_map[n_inter_coord] if coord in neighbor.inter_matrices: has_in_col = neighbor.inter_matrices[coord].any(axis=0) for i in range(8): if has_in_col[i]: active_indices.add(i) if len(active_indices) <= self.max_children: merge_plan[coord] = None continue child_degrees = [0] * 8 intra_row_counts = np.count_nonzero(n.intra_matrix, axis=1) intra_col_counts = np.count_nonzero(n.intra_matrix, axis=0) for i in range(8): child_degrees[i] += intra_row_counts[i] + intra_col_counts[i] out_inter_counts = np.zeros(8, dtype=int) for mat in n.inter_matrices.values(): out_inter_counts += np.count_nonzero(mat, axis=1) in_inter_counts = np.zeros(8, dtype=int) for n_inter_coord in n.inter_neighbors: if n_inter_coord in nodes_map: neighbor = nodes_map[n_inter_coord] if coord in neighbor.inter_matrices: in_inter_counts += np.count_nonzero(neighbor.inter_matrices[coord], axis=0) for i in range(8): child_degrees[i] += 2 * (out_inter_counts[i] + in_inter_counts[i]) active_list = sorted(list(active_indices)) scored = sorted( active_list, key=lambda idx: (-child_degrees[idx], idx) ) keep_nodes = sorted(scored[:self.max_children]) mapping = {idx: idx for idx in range(8)} for m_idx in active_list: if m_idx in keep_nodes: continue target_merge = min(keep_nodes, key=lambda x: (bit_dist(x, m_idx), x)) mapping[m_idx] = target_merge merge_plan[coord] = mapping # Pass 2: 根据全局 merge plan 同步重映射所有 intra/inter 矩阵。 for coord, n in nodes_map.items(): mapping = merge_plan.get(coord) if mapping is not None: old_intra = n.intra_matrix new_intra = np.zeros_like(old_intra) nz = np.argwhere(old_intra != 0) for i, j in nz: new_intra[mapping[int(i)], mapping[int(j)]] = 1 np.fill_diagonal(new_intra, 0) n.intra_matrix = new_intra for neigh_coord, mat in list(n.inter_matrices.items()): src_map = merge_plan.get(coord) dst_map = merge_plan.get(neigh_coord) if src_map is None and dst_map is None: continue new_mat = np.zeros_like(mat) nz = np.argwhere(mat != 0) for i, j in nz: ni = src_map[int(i)] if src_map is not None else int(i) nj = dst_map[int(j)] if dst_map is not None else int(j) new_mat[ni, nj] = 1 n.inter_matrices[neigh_coord] = new_mat def encode_level_tokens(self, depth): raw_faces, nodes_map = self.get_level_info(depth) self.reduce_node_children_to_max(nodes_map) node_F = {} node_active = {} empty_mat = np.zeros((8, 8), dtype=np.int8) for coord, n in nodes_map.items(): active_indices = set() has_intra_row = n.intra_matrix.any(axis=1) has_intra_col = n.intra_matrix.any(axis=0) for i in range(8): if has_intra_row[i] or has_intra_col[i]: active_indices.add(i) for mat in n.inter_matrices.values(): has_inter_row = mat.any(axis=1) for i in range(8): if has_inter_row[i]: active_indices.add(i) for n_inter_coord in n.inter_neighbors: if n_inter_coord in nodes_map: neighbor = nodes_map[n_inter_coord] if n.coord in neighbor.inter_matrices: has_in_col = neighbor.inter_matrices[n.coord].any(axis=0) for i in range(8): if has_in_col[i]: active_indices.add(i) F = [0] * 8 for idx in active_indices: F[idx] = 1 node_F[coord] = F node_active[coord] = sorted(list(active_indices)) memo_intra = {} memo_inter = {} def extract_NxN_intra(node_coord): if node_coord in memo_intra: return memo_intra[node_coord] active = node_active[node_coord][:self.max_children] n = nodes_map[node_coord] if len(active) > 0: M = np.zeros((self.max_children, self.max_children), dtype=np.int8) sub = n.intra_matrix[np.ix_(active, active)] M[:len(active), :len(active)] = sub np.fill_diagonal(M[:len(active), :len(active)], 1) res = M.tolist() else: res = np.zeros((self.max_children, self.max_children), dtype=np.int8).tolist() memo_intra[node_coord] = res return res def extract_NxN_inter(coord_A, coord_B): if coord_A == coord_B: return extract_NxN_intra(coord_A) key = (coord_A, coord_B) if key in memo_inter: return memo_inter[key] active_A = node_active[coord_A][:self.max_children] active_B = node_active[coord_B][:self.max_children] nA = nodes_map[coord_A] orig_M = nA.inter_matrices.get(coord_B, empty_mat) if len(active_A) > 0 and len(active_B) > 0: M = np.zeros((self.max_children, self.max_children), dtype=np.int8) M[:len(active_A), :len(active_B)] = orig_M[np.ix_(active_A, active_B)] res = M.tolist() else: res = np.zeros((self.max_children, self.max_children), dtype=np.int8).tolist() memo_inter[key] = res return res raw_tokens = [] output_tokens = [] for face in raw_faces: v0_c, v1_c, v2_c = face raw_tokens.append([v0_c, v1_c, v2_c]) F0, F1, F2 = node_F[v0_c], node_F[v1_c], node_F[v2_c] M00 = extract_NxN_intra(v0_c) M11 = extract_NxN_intra(v1_c) M22 = extract_NxN_intra(v2_c) M01 = extract_NxN_inter(v0_c, v1_c) M02 = extract_NxN_inter(v0_c, v2_c) M12 = extract_NxN_inter(v1_c, v2_c) f_encoded = ( v0_c, v1_c, v2_c, F0, F1, F2, M00, M11, M22, M01, M02, M12 ) output_tokens.append(f_encoded) return raw_tokens, output_tokens class OctreeNode: def __init__(self, x, y, z, depth): self.coord = (x, y, z) self.depth = depth self.children = [None] * 8 self.intra_neighbors = set() self.inter_neighbors = set() self.intra_matrix = np.zeros((8, 8), dtype=np.int8) self.inter_matrices = {} class MatrixOctree: def __init__(self, max_depth=7): self.max_depth = max_depth self.root = None def _get_parent_coord(self, coord): return (coord[0] >> 1, coord[1] >> 1, coord[2] >> 1) def _get_local_index(self, coord): return ((coord[2] & 1) << 2) | ((coord[1] & 1) << 1) | (coord[0] & 1) def build(self, vertices, faces): resolution = 2 ** self.max_depth quantized = discretize(vertices, num_discrete=resolution) leaf_nodes_map = {} for i in range(len(vertices)): coord = tuple(quantized[i]) if coord not in leaf_nodes_map: leaf_nodes_map[coord] = OctreeNode(coord[0], coord[1], coord[2], self.max_depth) for face in faces: coords = [tuple(quantized[i]) for i in face] for i in range(3): u, v = coords[i], coords[(i+1)%3] if u == v: continue parent_u = self._get_parent_coord(u) parent_v = self._get_parent_coord(v) node_u = leaf_nodes_map[u] node_v = leaf_nodes_map[v] if parent_u == parent_v: node_u.intra_neighbors.add(v) node_v.intra_neighbors.add(u) else: node_u.inter_neighbors.add(v) node_v.inter_neighbors.add(u) current_layer_nodes = list(leaf_nodes_map.values()) for d in range(self.max_depth, 0, -1): parent_layer_map = {} for child in current_layer_nodes: parent_coord = self._get_parent_coord(child.coord) if parent_coord not in parent_layer_map: parent_layer_map[parent_coord] = OctreeNode(*parent_coord, d - 1) parent_node = parent_layer_map[parent_coord] idx = self._get_local_index(child.coord) parent_node.children[idx] = child for child in current_layer_nodes: parent_coord = self._get_parent_coord(child.coord) parent_node = parent_layer_map[parent_coord] child_idx = self._get_local_index(child.coord) for sibling_coord in child.intra_neighbors: sibling_idx = self._get_local_index(sibling_coord) parent_node.intra_matrix[child_idx, sibling_idx] = 1 for neighbor_coord in child.inter_neighbors: neighbor_parent_coord = self._get_parent_coord(neighbor_coord) if neighbor_parent_coord not in parent_node.inter_matrices: parent_node.inter_matrices[neighbor_parent_coord] = np.zeros((8, 8), dtype=np.int8) neighbor_child_idx = self._get_local_index(neighbor_coord) parent_node.inter_matrices[neighbor_parent_coord][child_idx, neighbor_child_idx] = 1 grandparent_p = self._get_parent_coord(parent_coord) grandparent_n = self._get_parent_coord(neighbor_parent_coord) if grandparent_p == grandparent_n: parent_node.intra_neighbors.add(neighbor_parent_coord) else: parent_node.inter_neighbors.add(neighbor_parent_coord) current_layer_nodes = list(parent_layer_map.values()) if current_layer_nodes: self.root = current_layer_nodes[0] def reconstruct_next_level_optimized(output_tokens, resolution, max_children=4): all_face_coords = [] indices = np.arange(8) offsets = np.stack([indices & 1, (indices >> 1) & 1, (indices >> 2) & 1], axis=1).astype(np.int32) def expand_intra(F, M_NxN): M8x8 = np.zeros((8, 8), dtype=bool) active = [i for i, val in enumerate(F) if val] for i_n, i_o in enumerate(active): for j_n, j_o in enumerate(active): if i_n < max_children and j_n < max_children and M_NxN[i_n][j_n]: M8x8[i_o][j_o] = True return M8x8 def expand_inter(FA, FB, M_NxN): M8x8 = np.zeros((8, 8), dtype=bool) activeA = [i for i, val in enumerate(FA) if val] activeB = [i for i, val in enumerate(FB) if val] for i_n, i_o in enumerate(activeA): for j_n, j_o in enumerate(activeB): if i_n < max_children and j_n < max_children and M_NxN[i_n][j_n]: M8x8[i_o][j_o] = True return M8x8 for f_token in output_tokens: v0_c, v1_c, v2_c, F0, F1, F2, M00, M11, M22, M01, M02, M12 = f_token v_parents = [np.array(v[:3], dtype=np.int32) for v in [v0_c, v1_c, v2_c]] in0 = expand_intra(F0, M00) in1 = expand_intra(F1, M11) in2 = expand_intra(F2, M22) m01 = expand_inter(F0, F1, M01) m02 = expand_inter(F0, F2, M02) m12 = expand_inter(F1, F2, M12) # in_mats = [in0, in1, in2] # inter_edges = { # (0, 1): m01, # (1, 2): m12, # (0, 2): m02 # } # def are_connected_opt(p_a, c_a, p_b, c_b): # # p_a 和 p_b 是 0, 1, 2 这三个顶点索引。 # # 只要索引相同,说明是查询同一个顶点内部;索引不同,无脑查 inter_edges # if p_a == p_b: # return in_mats[p_a][c_a, c_b] # if p_a < p_b: # return inter_edges[(p_a, p_b)][c_a, c_b] # else: # return inter_edges[(p_b, p_a)][c_b, c_a] # from itertools import combinations_with_replacement # for pi, pj, pk in combinations_with_replacement(range(3), 3): # for i in range(8): # start_j = i + 1 if pi == pj else 0 # for j in range(start_j, 8): # if not are_connected_opt(pi, i, pj, j): continue # start_k = j + 1 if pj == pk else 0 # for k in range(start_k, 8): # if are_connected_opt(pj, j, pk, k) and are_connected_opt(pk, k, pi, i): # c0 = v_parents[pi] * 2 + offsets[i] # c1 = v_parents[pj] * 2 + offsets[j] # c2 = v_parents[pk] * 2 + offsets[k] # all_face_coords.append(np.stack([c0, c1, c2])) # 1. 构建 24x24 全局邻接矩阵 A A = np.zeros((24, 24), dtype=bool) # 填充内部矩阵 (Intra),使用 | .T 确保无向图的对称性 A[0:8, 0:8] = in0 | in0.T A[8:16, 8:16] = in1 | in1.T A[16:24, 16:24] = in2 | in2.T # 填充外部跨体素矩阵 (Inter) A[0:8, 8:16] = m01 A[8:16, 0:8] = m01.T A[0:8, 16:24] = m02 A[16:24, 0:8] = m02.T A[8:16, 16:24] = m12 A[16:24, 8:16] = m12.T # 2. 向量化寻找所有的 3-clique (三角形) # 只需要一行代码,判断 i-j, j-k, i-k 是否同时连通 valid_mask = A[_IDX_I, _IDX_J] & A[_IDX_J, _IDX_K] & A[_IDX_I, _IDX_K] valid_triangles = _TRI_INDICES[valid_mask] # 过滤出所有形成三角形的节点组合 # 3. 批量计算物理坐标 if len(valid_triangles) > 0: # 将 0~23 的索引还原为父节点索引 (p_idx: 0~2) 和子节点索引 (c_idx: 0~7) p_idx = valid_triangles // 8 c_idx = valid_triangles % 8 v_parents_arr = np.stack(v_parents) # shape: (3, 3) # 批量算出所有符合条件的三角面的世界坐标,shape: (N, 3, 3) coords = v_parents_arr[p_idx] * 2 + offsets[c_idx] all_face_coords.append(coords) if not all_face_coords: return np.array([]), np.array([]) flat_v = np.concatenate(all_face_coords, axis=0).reshape(-1, 3) V, inv = np.unique(flat_v, axis=0, return_inverse=True) F_raw = inv.reshape(-1, 3) # mask_not_degenerate = (F_raw[:, 0] != F_raw[:, 1]) & \ # (F_raw[:, 1] != F_raw[:, 2]) & \ # (F_raw[:, 2] != F_raw[:, 0]) # F_valid = F_raw[mask_not_degenerate] F_valid = F_raw F_sorted = np.sort(F_valid, axis=1) _, unique_face_indices = np.unique(F_sorted, axis=0, return_index=True) F = F_valid[unique_face_indices] V = undiscretize(V, num_discrete=resolution) return V, F def reconstruct_base_mesh(output_tokens, resolution): all_face_coords = [] for f_token in output_tokens: v0_c, v1_c, v2_c = f_token c0 = np.array(v0_c[:3], dtype=np.int32) c1 = np.array(v1_c[:3], dtype=np.int32) c2 = np.array(v2_c[:3], dtype=np.int32) all_face_coords.append(np.stack([c0, c1, c2])) if not all_face_coords: return np.array([]), np.array([]) flat_v = np.concatenate(all_face_coords, axis=0).reshape(-1, 3) V, inv = np.unique(flat_v, axis=0, return_inverse=True) F_raw = inv.reshape(-1, 3) # mask_not_degenerate = (F_raw[:, 0] != F_raw[:, 1]) & \ # (F_raw[:, 1] != F_raw[:, 2]) & \ # (F_raw[:, 2] != F_raw[:, 0]) # F_valid = F_raw[mask_not_degenerate] F_valid = F_raw F_sorted = np.sort(F_valid, axis=1) _, unique_face_indices = np.unique(F_sorted, axis=0, return_index=True) F = F_valid[unique_face_indices] V = undiscretize(V, num_discrete=resolution) return V, F class MeshCodec: def __init__(self, args): self.max_depth = args.max_depth self.max_resolution = args.max_resolution self.use_hourglass = args.use_hourglass self.max_children = args.max_children self.tokenizer = DynamicMeshTokenizer(resolution=self.max_resolution, max_children=self.max_children) def encode_mesh(self, mesh: trimesh.Trimesh, target_depth: int) -> tuple: mesh_copy = mesh.copy() if isinstance(mesh_copy, trimesh.Scene): mesh_copy = mesh_copy.dump(concatenate=True) v_restored = discretize(mesh_copy.vertices, num_discrete=2**(target_depth+1)) v_restored = undiscretize(v_restored, num_discrete=2**(target_depth+1)) # 4. 生成新的量化网格 quantized_mesh = trimesh.Trimesh(vertices=v_restored, faces=mesh.faces, process=False) # tree = MatrixOctree(max_depth=self.max_depth) tree = MatrixOctree(max_depth=target_depth+1) tree.build(mesh_copy.vertices, mesh_copy.faces) builder = OctreeFaceTokenBuilder(tree, max_children=self.max_children) raw_t, out_t = builder.encode_level_tokens(depth=target_depth) token_sequence = self.tokenizer.encode(out_t, self.use_hourglass) raw_token_sequence = self.tokenizer.encode_raw(raw_t, self.use_hourglass) return raw_token_sequence, token_sequence, quantized_mesh def decode_tokens(self, token_sequence: list, target_depth: int) -> trimesh.Trimesh: if not token_sequence: return None resolution = 2 ** (target_depth + 1) out_t = self.tokenizer.decode(token_sequence) v_next, f_next = reconstruct_next_level_optimized(out_t, resolution, max_children=self.max_children) if len(v_next) == 0 or len(f_next) == 0: return None detokenized_mesh = trimesh.Trimesh(vertices=v_next, faces=f_next, process=False) # trimesh.repair.fix_normals(detokenized_mesh) # clean_mesh(detokenized_mesh) return detokenized_mesh def decode_raw_tokens(self, raw_token_sequence: list, target_depth:int) -> trimesh.Trimesh: if not raw_token_sequence: return None resolution = 2 ** (target_depth) # [修改] 先使用 tokenizer 从 1D 整数序列解码回 raw_t 结构 raw_t = self.tokenizer.decode_raw(raw_token_sequence) v,f = reconstruct_base_mesh(raw_t, resolution) if len(v) == 0 or len(f) == 0: return None detokenized_mesh = trimesh.Trimesh(vertices=v, faces=f, process=False) # trimesh.repair.fix_normals(detokenized_mesh) # clean_mesh(detokenized_mesh) return detokenized_mesh def get_model_config(self) -> dict: return { "total_vocab_size": self.tokenizer.vocab_size, "coord_vocab_size": self.tokenizer.resolution, "f_vocab_size": 256, "m_vocab_size": self.tokenizer.m_vocab_size, "coord_offset": self.tokenizer.coord_offset, "f_offset": self.tokenizer.f_offset, "m_intra_offset": self.tokenizer.m_intra_offset, "m_inter_offset": self.tokenizer.m_inter_offset, "pad_token_id": self.tokenizer.pad_id, "max_children": self.max_children, "input_num_categories": self.tokenizer.resolution+1, "output_num_categories": 1 + 2*2**self.tokenizer.m_bits + 256, } # ========================================== # 测试重构与无损化流程 # ========================================== if __name__ == "__main__": # 1. 定义输入和输出文件夹 INPUT_DIRECTORY = "/root/junkai_lin/mul_res_diffusion/demo/complex_test_data/files" # 【修改这里】替换为你的模型文件夹路径 OUTPUT_DIRECTORY = "./output/complex_child8" os.makedirs(OUTPUT_DIRECTORY, exist_ok=True) import time @dataclass class MeshCodecArgs: max_depth: int = 10 max_resolution: int = 1024 use_hourglass: bool = True max_children: int = 8 # 2. 初始化 Codec (放在循环外,避免重复加载) args = MeshCodecArgs() codec = MeshCodec(args=args) print(f"[*] Initialized Codec. Total Vocab Size: {codec.tokenizer.vocab_size}") # 3. 获取所有 .obj 和 .glb 文件 input_dir_path = Path(INPUT_DIRECTORY) # rglob 会递归查找子文件夹,如果只需要当前层级,可以把 rglob 改成 glob mesh_files = list(input_dir_path.rglob("*.obj")) + list(input_dir_path.rglob("*.glb")) if not mesh_files: print(f"[!] No .obj or .glb files found in {INPUT_DIRECTORY}") # 4. 遍历处理每个文件 for idx, file_path in enumerate(mesh_files, 1): obj_name = file_path.stem # 获取不带后缀的文件名 print(f"\n{'='*50}") print(f"[{idx}/{len(mesh_files)}] Processing: {file_path.name}") print(f"{'='*50}") file_start_time = time.perf_counter() # try: # 加载并归一化 Mesh raw_mesh = trimesh.load(str(file_path), force="mesh") normalize(raw_mesh) # clean_mesh(raw_mesh) if raw_mesh: out_path = os.path.join(OUTPUT_DIRECTORY, f"{obj_name}_gt.obj") raw_mesh.export(out_path) print(f"[*] Successfully saved GT to: {out_path}") # 开始不同深度 (Depth) 的推理 for idx, file_path in enumerate(mesh_files, 1): obj_name = file_path.stem print(f"\n{'='*50}") print(f"[{idx}/{len(mesh_files)}] Processing: {file_path.name}") print(f"{'='*50}") file_start_time = time.perf_counter() # 记录单个文件处理开始 raw_mesh = trimesh.load(str(file_path), force="mesh") normalize(raw_mesh) if raw_mesh: out_path = os.path.join(OUTPUT_DIRECTORY, f"{obj_name}_gt.obj") raw_mesh.export(out_path) # 开始不同深度 (Depth) 的推理 for depth in [0,1,2,3,4,5,6]: print(f"\n--- Depth {depth} | N={args.max_children} ---") # --- 计时:Encoding --- t0 = time.perf_counter() raw_tokens, tokens, quantized_mesh = codec.encode_mesh(raw_mesh, target_depth=depth) encode_time = time.perf_counter() - t0 print(f"[Timer] Encoding: {encode_time:.4f}s") # --- 计时:Decoding (Fine) --- if len(tokens) > 0: t1 = time.perf_counter() recon_mesh = codec.decode_tokens(tokens, target_depth=depth) decode_fine_time = time.perf_counter() - t1 print(f"[Timer] Decoding (Fine): {decode_fine_time:.4f}s") # 导出保存 out_path = os.path.join(OUTPUT_DIRECTORY, f"{obj_name}_recon_d{depth}_res{2**(depth+1)}_N{args.max_children}.obj") recon_mesh.export(out_path) # --- 计时:Decoding (Coarse/Raw) --- if len(raw_tokens) > 0: t2 = time.perf_counter() raw_recon_mesh = codec.decode_raw_tokens(raw_tokens, target_depth=depth) decode_coarse_time = time.perf_counter() - t2 print(f"[Timer] Decoding (Coarse): {decode_coarse_time:.4f}s") raw_out_path = os.path.join(OUTPUT_DIRECTORY, f"{obj_name}_raw_recon_d{depth}_res{2**depth}.obj") raw_recon_mesh.export(raw_out_path) if quantized_mesh: quant_out_path = os.path.join(OUTPUT_DIRECTORY, f"{obj_name}_dis_d{depth+1}_res{2**(depth+1)}.obj") quantized_mesh.export(quant_out_path) file_total_time = time.perf_counter() - file_start_time print(f"\n[*] Finished {file_path.name} in {file_total_time:.2f}s")