# Bambu Studio 3MF format — reverse-engineered spec Documented from a real Bambu Studio 2.06.01.55 save file and OrcaSlicer source code. Relevant to any tool that wants to generate `.3mf` files that Bambu Studio accepts natively. --- ## File structure (ZIP) ``` [Content_Types].xml _rels/.rels 3D/3dmodel.model ← assembly/metadata (small) 3D/_rels/3dmodel.model.rels ← points to the object file 3D/Objects/object_1.model ← geometry (large) Metadata/model_settings.config Metadata/project_settings.config ← optional; sets filament colours Metadata/plate_1.png ← optional; thumbnail ``` The geometry is split into a separate file referenced via a relationship. Both model files use the same XML namespace set. --- ## `[Content_Types].xml` ```xml ``` --- ## `_rels/.rels` ```xml ``` --- ## `3D/3dmodel.model` — assembly / build manifest Contains metadata and a single top-level object that references the geometry file via a component. No geometry lives here. ```xml BambuStudio-02.06.01.55 1 ``` ### "From Bambu Lab" detection Bambu Studio 2.5+ shows **"not from Bambu Lab, load geometry data only"** unless both of these `` elements are present in `3D/3dmodel.model`: ```xml BambuStudio-XX.XX.XX.XX 1 ``` The exact version string in `Application` does not matter; only the prefix `BambuStudio-` is checked. --- ## `3D/_rels/3dmodel.model.rels` ```xml ``` --- ## `3D/Objects/object_1.model` — geometry Contains the actual mesh. Multi-colour is encoded **per triangle** using the `paint_color` attribute (see below), not via separate objects or material groups. ```xml 1 ``` --- ## `paint_color` encoding The `paint_color` hex string on each triangle is a serialized **OrcaSlicer/PrusaSlicer `TriangleSelector` leaf-node state** (the same format used by support painting and seam painting, extended to 16 extruder slots for multi-material). ### `EnforcerBlockerType` enum (from `TriangleSelector.hpp`) ``` NONE = 0 (no paint — inherits parent) ENFORCER = 1 = Extruder1 BLOCKER = 2 = Extruder2 Extruder3 = 3 Extruder4 = 4 … Extruder16 = 16 ``` Extruder 1 and 2 reuse the `ENFORCER`/`BLOCKER` values for backward compatibility with PrusaSlicer 2.3.1. ### Serialization (leaf triangle, no subdivision) From `TriangleSelector::serialize()` in `TriangleSelector.cpp`: ``` bitstream = [split_sides & 1, split_sides & 2] # always [0, 0] for a leaf + state_bits ``` **State bits** (depends on `n = extruder_state_value`): | n | State bits (LSB first) | Total bits | |---|---|---| | 0 (NONE) | `[0, 0]` | 4 | | 1 (Extruder1) | `[1, 0]` | 4 | | 2 (Extruder2) | `[0, 1]` | 4 | | 3+ (Extruder3…) | `[1, 1]` + 4 bits of `(n-3)` LSB-first | 8 | The bitstream is read as a **little-endian integer** (bit[0] = LSB) and formatted as an **uppercase hex string**, padded to a whole nibble: - 4-bit values → 1 hex digit (no leading zero) - 8-bit values → 2 hex digits (leading zero if needed) ### Lookup table (extruders 1–6) | Extruder | State (n) | Bitstream (LSB→MSB) | Integer | `paint_color` | |---|---|---|---|---| | 1 | 1 | `00 10` | 4 | `4` | | 2 | 2 | `00 01` | 8 | `8` | | 3 | 3 | `00 11 0000` | 12 | `0C` | | 4 | 4 | `00 11 1000` | 28 | `1C` | | 5 | 5 | `00 11 0100` | 44 | `2C` | | 6 | 6 | `00 11 1100` | 60 | `3C` | ### General formula (Python) ```python def paint_color(extruder: int) -> str: """extruder is 1-indexed (1 = first filament slot).""" n = extruder if n < 3: return format(n * 4, 'X') # '4' or '8' return format(12 + (n - 3) * 16, '02X') # '0C', '1C', '2C', … ``` --- ## `Metadata/model_settings.config` Per-object settings. The `object id` must match the `` in `3D/3dmodel.model`. ```xml ``` The `extruder` value here sets the **base** filament for the object. Per-triangle overrides come from `paint_color`. --- ## `Metadata/project_settings.config` (optional) A large JSON file containing machine profile, filament profiles, and print settings. Bambu Studio saves the full machine gcode here. For third-party generators, the minimum useful subset is `filament_colour`: ```json { "filament_colour": ["#B4A082", "#468CD2", "#50A550", "#DCD7C8"] } ``` Order corresponds to filament slots 1–4. Bambu Studio uses these hex colours to display each painted region in the viewport before slicing. If the file is absent, Bambu Studio uses the colours from the loaded filament profiles instead. --- ## Observations and caveats - **Separate-object approach doesn't work for multi-colour.** Writing separate `` elements (one per colour) and using `` / `pindex` attributes causes Bambu Studio 2.5+ to load geometry but show all parts with the default (first) filament colour. The `paint_color` per-triangle approach is required. - **`requiredextensions="p"`** on the `` element of `3D/3dmodel.model` is needed for the Production Extension namespace (`xmlns:p`). Without it some validators reject the file, though Bambu Studio loads it either way. - **Vertex indices are 0-based** in `` elements (same as standard 3MF; OBJ uses 1-based). - **Coordinates are in millimetres** with the origin at the model's natural zero; Bambu Studio auto-centres on the print plate on import. - **The geometry object file can be inlined** directly into `3D/3dmodel.model` (skipping the `object_1.model` split and the `.rels` file), but Bambu Studio's own exporter always uses the split form, so the split form is the safer choice for compatibility. --- ## Unresolved: `project_settings.config` and the preset-name dialog Including `Metadata/project_settings.config` with a `filament_colour` array causes Bambu Studio to display the correct per-slot colours in the viewport. However it also triggers a **"customised filament or printer presets"** dialog on every import, and filament slots 2–4 are named `model.3mf` (the project filename) rather than something meaningful. ### Root cause Bambu Studio matches `filament_settings_id` entries against its installed preset database. When a preset name is not found it creates an embedded preset named after the source file. The first slot gets its name from `filament_settings_id[0]`; slots 2–4 appear to fall back to the filename regardless of what `filament_settings_id[1..3]` contains. Preset names are machine-specific (e.g. `"Bambu PLA Basic @BBL A1"`) so a third-party generator cannot supply correct names without knowing the user's exact machine model and installed preset database. ### Things tried that did not help | Attempt | Result | |---|---| | `filament_colour` only | Correct colours, dialog shows `—`, slots unnamed | | `filament_settings_id: [""]` | Slots named `model.3mf` | | `filament_settings_id: ["Generic PLA", ...]` | Slots named `model.3mf` | | `filament_settings_id: ["Terrain","Water","Parks","Roads"]` | Slot 1 named `Terrain(model.3mf)`, rest `model.3mf` | | `filament_settings_id: ["Terrology"×4]` | Slot 1 named `Terrology(model.3mf)`, rest `model.3mf` | ### Practical outcome The `paint_color` encoding is correct and colours display properly. The dialog and naming are cosmetic nuisances only. A future fix would require either: - Detecting the user's Bambu machine model and looking up the corresponding preset name, or - Bambu Lab exposing a machine-agnostic generic filament preset name that suppresses the dialog.