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.