Stanford PLY#
.ply read + write lazy: binary only
Summary of the specification#
PLY - the Stanford Triangle Format - stores a mesh as a list of named elements, typically vertex and face, each declared in an ASCII header along with its property names and scalar types. The body that follows is either ASCII text or a packed binary block in the byte order the header names. Because the header is self-describing, PLY can carry arbitrary per-vertex and per-face attributes: colours, normals, confidence, intensity.
Specification at a glance#
magic / header |
ply, then format ascii | binary_little_endian | binary_big_endian 1.0 |
structure |
element <name> <count> declarations, each followed by its property lines |
scalar types |
char uchar short ushort int uint float double, plus list <count-type> <item-type> |
faces |
property list uchar int vertex_indices - 0-based, arbitrary polygon size |
comments |
comment lines anywhere in the header; obj_info for producer metadata |
published by |
Greg Turk, Stanford University |
Reading#
import polyxios as px
mesh = px.read("model.ply")
mesh.vertices # (n, 3)
mesh.element_types # element groups found in the file
Binary bodies can be memory-mapped instead of loaded:
mesh = px.read("big.ply", lazy=True)
Writing#
px.write(mesh, "out.ply")
Format-specific options:
Option |
Default |
Effect |
|---|---|---|
|
|
Write a packed binary body instead of ASCII. |
|
|
Byte order of the binary body; “big” emits binary_big_endian. |
Quirks worth knowing#
Vertex properties beyond x/y/z - colour, normals, confidence, intensity - are preserved as named vertex attributes rather than dropped.
Lazy loading applies to binary bodies only; an ASCII file must be parsed in full before any value is available.
Index widths are checked against the declared vertex count, so a mesh too large for the header’s list type raises instead of truncating.
Line elements travel as
element edgewithvertex1/vertex2, the spelling the spec gives them, rather than as a two-vertex face list a reader would take for a degenerate polygon. On read,vertex_index1/vertex_index2and a bare pair of integer properties are accepted too, and the edges land after the faces so a per-face attribute keeps lining up with its faces, whatever order the header declares the two elements in. An element block is read in the order the header names it, since that is the order it sits in the file; one this codec has no place for costs its own records and nothing else.An
element edgerecord carries the same element properties a face does, so a value the mesh held on a line survives the trip both ways. A property only one of the two elements declares is NaN over the other, the format spelling no missing value.An element index no vertex answers to is refused rather than read into a mesh nothing can draw.
PLY spells no 64-bit integer, so a column of one is written at the narrowest type that holds the values it actually carries -
intwhen they fit a signed 32-bit field,doublewhen they do not. The header and the record are taken from the same decision, so a field’s declared width is always the width written.A face’s vertex count is declared
uchar, as almost every PLY file does, and widens toushortoruintfor a mesh carrying a polygon of more than 255 vertices - a count the narrower type cannot spell.A face record is a flat ring of vertices and PLY spells no other shape, so an element that is not one - a
tetra, aquadratic_triangle- keeps its vertices and loses the type it was: a reader names a record by how many vertices it holds, so it comes back a triangle at three, a quad at four and a polygon otherwise. The elements are still written, and the types they lose are named in a warning rather than dropped quietly.An integer attribute is written in full in the ASCII flavour rather than through a float format, which would turn a large one into
1.23456789e+13: not a token a reader expecting the declared integer property accepts.
See also
Supported formats - the full format table.