NumPy-based PLY format input and output for Python with comprehensive support for reading and writing PLY polygon mesh files
npx @tessl/cli install tessl/pypi-plyfile@1.1.0NumPy-based PLY format input and output for Python. This library provides comprehensive support for reading and writing PLY (Polygon File Format) files, which are commonly used for storing 3D polygon mesh data in computer graphics and scientific computing applications.
pip install plyfileimport plyfile
from plyfile import PlyData, PlyElement, PlyProperty, PlyListPropertyFor exception handling:
from plyfile import PlyParseError, PlyElementParseError, PlyHeaderParseErrorfrom plyfile import PlyData
# Read PLY file from disk
ply_data = PlyData.read('mesh.ply')
# Access elements by name
vertex_element = ply_data['vertex']
face_element = ply_data['face']
# Access element data as NumPy arrays
vertices = vertex_element.data
faces = face_element.data
# Print file information
print(f"Number of vertices: {len(vertex_element)}")
print(f"Number of faces: {len(face_element)}")
print(f"Vertex properties: {[prop.name for prop in vertex_element.properties]}")import numpy as np
from plyfile import PlyData, PlyElement
# Create vertex data
vertex_data = np.array([
(0.0, 0.0, 0.0, 255, 0, 0),
(1.0, 0.0, 0.0, 0, 255, 0),
(0.0, 1.0, 0.0, 0, 0, 255)
], dtype=[('x', 'f4'), ('y', 'f4'), ('z', 'f4'),
('red', 'u1'), ('green', 'u1'), ('blue', 'u1')])
# Create face data with connectivity lists
face_data = np.array([
([0, 1, 2],),
], dtype=[('vertex_indices', 'O')])
# Create PLY elements
vertex_element = PlyElement.describe(vertex_data, 'vertex')
face_element = PlyElement.describe(face_data, 'face')
# Create PLY data and write to file
ply_data = PlyData([vertex_element, face_element])
ply_data.write('output.ply')The main container for PLY file data, including elements, comments, and format information.
class PlyData:
def __init__(self, elements=[], text=False, byte_order='=', comments=[], obj_info=[]):
"""
Initialize PLY data container.
Parameters:
- elements (list): List of PlyElement instances
- text (bool): Whether file format is text/ASCII (True) or binary (False)
- byte_order (str): Byte order for binary files ('<', '>', '=')
- comments (list): Comment lines for header
- obj_info (list): Object info lines for header
"""
@staticmethod
def read(stream, mmap='c', known_list_len={}):
"""
Read PLY data from file or stream.
Parameters:
- stream (str or file): Filename or open file object
- mmap (str or bool): Memory mapping mode ('c', 'r', 'r+', or False)
- known_list_len (dict): Fixed lengths for list properties to enable memory mapping
Returns:
- PlyData: Parsed PLY data
Raises:
- PlyParseError: If file cannot be parsed
- ValueError: If stream mode incompatible with PLY format
"""
def write(self, stream):
"""
Write PLY data to file or stream.
Parameters:
- stream (str or file): Filename or open file object
Raises:
- ValueError: If stream mode incompatible with PLY format
"""
def __getitem__(self, name):
"""
Get element by name.
Parameters:
- name (str): Element name
Returns:
- PlyElement: The requested element
Raises:
- KeyError: If element not found
"""
def __contains__(self, name):
"""
Check if element exists.
Parameters:
- name (str): Element name
Returns:
- bool: True if element exists
"""
def __len__(self):
"""
Get number of elements.
Returns:
- int: Number of elements
"""
def __iter__(self):
"""
Iterate over elements.
Yields:
- PlyElement: Each element in order
"""
# Properties
elements: list # List of PlyElement instances
comments: list # Header comment lines
obj_info: list # Header object info lines
text: bool # Text format flag
byte_order: str # Byte order specification
header: str # Complete PLY header stringContainer for element data with properties and metadata.
class PlyElement:
def __init__(self, name, properties, count, comments=[]):
"""
Initialize PLY element (internal use - prefer PlyElement.describe).
Parameters:
- name (str): Element name
- properties (list): List of PlyProperty instances
- count (int): Number of data rows
- comments (list): Element comments
"""
@staticmethod
def describe(data, name, len_types={}, val_types={}, comments=[]):
"""
Create PlyElement from NumPy array.
Parameters:
- data (numpy.ndarray): Structured NumPy array with element data
- name (str): Element name
- len_types (dict): Type specifications for list length fields
- val_types (dict): Type specifications for list value fields
- comments (list): Element comments
Returns:
- PlyElement: New element instance
Raises:
- TypeError: If data is not NumPy array
- ValueError: If array format unsupported
"""
def ply_property(self, name):
"""
Get property by name.
Parameters:
- name (str): Property name
Returns:
- PlyProperty: The requested property
Raises:
- KeyError: If property not found
"""
def dtype(self, byte_order='='):
"""
Get NumPy dtype for element data.
Parameters:
- byte_order (str): Byte order specification
Returns:
- numpy.dtype: Data type description
"""
def __getitem__(self, key):
"""
Access element data array.
Parameters:
- key: Array index or slice
Returns:
- Data from underlying NumPy array
"""
def __setitem__(self, key, value):
"""
Modify element data array.
Parameters:
- key: Array index or slice
- value: New value
"""
def __len__(self):
"""
Get number of data rows.
Returns:
- int: Number of rows
"""
def __contains__(self, name):
"""
Check if property exists.
Parameters:
- name (str): Property name
Returns:
- bool: True if property exists
"""
# Properties
name: str # Element name
count: int # Number of data rows
data: numpy.ndarray # Structured array with element data
properties: list # List of PlyProperty instances
comments: list # Element comments
header: str # PLY header block for this elementProperty metadata describing scalar and list data fields.
class PlyProperty:
def __init__(self, name, val_dtype):
"""
Initialize scalar property.
Parameters:
- name (str): Property name
- val_dtype (str): PLY or NumPy data type specification
"""
def dtype(self, byte_order='='):
"""
Get NumPy dtype string.
Parameters:
- byte_order (str): Byte order specification
Returns:
- str: NumPy dtype string
"""
# Properties
name: str # Property name
val_dtype: str # NumPy data type code
class PlyListProperty(PlyProperty):
def __init__(self, name, len_dtype, val_dtype):
"""
Initialize list property.
Parameters:
- name (str): Property name
- len_dtype (str): Data type for list length field
- val_dtype (str): Data type for list elements
"""
def list_dtype(self, byte_order='='):
"""
Get NumPy dtypes for list components.
Parameters:
- byte_order (str): Byte order specification
Returns:
- tuple: (length_dtype, value_dtype)
"""
def dtype(self, byte_order='='):
"""
Get NumPy dtype for property field (always object type).
Parameters:
- byte_order (str): Byte order specification
Returns:
- str: Always '|O' for object arrays
"""
# Properties (inherited from PlyProperty)
name: str # Property name
val_dtype: str # NumPy data type for list elements
len_dtype: str # NumPy data type for list lengthError handling for PLY file parsing and validation.
class PlyParseError(Exception):
"""Base class for PLY parsing errors."""
class PlyElementParseError(PlyParseError):
def __init__(self, message, element=None, row=None, prop=None):
"""
Element parsing error.
Parameters:
- message (str): Error description
- element (PlyElement): Element being parsed
- row (int): Row number where error occurred
- prop (PlyProperty): Property being parsed
"""
# Properties
message: str # Error message
element: PlyElement # Element context
row: int # Row number
prop: PlyProperty # Property context
class PlyHeaderParseError(PlyParseError):
def __init__(self, message, line=None):
"""
Header parsing error.
Parameters:
- message (str): Error description
- line (str): Header line where error occurred
"""
# Properties
message: str # Error message
line: str # Header line contextfrom plyfile import PlyData
# Enable memory mapping for better performance with large files
# Requires known list lengths for elements with list properties
known_lengths = {
'vertex': {}, # No list properties
'face': {'vertex_indices': 3} # Triangular faces
}
ply_data = PlyData.read('large_mesh.ply', mmap='c', known_list_len=known_lengths)import numpy as np
from plyfile import PlyElement
# Create data with custom list length and value types
vertex_data = np.array([
(0.0, 0.0, 0.0, [255, 128, 64]),
(1.0, 0.0, 0.0, [0, 255, 128]),
], dtype=[('x', 'f4'), ('y', 'f4'), ('z', 'f4'), ('rgb', 'O')])
# Specify types for list properties
element = PlyElement.describe(
vertex_data,
'vertex',
len_types={'rgb': 'u1'}, # 8-bit unsigned for list length
val_types={'rgb': 'u1'} # 8-bit unsigned for RGB values
)from plyfile import PlyData, PlyElement
# Create data
element = PlyElement.describe(data, 'vertex')
# Write as ASCII text format
ply_ascii = PlyData([element], text=True)
ply_ascii.write('mesh_ascii.ply')
# Write as binary format with specific byte order
ply_binary = PlyData([element], text=False, byte_order='<') # Little-endian
ply_binary.write('mesh_binary.ply')from plyfile import PlyData, PlyElement
# Add comments and object info to PLY file
element = PlyElement.describe(data, 'vertex', comments=['Vertex data'])
ply_data = PlyData(
[element],
comments=['Generated by my application', 'Processed on 2024-01-01'],
obj_info=['Author: John Doe', 'Version: 1.0']
)
ply_data.write('annotated_mesh.ply')