Model Formats¶
SceneView loads a model from a path, a URI or a byte buffer. The format is detected from
the bytes, not from the file name — a file shared into your app with no extension and a
application/octet-stream MIME type still opens.
| Format | Extensions | Where it works | Notes |
|---|---|---|---|
| glTF / GLB | .gltf, .glb |
Android · Web · Desktop · TV · Flutter · React Native | The native format across the SDK. Skeletal and morph animations, PBR materials, Draco and WebP textures. |
| USDZ / Reality | .usdz, .reality |
Apple (iOS · macOS · visionOS) | Loaded by RealityKit through SceneViewSwift. |
| 3MF | .3mf |
Android | Converted to GLB in memory. The parser lives in sceneview-core (Kotlin Multiplatform). |
3MF — the format AI print flows emit¶
Ask ChatGPT for a 3D print from a drawing and it hands back a .3mf: an OPC/ZIP package
whose 3D/3dmodel.model part is XML, with <vertices>, <triangles>, <components> and a
<build> plate, in millimetres and Z-up. It is what every slicer and every image-to-print
flow produces — and until SceneView read it, no Android app opened one in 3D, let alone in
AR.
There is no new API¶
ModelLoader sniffs the payload by its ZIP magic and converts it to GLB before the bytes
reach Filament, so every entry point you already use accepts a 3MF:
val modelLoader = rememberModelLoader(engine)
SceneView(modifier = Modifier.fillMaxSize(), engine = engine, modelLoader = modelLoader) {
rememberModelInstance(modelLoader, "print.3mf")?.let {
ModelNode(modelInstance = it, scaleToUnits = 1.0f)
}
}
A file shared in from another app works the same way — pass the content:// URI as a
string:
rememberModelInstance(modelLoader, uri.toString())?.let {
ModelNode(modelInstance = it, scaleToUnits = 1.0f)
}
A payload that is not a ZIP costs one four-byte comparison, so the check is free for the glTF path.
Receiving a file from another app¶
The Android demo declares ACTION_VIEW and ACTION_SEND filters for .3mf, .glb and
.gltf, so it appears in the app chooser and the share sheet
(#3510). Two things the platform forces,
if you do the same in your own app:
- Sniff the bytes, not the metadata. A
.3mfsent through the share sheet arrives withapplication/octet-streamas its type and with no queryable display name — both signals blank. A name-or-MIME check refuses a file the SDK reads perfectly. - Read the ClipData, not a bare
EXTRA_STREAM. A sender'sFLAG_GRANT_READ_URI_PERMISSIONcoversgetData()and the ClipData only; read the extra of a share that carries both andopenInputStreamreturnsnull.
samples/android-demo/src/main/java/io/github/sceneview/demo/OpenedModel.kt is the working
implementation.
What the conversion does¶
| Step | Why |
|---|---|
| Millimetres → metres | 3MF carries real-world size (unit="millimeter" unless stated otherwise). A 60 mm print is life-size in AR without a magic number. |
| Z-up → Y-up | 3MF uses the printer's build-plate axes; glTF and SceneView use Y-up. The part stands up instead of lying on its back. |
| Flat normals | 3MF stores no normals. Faces are de-indexed and given per-face normals — flat shading is what a printed part looks like, and a smoothed normal would round over the facets the slicer will extrude. |
| Colours → glTF materials | <basematerials> and the materials extension's <colorgroup> become one glTF material per colour, per object and per triangle. |
doubleSided |
Generated meshes are often inconsistently wound, and a one-sided print renders inside-out. |
Unrecognised 3MF extensions (slice, beamlattice, production) are skipped, never rejected — an unknown extension must not stop a print from being previewed.
Custom pipelines¶
The parser is pure Kotlin in sceneview-core — no java.util.zip, no XML library, no
expect/actual — so it runs on every Kotlin target:
if (ThreeMfLoader.isThreeMf(bytes)) {
val model = ThreeMfLoader.parse(bytes) // objects, meshes, build items
val glb = ThreeMfLoader.toGlb(bytes) // ready for any glTF consumer
}
Roadmap¶
The 3MF parser is deliberately dependency-free so the same shape carries the rest of the formats a print or scan pipeline emits. These are tracked and not yet shipped — check the issue before relying on one:
| Format | Issue |
|---|---|
| STL (binary + ASCII) | #3486 |
| PLY (mesh, vertex colours) | #3487 |
| OBJ + MTL | #3488 |
One ModelFormat entry point (supportedFormats, unit, typed errors) |
#3489 |
| Open-with for every format in the Android demo | #3490 |
Every format on the web (loadModel, /open, the view_3d_model widget) |
#3491 |
See also¶
llms.txt— the 3MF section in the machine-readable API reference- Nodes Reference — what to do with a model once it is loaded
- Quickstart — the shortest path to a rendered model