cadtomesh

STL → 3MF

Converting STL into a 3MF Package with Declared Units

An STL file is a bare list of 50-byte facets with no unit, name, colour or object boundaries. This conversion reindexes those triangles into shared vertices and writes one 3D/3dmodel.model part inside an OPC ZIP. The model XML declares a millimetre unit, emits a single object with vertices and triangles, and carries no colour. Coordinates are untouched.

Drop a .stl file here, or

Processed on this device ยท nothing uploaded

16input formats
7output formats
0 Buploaded, ever

How to convert STL to 3MF

  1. Load the source facetsThe STL bytes are read as either binary 50-byte facet records or ASCII facet text, and each triangle is pulled into an in-memory array.
  2. Weld duplicated cornersCorner positions that coincide across neighbouring facets are collapsed into one shared vertex so the mesh can be stored as an index list.
  3. Flatten into one geometryEvery triangle is folded into one combined structure that keeps just positions and normals; groups, material slots and UV sets are all left behind.
  4. Assemble the OPC packageThree parts are written into a ZIP: [Content_Types].xml, _rels/.rels and 3D/3dmodel.model containing the indexed mesh.
  5. Choose the 3MF outputSelect the 3MF target and save; the exporter stamps a millimetre unit attribute, emits one object and one build item, and stores no colour.

What happens when STL is converted to 3MF

Welding a triangle soup into indexed vertices

An STL stores geometry as an ordered stream of triangles, and each facet repeats all three corner coordinates even where neighbours touch. Binary STL uses 50-byte records: a 12-byte facet normal, three 12-byte vertices and a 2-byte attribute field, so the same point appears many times. To build a 3MF <vertices> table, the converter scans every corner, quantises matching coordinates to detect duplicates, and builds one shared index per unique position. Each original triangle then becomes a triple of indices written into <triangles>. Triangle count stays identical to the source, but vertex count drops substantially because the redundancy is removed. This deduplication is purely a storage change; the shape, winding order and coordinate values are preserved exactly.

Laying out the OPC ZIP parts

3MF was designed for additive manufacturing as an Open Packaging Conventions archive, so the output is a ZIP container rather than a single flat file. Three named entries are produced. [Content_Types].xml declares the default content types for relationship parts and the model part so a reader knows how to interpret each byte stream. _rels/.rels is the package-level relationship file whose single relationship points at 3D/3dmodel.model with the model relationship type. The model part itself is an XML document under the 3D folder. A ZIP also needs local file headers and a central directory, which is why even an empty mesh yields a container with a small fixed byte overhead.

What the model XML actually contains

Inside 3D/3dmodel.model the root <model> element carries a unit attribute, and this converter sets it to millimetre. The format is capable of far more: multiple <object> elements, a <build> section with per-item placements, colour groups and base materials. This exporter uses none of that richness. It writes exactly one <object> holding one <mesh>, with a <vertices> list of x/y/z coordinates and a <triangles> list of v1/v2/v3 index triples that reference that list. No <color>, no <basematerials> and no material indices are emitted, because the flattened source geometry has only positions and normals and the STL carried no appearance data to begin with.

Normals and the unchanged geometry

Every binary STL facet carries its own normal vector, but the 3MF mesh written here stores only vertex positions and triangle indices; there is no normal element in the produced XML. Consumers recompute shading normals from triangle winding when they load the file, so a faceted or smooth appearance is a rendering decision made downstream rather than data carried across. Crucially, nothing about the geometry is modified: the same number of triangles is emitted, each corner keeps the exact float coordinate it had in the source, and no tessellation, decimation, smoothing or repair is applied, because the input is already a triangulated mesh rather than a CAD solid.

Why a declared unit removes ambiguity

STL has no header field, attribute or metadata slot in which to state a physical unit. A facet whose coordinates read 100 could mean 100 millimetres or 100 inches, and a slicing program has to guess, with a wrong guess producing a 25.4 times scale error. By contrast, the 3MF <model> root accepts a unit attribute, and stamping millimetre makes the numeric interpretation explicit. Slicing software can then read the declaration instead of relying on a user preference or a filename hint. The trade is that the converter always writes millimetre; it does not inspect the STL, cannot know what unit the author intended, and never rescales the coordinates to match the label.

Container overhead versus the bare source

Because the source is a facet stream with essentially no framing, the transition to 3MF adds a predictable, mostly fixed cost. The ZIP local headers, the central directory, the two scaffolding XML parts and the XML tags around each vertex and triangle all consume bytes that an STL never needed. This overhead is small and does not grow with triangle count in the same way the payload does; it is dominated by the archive structure and the textual markup. Binary STL is compact per facet, while the XML vertex and triangle elements are human-readable but verbose. The result is that a 3MF of the same mesh is somewhat larger, with the difference shrinking in relative terms as the triangle count climbs.

STL vs 3MF

STL source3MF output
File structureRaw facet streamOPC ZIP archive
Unit declarationAbsentmodel unit set to millimetre
Vertex storageDuplicated per facetShared <vertices> table
Face indexingImplicit by orderExplicit <triangles> index triples
Colour / materialsNone availableAllowed by schema, none written
Object countNo object boundariesOne <object> and one build item
NormalsOne vector per facetNot stored, derived from winding
Package partsNot applicable[Content_Types].xml, _rels/.rels, 3D/3dmodel.model
Facet encoding50-byte binary recordsTextual XML elements

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 data is never carried across

