You find a great model on MakerWorld, download the .3mf, open PrusaSlicer to tweak it... and PrusaSlicer just won't load it. No mesh, or an error, or an empty plate. The file isn't corrupt — Bambu Studio opens it fine. So what's going on?
I went down this rabbit hole after a real bug report on a PrusaSlicer GitHub issue, fixed it, and the fix runs entirely in the browser. Here's the anatomy of the problem and how the flattening works.
A 3MF is just a zip
.3mf is a zip archive. Rename one to .zip, unzip it, and a simple one looks like:
3D/3dmodel.model ← the geometry (XML)
[Content_Types].xml
_rels/.rels
Metadata/... ← thumbnails, slicer settings
3dmodel.model holds <vertices> and <triangles> inside <resources>, and a <build> block that places those objects on the plate. Any slicer that speaks vanilla 3MF reads this and you're done.
Bambu uses the "production extension"
Open a Bambu Studio 3MF and the root model looks very different:
<model requiredextensions="p" xmlns:p="http://schemas.microsoft.com/3dmanufacturing/production/2015/06" ...>
<resources>
<object id="1" p:UUID="...">
<components>
<component p:path="/3D/Objects/object_1.model" objectid="1" p:UUID="..."/>
<component p:path="/3D/Objects/object_2.model" objectid="1" p:UUID="..."/>
...
</components>
</object>
</resources>
<build>...</build>
</model>
Notice: the root model has no geometry of its own. It declares requiredextensions="p" (the 3MF production extension) and points at a pile of external part files:
3D/3dmodel.model ← no meshes, just <component p:path=...> references
3D/Objects/object_1.model ← actual <vertices>/<triangles>
3D/Objects/object_2.model
... (one real file had 25 of these)
The production extension exists to split big assemblies across files and stream them. Bambu Studio leans on it heavily. The catch: PrusaSlicer doesn't implement the production extension. It sees requiredextensions="p", sees a root model with no geometry, and gives up. That's the whole bug.
The fix: flatten it back to vanilla 3MF
The goal is to produce a single self-contained 3dmodel.model with all geometry inlined, no p: namespace, no external parts — the boring kind of 3MF every slicer reads. Sounds simple; the fiddly part is the ID namespaces.
Each part file has its own local object id space. A <component objectid="1"> inside the root refers to id 1 in the file named by p:path, not id 1 in the root. So you can't just concatenate — you have to remap.
The flattening steps:
-
Assign globally-unique object ids across every
.modelfile in the archive. - Inline external meshes in dependency order — topological sort so leaf meshes come before the assemblies that reference them.
-
Rewrite every reference: each
p:path+objectidpair, same-file<component>references, and the<build>itemobjectids, all repointed to the new global ids. -
Strip the extension: drop
requiredextensions, the production (p) namespace, and Bambu's vendor namespace. -
Emit a clean container: a minimal
[Content_Types].xmland_rels/.rels(this also fixes dangling thumbnail relationships).
Why I did it with string ops, not a DOM parser
The transform only touches structural tags — <object>, <component>, <build>, the namespace declarations. It never needs to read or rewrite the <vertex> / <triangle> bodies, which are the huge part.
So instead of pulling in a DOM/XML parser (heavy, and browser/Node behave differently), it's careful string manipulation over the structural elements. Two payoffs:
- It runs identically in Node and the browser, so I could validate the exact same code against a real file on the command line.
- It's fast and memory-light even on multi-megabyte files.
Did it actually work?
The bug report came with a real 6.3 MB file from MakerWorld — 25 external object models. After flattening:
- 151,317 vertices in → 151,317 out
- 302,542 triangles in → 302,542 out
- all 25 external files merged into one root model
- every reference resolved, no forward references, XML well-formed
- zero production-extension residue
Then the actual test: the flattened file opens in PrusaSlicer. Same geometry, now as a vanilla 3MF.
Try it / steal it
I put this in a free, browser-only tool — drop a Bambu .3mf, get a Prusa-friendly .3mf back, nothing uploaded: flip3d.app/tools/bambu-3mf-to-prusa. It's part of Flip3D, a set of 3D file tools that all run client-side.
Update: the same flattener now powers a free 3MF viewer — drop any Bambu Studio, Orca or PrusaSlicer .3mf to see the whole project with each object in its filament color, plus the plates, printer and slicer thumbnail. Still 100% in the browser, nothing uploaded.
Follow-up: How Bambu Studio stores multi-color painting in a 3MF: decoding the per-triangle paint_color data so painted models show their real colors.
It's open source if you want to read the flattener or file an issue: github.com/suyfdong/flip3d.
If you've hit the "PrusaSlicer won't open my Bambu file" wall, that's why — and now you know exactly what's happening under the zip.
Top comments (2)
Great breakdown of why this fails. The production/vanilla extension split is one of the messier parts of the 3MF spec in practice — it creates real cross-tool headaches that most users never understand because the error message gives nothing away. The string manipulation approach instead of full XML parsing is smart for a tool that only needs to restructure references without touching geometry. Curious: do Bambu files always resolve to a clean DAG, or have you hit circular component references in the wild?
Thanks! Sorry, only just saw this.
Haven't run into a cycle so far. The spec doesn't allow an object to reference itself anyway, and Bambu's layout is pretty shallow — basically an assembly object in the root pointing at meshes in 3D/Objects/. The file I was debugging (25 external parts, from MakerWorld) was a clean DAG.
There's a guard in the topo sort just in case: if it ever loops back to a node it's still visiting, it drops that edge instead of recursing forever. So a broken file would lose a reference rather than freeze the tab. If you've got one that does this I'd love to see it.
Also, the same flattener now powers a 3MF viewer if that's useful: flip3d.app/3mf-viewer/