How to convert 3MF to STL
- Unpack the OPC archiveThe 3MF is opened as a ZIP; [Content_Types].xml and _rels/.rels are read to locate 3D/3dmodel.model, and compressed entries are inflated by client-side JavaScript.
- Parse the model documentThe XML is scanned for the unit attribute, every <object> mesh, <build> items and any <components> matrices that place each part.
- Bake transforms into verticesEach component and build matrix is applied to the affected vertex positions so part placement becomes absolute coordinates rather than a transform node.
- Merge into one triangle setObjects are gathered into a lone triangle list, and the merged surface is stored with only coordinates and computed normals, dropping colour groups, material indices and part ids.
- Emit binary STL facetsAn 80-byte header and a stream of 50-byte facet records are written, each holding a computed normal and three corner positions, with no unit field.
What happens when 3MF is converted to STL
Opening and inflating the OPC package
3MF files arrive as ZIP archives, so the first job is to read the container rather than a flat stream. The reader walks the central directory, finds [Content_Types].xml to learn the part content types and reads _rels/.rels to resolve the package relationship that points to 3D/3dmodel.model. Some producers store the model part deflated, so each compressed entry is decompressed by JavaScript on the client before its XML is parsed. The same loader also accepts a plain, unzipped XML 3MF and the zipped AMF-style packages that some tools emit, so the geometry can be reached regardless of which container variant was supplied.
Resolving content types and relationships
A ZIP alone does not say which member is the 3D payload, so the package conventions matter. [Content_Types].xml provides a Default entry for the rels extension and an Override entry mapping the model part to the 3MF model media type. _rels/.rels then supplies a Relationship whose Target is 3D/3dmodel.model and whose Type identifies it as the model. Following that chain is what lets the reader ignore thumbnail images, metadata parts or extra rels files that a richer 3MF might carry. Only the resolved model document is parsed for geometry; the auxiliary parts are effectively skipped on the way to the STL.
Reading units, objects and build items
The <model> root carries a unit attribute whose legal values include millimetre, micron, inch, foot, centimetre and metre, so the number stored beside a vertex has an explicit physical meaning. Below it sit one or more <object> elements, each with a <mesh> of <vertices> and <triangles>. A <build> section lists items that reference those objects, and <components> can nest further <component> references with their own affine transform matrices. Colour groups, base materials and per-face material indices may also be present. All of these are read, but only the unit, the vertex and triangle data and the transforms are relevant to producing a bare facet stream.
Baking placement into vertex coordinates
STL has no node tree and no transform records, so any placement encoded in 3MF must be resolved before export. For every build item and nested component, the reader takes the associated matrix and multiplies it into the object's vertex positions, turning local coordinates into final world coordinates. Repeat references to the same object are expanded into separate transformed copies at this stage. Normals are transformed by the rotation part so they stay consistent with the moved triangles. After baking, a part that sat at an offset or a rotation is simply a set of triangles already in the right place, with no matrix left to record how it got there.
Collapsing parts into one triangle set
Once transforms are applied, the reader concatenates the triangles from every object into a single undifferentiated list and flattens them into one geometry carrying only position and normal attributes. There is no group, no material slot and no object id in that structure. Object boundaries, <build> items and material group assignments simply have nowhere to live and are dropped. Colour values attached to vertices or triangles are likewise ignored. The result is a pure triangle soup: a shared coordinate array plus a facet list, ready to be written as STL records. Compatibility with the target's simplicity is what forces this collapse.
Re-expanding indexed meshes into STL facets
3MF stores a compact <vertices> table plus <triangles> index triples, whereas STL repeats all three corners of each facet. The exporter therefore walks each triangle, looks up its three vertex indices in the table, and writes the resulting coordinates back out as an explicit 50-byte facet: a normal vector, three positions and a 2-byte attribute field, preceded by an 80-byte header. The facet normal is derived from the triangle winding via a cross product, since 3MF stored no normals. Unit, colour and object metadata have no field in the binary layout and are simply omitted. The numbers themselves are copied without rescaling.
3MF vs STL
| 3MF source | STL output | |
|---|---|---|
| Container | OPC ZIP with XML parts | Raw binary facet stream |
| Unit declaration | model unit: mm, micron, inch and others | None |
| Vertex storage | Shared <vertices> plus indexed <triangles> | Duplicated per facet |
| Object structure | Multiple <object> and <build> items | One undifferentiated soup |
| Transforms | Per-component affine matrices | Baked into vertex coordinates |
| Colour / materials | Optional colour and material groups | None |
| Normals | Not stored, derived from winding | One facet normal per triangle |
| Scene metadata | [Content_Types].xml and relationship parts | 80-byte free-form header |
| Scale semantics | Explicitly declared | Ambiguous outside the file |
Supported formats
| Format | Read | Write | Notes |
|---|---|---|---|
| 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) | Yes | Yes | Binary and ASCII triangles, no units |
| OBJ (.obj) | Yes | Yes | v / vn / f records; .mtl references ignored |
| PLY (.ply) | Yes | Yes | Binary position and normal arrays |
| GLB / glTF (.glb .gltf) | Yes | Yes | glTF 2.0, binary or JSON with data URI |
| 3MF (.3mf) | Yes | Yes | OPC/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) | Yes | Yes | ZIP containing a USD stage |
Known limitations and why a conversion can fail
Losing the unit is the dominant risk
The most consequential loss in a 3MF-to-STL conversion is the unit declaration. 3MF states whether coordinates mean millimetres, microns, inches or something else; STL has no header field for units whatsoever. The exporter writes the raw coordinate numbers exactly as read, so a model declared in microns and one declared in millimetres produce byte-for-byte similar magnitudes. A consuming program that assumes millimetres will then interpret a micron-based part as a thousand times too small. Because the label is gone, there is no way to recover the intended scale from the STL alone. Always record the source unit before converting.
Colours and material groups disappear
Colour information in 3MF can live in colour groups, base materials or per-triangle material indices, and a rich file may define several named materials with different properties. The flattened geometry keeps only position and normal attributes, so none of that survives into STL. STL facets carry a single 2-byte attribute field that no mainstream reader interprets as colour, and the exporter leaves it at zero. Any tint, transparency or material name present in the source is therefore gone, and STL offers no channel in which it could be re-encoded. Because the colour group indices are dropped before the facet records are assembled, there is no stage in the pipeline where a tint could be recovered or reattached.
Multi-part structure is flattened away
A 3MF can describe an assembly of several distinct objects, each placed through its own build item or nested component transform. STL has no concept of solids, part names, a scene graph or object boundaries; it is one continuous stream of triangles. The reader merges everything into a single triangle set, so those separate parts become indistinguishable regions of the same soup. Baking their transforms preserves where each sat but destroys the fact that they were separate. A downstream tool cannot split them back into the original parts without re-deriving connectivity from the geometry itself.
Transforms survive only as baked numbers
When a build item or component matrix is applied, the placement is folded permanently into the vertex coordinates. That is the only way STL can represent it, since the format has no transform nodes. The consequence is that the transform is no longer editable or even visible as a parameter: you cannot read back a rotation angle or an offset from the output. Repeated instances of an object are expanded into ordinary triangles too, losing the instancing relationship. The geometry is correct in position, but all parametric placement information has been replaced by absolute numbers.
No scaling, healing or normal rescue
The conversion neither rescales coordinates to a common unit nor repairs the surface. A source declared in inches is not multiplied by 25.4, and a source in microns is not divided by a thousand; the raw values are written unchanged. Non-manifold edges, flipped windings, gaps and degenerate triangles in the 3MF mesh pass straight into the STL because triangle data is copied verbatim. Normals are recomputed from winding rather than taken from any stored source, so smoothed shading intent is lost. The boundary-surface tessellator is not part of this path at all, because a 3MF already supplies triangles read through three.js loaders.
Frequently asked questions
What happens to the 3MF unit declaration?
It is dropped. The <model> unit attribute has no counterpart anywhere in the STL layout, whose 80-byte header is free-form text and whose 50-byte facets hold only a normal, three vertices and a 2-byte attribute. The exporter writes the stored coordinate numbers verbatim and discards the unit label, so a slicer must guess the physical scale from the magnitudes alone. No scaling is applied to the stored numbers either, which leaves the physical interpretation entirely to whatever reads the file afterwards.
Why did my part come out a thousand times too small?
The source was probably declared in microns. 3MF allows micron as a legal unit, but the STL carries no unit field, so those micron-based numbers are written unchanged and a reader that assumes millimetres interprets them as a thousand times smaller. The converter does not detect the unit or rescale to millimetres. Record the source unit before export and scale the geometry yourself if the consumer expects a different one.
Are colours and materials preserved?
No. Colour groups, base materials and per-face material indices in the 3MF are ignored during flattening, which keeps only position and normal attributes. STL facets have a 2-byte attribute field that is not used for colour here, and no material sidecar exists for a bare mesh. Every exported triangle is uncoloured, and any appearance comes from whatever default the viewing application applies.
Do separate parts stay separate in the STL?
No. Every <object> in the source is merged into one triangle set, because STL has no solids, groups or object boundaries. A multi-part 3MF becomes an undifferentiated facet stream. The transforms that positioned those parts are baked into the vertex coordinates during flattening, so their placement is geometrically correct but the fact that they were distinct components is unrecoverable from the output.
Are build item and component transforms kept?
They are applied, not kept. Each build item and nested <component> matrix is multiplied into the affected vertex positions so parts land in their final world coordinates. STL has no transform nodes, so afterwards there is no rotation angle, offset or instance reference to read back. Repeated instances of one object are expanded into ordinary triangles, losing the instancing relationship while preserving where each copy sat.
Can the reader open compressed or zipped 3MF files?
Yes. The loader inflates deflated ZIP entries with client-side JavaScript before parsing the XML, and it also accepts a plain uncompressed XML 3MF as well as the zipped AMF-style packages some exporters produce. [Content_Types].xml and _rels/.rels are read first to resolve the 3D/3dmodel.model part, so the geometry is reached whichever container variant was supplied. Deflated members are inflated into memory while the central directory is walked to enumerate them, then the model part is decoded as text before any vertex is read. An uncompressed 3MF skips the inflate step yet follows the same relationship lookup.
Does the STL store vertex normals?
It stores one normal per facet, not per vertex. 3MF meshes contain no normal element, so the exporter derives each facet normal from the cross product of two triangle edges, following winding order. The flattened geometry does compute a normal attribute, but the binary STL record only holds a single facet normal plus three positions, so smoothed per-vertex shading from the source is not reproducible.
Is the geometry rescaled to millimetres?
No. The pipeline copies coordinate numbers exactly as parsed and never converts between units. A 3MF declared in inches, microns or millimetres yields STL facets with the same magnitudes; the unit label alone is removed. If the consumer expects millimetres but the source used a different unit, you must apply the scale factor before or after conversion. Healing, winding fixes and tessellation are likewise not performed.
Related converters
All converters
Last updated: