cadtomesh

OBJ → STL

Turning OBJ faces into STL triangle facets

OBJ is a text format of v, vn, vt and f records with optional o, g and usemtl names, and it allows quads, n-gons and negative relative indices. STL accepts triangles only. Conversion triangulates every face, resolves relative references, discards materials, UVs, groups and smoothing tags, then writes a facet normal plus three corners with no unit declaration.

Drop a .obj file here, or

Processed on this device ยท nothing uploaded

16input formats
7output formats
0 Buploaded, ever

How to convert OBJ to STL

  1. Load the OBJ textOpen a .obj file. The reader tokenises v, vn, vt and f lines, collects coordinate, normal and UV arrays, and notes optional o, g, usemtl and s records without acting on them.
  2. Resolve face referencesEach f entry is parsed into vertex, texture and normal indices. Negative values are relative offsets from the current list length and are converted to absolute 1-based positions; mixed v/vt/vn triples are reduced to their vertex slots.
  3. Triangulate every polygonQuads and n-gons are split into triangles by a fan from the first corner, because STL accepts exactly three corners per facet. A polygon with k vertices becomes k minus 2 triangles.
  4. Drop appearance and naming datauv coordinates, usemtl names, .mtl references, o and g labels and smoothing groups are discarded. The reader does not fetch the .mtl sidecar or load any texture map.
  5. Flatten into one geometryAll triangles are merged into a single buffer holding just position and normal channels. For meshes below 300,000 triangles, corners sharing a position and normal are deduplicated into an indexed structure; larger ones are expanded without an index buffer.
  6. Write STL facetsEach triangle becomes a 50-byte binary record with a facet normal and three corners, preceded by an 80-byte header and a 32-bit count. No unit field is stored.

What happens when OBJ is converted to STL

The records an OBJ file is made of

An OBJ is line-oriented plain text. A v line holds three coordinates, a vn line a normal vector, and a vt line a texture coordinate, each appended to a global list in file order. f lines describe faces by referencing those lists, and optional o and g records name objects or groups while usemtl selects a material from a companion .mtl file and s declares a smoothing group. The reader parses all of these tokens into arrays but treats names and material selections as annotation only. Because geometry lives in shared global arrays, a single v line can be referenced by many faces, which is the central structural difference from the facet-by-facet layout STL expects.

Resolving face indices and relative references

A face entry may be a bare vertex number, a v/vt/vn triple, or a v//vn pair where the texture slot is empty. Values are normally 1-based into the global arrays, but OBJ also permits negative integers that count backwards from the most recently defined element, so -1 means the last vertex seen so far. The loader converts those relative offsets into absolute positions before building triangles. It keeps only the vertex and, where present, the normal component of each triple; the texture index is read and then ignored. Malformed entries whose indices point outside the arrays are a frequent source of parse failures, because the format has no checksum or length prefix to validate them.

Turning polygons into triangle facets

STL has no representation for a quad or an n-gon; every record must describe exactly three corners. OBJ freely allows four or more, so the converter triangulates each polygonal face before export. A fan from the first vertex splits a face with k corners into k minus 2 triangles that share that corner. Winding is preserved so the derived facet normals point the same way as the original polygon. On a convex face this is exact, but a concave n-gon can produce overlapping or outward-slipping triangles because a simple fan does not respect indentations. The resulting triangle set is what the flattened geometry receives, and no further polygon information survives the step.

Material, UV and naming records are dropped

The reader does not follow .mtl references or load texture maps, even when a usemtl line names a library that sits beside the .obj. Every usemtl statement, o and g label, vt coordinate and smoothing group is discarded as the faces are reduced to triangles. Smoothing groups in particular have no analogue in STL, so any interpolated shading they requested is lost. Since the pipeline keeps only position and normal channels, there is no slot into which a material name or UV pair could be copied. Colour that existed only through a .mtl definition therefore vanishes completely, and the export contains a single unnamed mesh rather than a collection of named objects.

One geometry before the STL writer runs

After triangulation, all triangles are merged into a lone mesh buffer carrying coordinates and normal vectors only. There are no groups, no material slots and no UV set, even if the source declared dozens of objects. When the triangle total stays below 300,000, the builder welds vertices keyed on position and normal into an indexed structure; above that threshold it expands the geometry without an index buffer. The STL writer then expands whatever it receives back into three explicit corners per facet, because the target format cannot express shared vertices. This flattening is why object boundaries, group names and material assignments have already disappeared before a single facet byte is written.

Constructing the binary facet stream

