PLY to OBJ Converter
Paste a Stanford PLY mesh or point cloud — ASCII as text, binary as base64 or hex — and get a Wavefront OBJ file back. Vertex colors, normals and texture coordinates come across, and you can scale, reorient axes or triangulate n-gons, all locally in your browser.
About this tool
PLY (the Stanford Polygon Library format) is what most 3D scanners, photogrammetry pipelines and point-cloud tools export. Wavefront OBJ is what most modelling packages, game engines and slicers import. This converter turns one into the other in your browser — the file never leaves your machine, and there is no upload, sign-up or queue.
It reads all three PLY encodings the spec defines: ascii,
binary_little_endian and binary_big_endian. An ASCII PLY is plain text, so
paste it straight in. A binary PLY is not text — paste its bytes as base64 or
hex instead (a data:…;base64,… URL works too) and leave the encoding on
Auto-detect.
Both meshes and point clouds are supported. A PLY with no face element is a
point cloud, and the OBJ comes back as v lines only — which is exactly what
CloudCompare, MeshLab and Blender expect from a point-cloud OBJ.
Worked example
Input — an ASCII PLY triangle with per-vertex colors:
ply
format ascii 1.0
element vertex 3
property float x
property float y
property float z
property uchar red
property uchar green
property uchar blue
element face 1
property list uchar int vertex_indices
end_header
0 0 0 255 0 0
1 0 0 0 255 0
0 1 0 0 0 255
3 0 1 2
Output — Wavefront OBJ, with the colors carried across:
# OBJ generated by gizza ply-to-obj
# source: PLY (ascii), 3 vertices, 1 faces
# vertex colors are the r g b values after each v (0..1)
o mesh
v 0 0 0 1 0 0
v 1 0 0 0 1 0
v 0 1 0 0 0 1
f 1 2 3
PLY indices count from 0 and OBJ indices count from 1, so face 3 0 1 2 becomes
f 1 2 3. The uchar color channels (0–255) are divided by 255 to the 0–1
range OBJ readers expect.
What comes across
| PLY property | Written as | Notes |
|---|---|---|
x y z | v x y z | Always. |
red green blue | v x y z r g b | The vertex-color extension MeshLab, CloudCompare and Blender read. uchar 0–255 is scaled to 0–1; float channels are used as-is. |
nx ny nz | vn + f v//vn | |
s/t, u/v, texture_u/texture_v | vt + f v/vt | All three spellings are recognised. |
vertex_indices / vertex_index | f … | Both spellings are recognised. |
alpha | — | Dropped: OBJ has no per-vertex alpha channel. |
| edges, materials, custom scanner properties | — | Skipped, but still consumed so the rest of the file stays in sync. |
Switch Output to Source-file summary to see what a file actually contains — its encoding, whether it is a mesh or a point cloud, vertex/face/polygon counts, which of colors/normals/UVs were declared and written, every header element, and which vertex properties were ignored — without converting it.
Limits and edge cases
- 200,000 vertices and 200,000 faces maximum. Larger files are rejected with a clear message rather than hanging the page.
- No
.mtlfile is written. PLY stores no material definitions, so there is nothing to write one from. Vertex colors are the colour channel PLY actually carries, and they are preserved. - Faces keep their corner count as OBJ n-gons by default, which is the faithful conversion — OBJ supports polygons natively. Tick Triangulate n-gon faces for slicers and engines that only accept triangles; it fan-triangulates from the first corner, which is correct for convex faces.
- Normals are rotated but never scaled. A uniform scale does not change a unit normal's direction, so the scale factor applies to positions only.
- A face index pointing past the end of the vertex list, a truncated body, a
missing
x/y/zproperty or a missingend_headerline are all reported as errors naming the offending vertex or face, rather than producing a silently broken OBJ.
FAQ
My PLY is binary — why can't I just paste it?
Binary PLY is raw bytes, not text: pasting it through a browser text field would
mangle every byte that is not valid UTF-8. Paste the bytes as base64 or
hex instead and the converter decodes them for you. On macOS or Linux,
base64 -w0 model.ply gives you something you can paste directly; a
data:…;base64,… URL is accepted too. Leave the encoding on Auto-detect — hex
and base64 are both recognised, and the decoded bytes are checked for the ply
magic word before parsing.
Will my vertex colors survive the conversion?
Yes, if the PLY declares red/green/blue properties. They are written using
the v x y z r g b extension, where the three extra numbers on each v line are
the colour in the 0–1 range. MeshLab, CloudCompare and Blender all read this
form. It is an extension rather than base OBJ, so a stricter reader may ignore
the extra values — untick Keep vertex colors to emit plain v x y z lines if
that causes trouble. Per-vertex alpha is always dropped, because OBJ has no
alpha channel.
Can it convert a point cloud, or does it need faces?
Point clouds work. A PLY with no face element — which is what most scanners and
photogrammetry tools export before meshing — converts to an OBJ containing only
v lines (plus v x y z r g b if it carries colours). No faces are invented.
The Source-file summary output will tell you explicitly whether a file was read
as a mesh or a point cloud.
Why does my OBJ have no .mtl material file?
Because PLY has no materials to convert. The format stores per-vertex data —
positions, colours, normals, texture coordinates — but no material definitions,
no texture image references and no shading parameters, so there is nothing a
.mtl file could be generated from. If your model was textured, the texture
image and its material live outside the PLY; re-attach them in your 3D
application after importing the OBJ. Texture coordinates (s/t, u/v or
texture_u/texture_v) do come across as vt lines.
My model is on its side after importing — what do I fix?
That is a coordinate-frame mismatch, not a conversion bug. Graphics tools (Blender's glTF path, Unity, three.js) treat +Y as up, while 3D-printing and CAD tools (slicers, Fusion, Blender's own scene) treat +Z as up. Set Axis conversion to Y-up → Z-up or Z-up → Y-up to rotate the model 90° about the X axis on the way out. Vertex normals rotate with the positions, so lighting stays correct.
What does the scale factor do, and when do I need it?
It multiplies every vertex position uniformly. PLY files carry no units, so a
scanner exporting millimetres and a slicer expecting metres will disagree by
1000× with no way to detect it. Use 0.001 for millimetres → metres, 1000 for
metres → millimetres, 25.4 for inches → millimetres, or 0.0393701 for the
reverse. Leave it at 1 to change nothing. Normals are direction vectors and are
deliberately left unscaled.
Does it handle files with edges or unusual scanner properties?
Yes. Unknown elements (edge, material, anything a particular scanner adds)
and unmapped per-vertex properties such as quality, confidence or
intensity are skipped — but they are still consumed from the file, so the
values that follow them are read at the right offsets. This matters especially
for binary PLY, where silently skipping an unknown property would desynchronise
the byte stream and turn the rest of the file into garbage. Run the Source-file
summary output to see exactly which properties were ignored.
Developer & Automation Access
Run it from the terminal
Same engine as this page, headless — via the gizza CLI:
gizza tool ply-to-obj "ply
format ascii 1.0
element vertex 3
property float x
property float y
property float z
element face 1
property list uchar int vertex_indices
end_header
0 0 0
1 0 0
0 1 0
3 0 1 2"New to the CLI? Get gizza →
Open it by URL
Pre-fill and auto-run this tool with query parameters — the names match the API/CLI:
https://gizza.ai/tools/ply-to-obj/?ply=ply%0Aformat%20ascii%201.0%0Aelement%20vertex%203%0Aproperty%20float%20x%0Aproperty%20float%20y%0Aproperty%20float%20z%0Aelement%20face%201%0Aproperty%20list%20uchar%20int%20vertex_indices%0Aend_header%0A0%200%200%0A1%200%200%0A0%201%200%0A3%200%201%202&input_format=auto&colors=true&normals=true&uvs=true&triangulate=true&scale=1&axis=keep&name=mesh&output=objMachine-readable descriptor: tool.json — title + parameters JSON Schema for agents.
