File size: 10,479 Bytes
727983f | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 | # 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
<?xml version="1.0" encoding="UTF-8"?>
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
<Default Extension="model" ContentType="application/vnd.ms-package.3dmanufacturing-3dmodel+xml"/>
<Default Extension="png" ContentType="image/png"/>
</Types>
```
---
## `_rels/.rels`
```xml
<?xml version="1.0" encoding="UTF-8"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
<Relationship Target="/3D/3dmodel.model" Id="rel-1"
Type="http://schemas.microsoft.com/3dmanufacturing/2013/01/3dmodel"/>
</Relationships>
```
---
## `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
<?xml version="1.0" encoding="UTF-8"?>
<model unit="millimeter" xml:lang="en-US"
xmlns="http://schemas.microsoft.com/3dmanufacturing/core/2015/02"
xmlns:BambuStudio="http://schemas.bambulab.com/package/2021"
xmlns:p="http://schemas.microsoft.com/3dmanufacturing/production/2015/06"
requiredextensions="p">
<metadata name="Application">BambuStudio-02.06.01.55</metadata>
<metadata name="BambuStudio:3mfVersion">1</metadata>
<!-- other optional metadata (Copyright, CreationDate, β¦) -->
<resources>
<object id="2" type="model">
<components>
<component p:path="/3D/Objects/object_1.model" objectid="1"
transform="1 0 0 0 1 0 0 0 1 0 0 0"/>
</components>
</object>
</resources>
<build>
<item objectid="2" printable="1"/>
</build>
</model>
```
### "From Bambu Lab" detection
Bambu Studio 2.5+ shows **"not from Bambu Lab, load geometry data only"** unless both of
these `<metadata>` elements are present in `3D/3dmodel.model`:
```xml
<metadata name="Application">BambuStudio-XX.XX.XX.XX</metadata>
<metadata name="BambuStudio:3mfVersion">1</metadata>
```
The exact version string in `Application` does not matter; only the prefix `BambuStudio-`
is checked.
---
## `3D/_rels/3dmodel.model.rels`
```xml
<?xml version="1.0" encoding="UTF-8"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
<Relationship Target="/3D/Objects/object_1.model" Id="rel-1"
Type="http://schemas.microsoft.com/3dmanufacturing/2013/01/3dmodel"/>
</Relationships>
```
---
## `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
<?xml version="1.0" encoding="UTF-8"?>
<model unit="millimeter" xml:lang="en-US"
xmlns="http://schemas.microsoft.com/3dmanufacturing/core/2015/02"
xmlns:BambuStudio="http://schemas.bambulab.com/package/2021"
xmlns:p="http://schemas.microsoft.com/3dmanufacturing/production/2015/06">
<metadata name="BambuStudio:3mfVersion">1</metadata>
<resources>
<object id="1" type="model">
<mesh>
<vertices>
<vertex x="0.0000" y="0.0000" z="-3.0000"/>
β¦
</vertices>
<triangles>
<triangle v1="0" v2="1" v3="2" paint_color="4"/>
<triangle v1="3" v2="4" v3="5" paint_color="1C"/>
β¦
</triangles>
</mesh>
</object>
</resources>
</model>
```
---
## `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 `<object id>` in `3D/3dmodel.model`.
```xml
<?xml version="1.0" encoding="UTF-8"?>
<config>
<object id="2">
<metadata key="name" value="my model"/>
<metadata key="extruder" value="1"/>
<metadata face_count="474884"/>
<part id="1" subtype="normal_part">
<metadata key="name" value="my model"/>
<metadata key="matrix" value="1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1"/>
<metadata key="extruder" value="1"/>
</part>
</object>
<plate>
<metadata key="plater_id" value="1"/>
<metadata key="locked" value="false"/>
</plate>
</config>
```
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 `<object>`
elements (one per colour) and using `<basematerials>` / `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 `<model>` 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 `<triangle>` 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.
|