# PLY to OBJ Converter

Convert a Stanford PLY mesh or point cloud to Wavefront OBJ. Reads ascii and binary PLY, keeps vertex colors, normals and UVs, scales and reorients axes.

## Run it

- **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"`
- **Web:** https://gizza.ai/tools/ply-to-obj/
- **Agents:** machine-readable descriptor (parameters JSON Schema) at https://gizza.ai/tools/ply-to-obj/tool.json

## Inputs

- `ply` — PLY file (ASCII text, or binary bytes as base64/hex) _(field)_
- `input_format` — Input encoding _(field)_
- `colors` — Keep vertex colors (v x y z r g b) _(field)_
- `normals` — Keep vertex normals (vn) _(field)_
- `uvs` — Keep texture coordinates (vt) _(field)_
- `triangulate` — Triangulate n-gon faces _(field)_
- `scale` — Scale factor _(field)_
- `axis` — Axis conversion _(field)_
- `name` — Object name _(field)_
- `output` — Output _(field)_

## Output

- Wavefront OBJ (text)

## Query parameters

Open the tool pre-filled and auto-run via URL:

- `ply` — PLY file (ASCII text, or binary bytes as base64/hex)
- `input_format` — Input encoding
- `colors` — Keep vertex colors (v x y z r g b)
- `normals` — Keep vertex normals (vn)
- `uvs` — Keep texture coordinates (vt)
- `triangulate` — Triangulate n-gon faces
- `scale` — Scale factor
- `axis` — Axis conversion
- `name` — Object name
- `output` — Output

Example: `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=obj`

---

## 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 `.mtl` file 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`/`z` property or a missing `end_header` line are all reported as
  errors naming the offending vertex or face, rather than producing a silently
  broken OBJ.

## FAQ

<details>
<summary>My PLY is binary — why can't I just paste it?</summary>

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.

</details>

<details>
<summary>Will my vertex colors survive the conversion?</summary>

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.

</details>

<details>
<summary>Can it convert a point cloud, or does it need faces?</summary>

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.

</details>

<details>
<summary>Why does my OBJ have no .mtl material file?</summary>

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.

</details>

<details>
<summary>My model is on its side after importing — what do I fix?</summary>

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.

</details>

<details>
<summary>What does the scale factor do, and when do I need it?</summary>

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.

</details>

<details>
<summary>Does it handle files with edges or unusual scanner properties?</summary>

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.

</details>

## Related tools

- [OBJ ↔ STL Mesh Converter](https://gizza.ai/tools/mesh-convert/): Convert pasted OBJ or ASCII STL meshes to OBJ, ASCII STL, or binary STL data URLs. Supports scaling, Y-up/Z-up axis swaps, and triangulation.
- [OBJ Vertices to CSV](https://gizza.ai/tools/obj-vertices-to-csv/): Extract every vertex of a Wavefront OBJ mesh into an x,y,z CSV or plain XYZ point cloud — with unit scaling, Y-up/Z-up conversion, rounding, dedupe and object filters.
- [STL Vertices to CSV](https://gizza.ai/tools/stl-vertices-to-csv/): Extract triangle vertex coordinates from ASCII or binary STL into CSV or plain XYZ text — with triangle rows, normals, axis conversion, scale, rounding and dedupe.
- [Strip accents and transliterate text to plain ASCII](https://gizza.ai/tools/accent-stripper/): Paste text and strip accents to plain ASCII. Transliterate non-Latin scripts, preserve chosen characters, lowercase, and audit unmapped output.
- [Brotli Decompress](https://gizza.ai/tools/brotli-decompress/): Paste a Brotli (.br) payload as Base64 or hex and read what's inside. Output as text, hex, or Base64, with size and ratio stats. Free, private, no sign-up.