Facet bytes are laid out in the compact binary dialect. An 80-byte header comes first, then an unsigned 32-bit integer giving the triangle total. Every facet occupies 50 bytes: a normal, three corner triples and a 16-bit attribute short left at zero. Coordinates are taken verbatim from the welded or expanded geometry with no scaling, and the stream stores no unit descriptor at all. The normal is derived from triangle winding, which is why consistent face ordering in the source matters. Fixed record width means the byte total is 84 plus 50 times the facet count, so emission is strictly sequential and needs no delimiter scan.

Normals, smoothing and encoding size

OBJ can attach vn normals to individual corners and use s smoothing groups to interpolate shading across a surface, but STL stores exactly one normal per flat facet. During export those per-vertex normals and smoothing assignments are discarded, and each facet receives a single direction derived from its three corners, so a smoothly shaded sphere becomes visibly faceted. The writer chooses binary STL over the alternative ASCII spelling of facet normal, outer loop and vertex lines. Binary is markedly smaller: the textual form repeats keywords and decimal digits for every corner, while a binary facet is a fixed 50 bytes. No colour or texture accompanies either encoding.

OBJ vs STL

OBJ sourceSTL result
Face primitivesTriangles, quads and n-gonsTriangles only; polygons fan-triangulated
Vertex referencesGlobal v/vn/vt lists, 1-based or negative relative indicesEach facet stores three explicit corners
UV datavt records presentNo UV channel written
Materialsusemtl names and a .mtl sidecarAbsent; the sidecar is not followed
Normal storagevn corner normals and s smoothing groupsOne normal computed per facet
Object / group nameso and g recordsDiscarded; one flattened mesh
Coordinate unitsNo declaration in OBJNo unit field in the facet stream
EncodingPlain text v, vn, vt and f linesBinary 50-byte facets with an 80-byte header
Colour / texturesPossible through .mtl and image mapsNone; no texture loading occurs
HierarchyNamed objects and groupsNo hierarchy or assembly structure

Supported formats

FormatReadWriteNotes
STEP (.step .stp)Yes–ISO 10303 B-rep, AP203 / AP214 / AP242
IGES (.iges .igs)Yes–Entity-based surfaces, up to 5.3
BREP (.brep)Yes–OpenCascade native B-rep
STL (.stl)YesYesBinary and ASCII triangles, no units
OBJ (.obj)YesYesv / vn / f records; .mtl references ignored
PLY (.ply)YesYesBinary position and normal arrays
GLB / glTF (.glb .gltf)YesYesglTF 2.0, binary or JSON with data URI
3MF (.3mf)YesYesOPC/ZIP package, declares millimetres
FBX (.fbx)Yes–Binary and ASCII, static mesh only
DAE (.dae)Yes–Collada scene graph is flattened
3DS (.3ds)Yes–Legacy chunk-based 3D Studio format
AMF (.amf)Yes–XML, ZIP-compressed packages supported
USDZ (.usdz)YesYesZIP containing a USD stage

Known limitations and why a conversion can fail

Appearance and structure do not survive

Every usemtl name, .mtl reference, vt texture coordinate, o and g label and smoothing group is stripped before the STL is built. The reader never opens the sidecar material library and never loads a texture image, so a model whose entire look came from material definitions arrives as bare geometry. Grouping is lost too: dozens of named objects collapse into one flat facet list with no way to separate them afterwards. STL has no material channel, no UV set and no object name field, so there is nothing to translate into. Colour that was only ever expressed through .mtl is unrecoverable from the output under any reader.

Concave polygons can triangulate badly

The converter splits quads and n-gons with a simple fan from the first corner. That is exact for convex faces but can generate overlapping or incorrectly oriented triangles on concave outlines, since a fan ignores indentations and may bridge across a notch. STL offers no way to mark such artefacts, so the extra facets are written as ordinary triangles and become part of the mesh. A face with many corners also adds triangles linearly, which inflates the facet count beyond what a quad-only model would suggest. There is no ear-clipping or quality check here; inspecting or retessellating concave faces in the source is the practical remedy.

Relative indices and broken f lines

OBJ permits negative face indices that count backwards from the current list length, and the loader resolves them to absolute positions. A mistake in that bookkeeping, or an index larger than the declared v, vn or vt arrays, can orphan a face or stop parsing. Mixed v/vt/vn triples are reduced to their vertex and normal slots, so a file relying on subtle texture-index alignment gains nothing from it. Because the format is plain text with no checksum, a stray token or a missing separator is a realistic failure mode. Such records are not repaired; the converter works with whatever valid triangles it can assemble from the remaining lines.

No unit is declared at either end

OBJ coordinates are bare numbers and STL coordinates are bare numbers, so the writer copies x, y and z verbatim without scaling and stores no unit field in the 80-byte header or the facet records. A part modelled as 100 units wide is written as 100.0 regardless of whether the author meant millimetres or inches. Slicers commonly assume millimetres, but the file offers no evidence for that reading. Since this conversion only triangulates and re-encodes geometry, it neither introduces nor fixes any scale ambiguity. Resizing must happen before conversion or through direct editing of the exported facet coordinates.