3MF can express colour groups, material groups and per-triangle material indices, yet the file produced from an STL contains none of them. The source format has no colour channel at all, only a 2-byte attribute field per facet that is almost universally ignored, and the flattening step keeps just position and normal attributes. Consequently the resulting model XML has no <color> and no <basematerials> element. If you hoped the conversion would paint the mesh, it will not; every triangle is uncoloured and any downstream viewer applies its own default surface. This is a deliberate consequence of a source that never stored appearance, not a lost setting.

One object only, no build transforms

A single STL has no concept of separate solids, sub-assemblies or named parts, and the converter reflects that by emitting exactly one <object> and a single <build> item. The 3MF specification happily supports many objects, <components> subtrees and per-item affine transforms, but none of those structures are generated here. If the STL happened to contain several disjoint shells that a human would call distinct parts, they are welded into the one vertex and triangle table. There is no object id, no part name and no transform matrix to recover, so a consumer cannot separate those shells again without geometric analysis.

The unit is asserted, not converted

Writing a millimetre unit attribute is a declaration, not a measurement. The exporter copies the STL coordinates verbatim and labels them millimetre regardless of what the person who built the file had in mind. An STL exported from a system that worked in inches, or one whose author simply treated the numbers as generic units, will be tagged millimetre anyway with its magnitudes unchanged. That removes the reader's guess but it can also freeze in a wrong assumption. The only safe workflow is to know the source unit before conversion; the pipeline will not detect, validate or translate it, and there is no scaling stage anywhere in the path.

Per-facet normals are discarded

Binary STL dedicates twelve bytes of each facet to a normal vector, and some writers use those to encode a smoothed look. The 3MF mesh written here has no normal element, so those vectors are dropped on export. Shading is reconstructed later from the winding order of each triangle in the <triangles> list, which yields a hard, faceted appearance wherever the original relied on stored normals. The flattened geometry does compute a normal attribute during processing, but the 3MF exporter writes only <vertices> and <triangles>, so that attribute never reaches the file. Smoothing intent, if any, is therefore unrecoverable.

No healing, repair or re-tessellation

The conversion is a repackaging, not a cleanup. If the incoming STL is non-manifold, has flipped facets, contains self-intersections, has gaps along a supposedly watertight shell, or carries degenerate zero-area triangles, every one of those defects is reproduced in the 3MF because the vertex and triangle tables are copied straight through. Nothing checks winding consistency, merges near-coincident shells beyond the exact deduplication, fills holes or removes slivers. Since the source is a mesh rather than a boundary-representation solid, the OpenCascade WebAssembly kernel with its Draft, Standard, Fine and Max deflection levels is never invoked; that tessellator exists for STEP, IGES and BREP surfaces.

Frequently asked questions

Does the triangle count change during conversion?

No. The exporter writes one <triangles> entry for every facet present in the STL, so the face count is identical before and after. What changes is storage: the three repeated corner positions of each facet are replaced by references into a shared <vertices> table, which is usually smaller. Neither triangles nor coordinates are added, removed or split, so the polygon count and every vertex position are preserved exactly.

Will the 3MF contain any colours or materials?

No. Even though the 3MF schema allows <color> groups, <basematerials> and per-face material indices, this exporter emits a mesh with only <vertices> and <triangles>. The source STL has no colour channel, and the flattening step retains just position and normal attributes. Every triangle is therefore uncoloured, and any shading or material you see later is supplied by the application that opens the file.

Which unit is written into the model?

The <model> root receives a unit attribute set to millimetre. This is a fixed choice made by the exporter, not a value read from the STL, since STL has nowhere to store a unit. The declaration tells a consuming slicer how to interpret the raw coordinates, but the numbers themselves are copied unchanged. If the original was authored in inches or microns, the label will not match the intent.

Why is the output 3MF larger than the input STL?

An STL is a compact facet stream with an 80-byte header and 50-byte records. A 3MF is a ZIP archive that must also hold [Content_Types].xml, _rels/.rels, the XML model part, local file headers and a central directory, plus textual tags around every vertex and triangle. That packaging adds a small, largely fixed byte overhead. The gap narrows in percentage terms as the triangle count grows large.

Are the STL facet normals stored in 3MF?

They are not. The 3MF mesh produced here contains only a <vertices> list and a <triangles> index list, with no normal element. The normal attribute computed while flattening the geometry is never serialised. A reader reconstructs shading from triangle winding order, so any smoothed look that depended on the stored STL normals is lost and a faceted appearance is the usual result.

Is the mesh repaired or made watertight?

No. There is no healing, hole filling, winding fix or degenerate-triangle removal in this path. Non-manifold edges, gaps, self-intersections and flipped facets pass straight through into the new container because the vertex and triangle data are copied verbatim. The mesh is parsed directly, so no boundary-surface re-tessellation or deflection setting is applied. Treat the 3MF as geometrically identical to the STL. The <vertices> and <triangles> tables are written from the same floats the loader produced, with no validity check in between.

Can several STL solids become several 3MF objects?

No. The flattening step merges everything into one geometry, and the exporter writes exactly one <object> with one <mesh> and a single <build> item. Disjoint shells that a person might regard as separate parts are fused into the same vertex and triangle table. No object identifiers, part names or component transforms are generated, so the segmentation is not recoverable from the file.

Is the geometry altered in any way?

It is not. Coordinates, winding order and triangle count are preserved; only the representation changes, from a duplicated-corner facet stream to an indexed <vertices> plus <triangles> model inside an OPC ZIP. No scaling, rotation, smoothing, decimation or tessellation occurs. The deflection controls used for CAD surfaces do not apply to a mesh source, so the surface you export is the surface you loaded.

Related converters

All converters

Last updated: