from typing import Tuple, Dict import numpy as np from trimesh import grouping, util, remesh import struct import re from plyfile import PlyData, PlyElement def read_ply(filename): """ Read a PLY file and return vertices, triangle faces, and quad faces. Args: filename (str): The file path to read from. Returns: vertices (np.ndarray): Array of shape [N, 3] containing vertex positions. tris (np.ndarray): Array of shape [M, 3] containing triangle face indices (empty if none). quads (np.ndarray): Array of shape [K, 4] containing quad face indices (empty if none). """ with open(filename, 'rb') as f: # Read the header until 'end_header' is encountered header_bytes = b"" while True: line = f.readline() if not line: raise ValueError("PLY header not found") header_bytes += line if b"end_header" in line: break header = header_bytes.decode('utf-8') # Determine if the file is in ASCII or binary format is_ascii = "ascii" in header # Extract the number of vertices and faces from the header using regex vertex_match = re.search(r'element vertex (\d+)', header) if vertex_match: num_vertices = int(vertex_match.group(1)) else: raise ValueError("Vertex count not found in header") face_match = re.search(r'element face (\d+)', header) if face_match: num_faces = int(face_match.group(1)) else: raise ValueError("Face count not found in header") vertices = [] tris = [] quads = [] if is_ascii: # For ASCII format, read each line of vertex data (each line contains 3 floats) for _ in range(num_vertices): line = f.readline().decode('utf-8').strip() if not line: continue parts = line.split() vertices.append([float(parts[0]), float(parts[1]), float(parts[2])]) # Read face data, where the first number indicates the number of vertices for the face for _ in range(num_faces): line = f.readline().decode('utf-8').strip() if not line: continue parts = line.split() count = int(parts[0]) indices = list(map(int, parts[1:])) if count == 3: tris.append(indices) elif count == 4: quads.append(indices) else: # Skip faces with other numbers of vertices (can be extended as needed) pass else: # For binary format: read directly from the binary stream # Each vertex consists of 3 floats (12 bytes per vertex) for _ in range(num_vertices): data = f.read(12) if len(data) < 12: raise ValueError("Insufficient vertex data") v = struct.unpack(' 0 else np.empty((0, 3), dtype=np.int32) quads = np.array(quads, dtype=np.int32) if len(quads) > 0 else np.empty((0, 4), dtype=np.int32) return vertices, tris, quads def write_ply( filename: str, vertices: np.ndarray, tris: np.ndarray, quads: np.ndarray, vertex_colors: np.ndarray = None, ascii: bool = False ): """ Write a mesh to a PLY file, with the option to save in ASCII or binary format, and optional per-vertex colors. Args: filename (str): The filename to write to. vertices (np.ndarray): [N, 3] The vertex positions. tris (np.ndarray): [M, 3] The triangle indices. quads (np.ndarray): [K, 4] The quad indices. vertex_colors (np.ndarray, optional): [N, 3] or [N, 4] UInt8 colors for each vertex (RGB or RGBA). ascii (bool): If True, write in ASCII format; otherwise binary little-endian. """ import struct num_vertices = len(vertices) num_faces = len(tris) + len(quads) # Build header header_lines = [ "ply", f"format {'ascii 1.0' if ascii else 'binary_little_endian 1.0'}", f"element vertex {num_vertices}", "property float x", "property float y", "property float z", ] # Add vertex color properties if provided has_color = vertex_colors is not None if has_color: # Expect uint8 values 0-255 header_lines += [ "property uchar red", "property uchar green", "property uchar blue", ] # Include alpha if RGBA if vertex_colors.shape[1] == 4: header_lines.append("property uchar alpha") header_lines += [ f"element face {num_faces}", "property list uchar int vertex_index", "end_header", "" ] header = "\n".join(header_lines) mode = 'w' if ascii else 'wb' with open(filename, mode) as f: # Write header if ascii: f.write(header) else: f.write(header.encode('utf-8')) # Write vertex data for i, v in enumerate(vertices): if ascii: line = f"{v[0]} {v[1]} {v[2]}" if has_color: col = vertex_colors[i] line += ' ' + ' '.join(str(int(c)) for c in col) f.write(line + '\n') else: # pack xyz as floats f.write(struct.pack('