Smooth shading becomes flat facets

Smoothing groups declared with s lines and corner normals from vn records let an OBJ render a curved surface with interpolated shading, yet STL allows a single normal per facet. Export discards those per-vertex directions and computes one facet normal from each triangle winding, so a smooth sphere reads as a faceted polyhedron in any viewer that relies on facet normals. A finer source mesh reduces the visual effect but cannot restore interpolation, because the target carries no vertex-normal channel. Hard edges that were intentionally split in the OBJ also collapse into ordinary facet boundaries, since there is no crease metadata to preserve them.

Encoding affects size, not geometry

The writer emits binary STL, whose fixed 50-byte facets and 80-byte header keep the file compact. ASCII STL would spell the same triangles as facet normal, outer loop and vertex text lines and is considerably bulkier, since every corner is printed as decimal digits with keywords around it. Either encoding stores 32-bit floats and no unit, so switching between them changes length, not precision or shape. There is no colour, texture or metadata channel in either. Large OBJ meshes with many n-gons can still produce a sizable binary file, because each triangulated facet adds a full 50-byte record.

Frequently asked questions

What happens to quads and n-gons in my OBJ?

STL records describe exactly three corners, so every quad and every n-gon is triangulated before export. The converter uses a fan from the first vertex, splitting a face with k corners into k minus 2 triangles that preserve the original winding. Convex faces convert cleanly, while a concave polygon can yield overlapping or misplaced triangles because a simple fan does not respect indentations. The extra facets are written as ordinary STL triangles, so they remain in the mesh rather than being flagged.

Does the converter read my .mtl file or textures?

The sidecar is not opened. The OBJ reader parses usemtl names and .mtl references as annotation only and never opens the sidecar library or loads a texture image. Every material selection, vt texture coordinate, o or g label and smoothing group is discarded as faces are reduced to triangles. Since the flattened geometry keeps just position and normal channels, there is nowhere to store a material name or UV pair. Colour that existed solely through .mtl definitions is therefore absent from the STL.

Why did a smooth model come out looking faceted?

OBJ can attach vn corner normals and declare s smoothing groups to interpolate shading, but STL stores one normal per flat facet. Export drops those per-vertex directions and computes a single facet normal from each triangle winding, so a curved surface reads as a polyhedron wherever a viewer relies on facet normals. A denser source mesh reduces the effect, yet interpolation cannot be restored because the target has no vertex-normal channel. Hard creases also lose their explicit marking.

Are negative face indices handled?

They are resolved. OBJ allows negative integers in f records that count backwards from the most recently defined element, so -1 refers to the last vertex seen so far. The loader resolves these relative offsets into absolute 1-based positions before building triangles, and it reduces mixed v/vt/vn triples to their vertex and normal slots while ignoring the texture index. An out-of-range index or a malformed token remains a plausible parse failure, since OBJ text carries no checksum to validate entries.

Is any unit recorded in the STL facet stream?

None whatsoever. The writer copies x, y and z from the OBJ v lines verbatim into facet corners without scaling, and the 80-byte header and 50-byte records contain no unit field. A 100-unit part is written as 100.0 whether the author meant millimetres or inches. Slicers often assume millimetres, but the file itself gives no hint. Because conversion only triangulates and re-encodes geometry, any resizing must be applied before export or by editing facet values afterwards.

How does binary STL compare with the ASCII spelling in size?

Binary is what this writer produces. It starts with an 80-byte header and a 32-bit count, then lays down 50-byte facet blocks that each carry a normal, three corners and a zeroed attribute short. ASCII STL would spell those triangles out as facet normal, outer loop and vertex text and is considerably longer, since keywords and decimal digits repeat for every corner. Both variants store 32-bit floats and omit any unit, so the choice affects byte length rather than shape or precision.

Do object and group names survive the conversion?

They do not survive. o and g records are parsed as annotation and then discarded along with usemtl names, vt coordinates and smoothing groups. Before any facet is written, all triangles are merged into a single geometry that carries only position and normal channels, so a file containing dozens of named objects collapses into one undifferentiated facet list. STL has no field for an object name, a group or a material slot, so there is nothing in the target format to hold them.

Can STL keep the per-vertex normals from vn lines?

It cannot. A binary STL facet has room for exactly one normal vector shared by its three corners, plus an unused 16-bit attribute field. The vn records that gave each OBJ corner its own direction are dropped, and the writer computes a single facet normal from the triangle winding instead. Smoothing groups are discarded at the same time. Any shading interpolation the source requested is therefore lost, and the exported mesh relies entirely on flat per-facet normals.

Related converters

All converters

Last updated: