mesh3d
Reads 3D models in pure Dart and gives you back the parts: name, mesh and colour.
final model = readModel(bytes);
for (final part in model.parts) {
print('${part.name}: ${part.triangleCount} triangles, colour ${part.color}');
}
Formats: .3mf, .glb/.gltf, .obj (loose or in a .zip with
its .mtl) and .stl (binary or ASCII).
Why it exists
A 3D-printed pet bowl has two pieces, and the customer picks a colour for each one before buying. For that, a shop needs to know what the parts are — not just what the model looks like.
Nothing on pub.flutter-io.cn read .3mf at all, and the packages that read the other
formats are tied to a renderer: they load meshes straight into a 3D scene.
That serves whoever is about to draw. It does not serve whoever just needs to
know the file has a part called "Bowl" and another called "Base" — a server
validating an upload, a build step, a test.
Here, reading is separate from drawing. The same code runs in a Flutter app, on a headless server, and at a command line.
What you get
class ModelPart {
final String id; // stable identity within the file
final String name; // the label the authoring tool wrote
final Float32List vertices; // x, y, z, x, y, z…
final Uint32List indices; // three per triangle
final String? color; // '#RRGGBB', when the file carries one
}
Flat on purpose: it is the shape every renderer wants to receive, and building objects only to take them apart again would spend memory a phone does not have to spare.
Bind your settings to id, never to name — exporters repeat labels far more
often than you would expect.
The format comes from the content, never the extension
readModel looks at the file's signature. An extension is just what the sender
typed: .stl files turn up that are really OBJ, and .zip files that could be
either a 3MF or an OBJ with its .mtl alongside. Nothing here depends on a
filename, because a filename may simply not be available.
If you already know the format, call the reader directly: ThreeMfReader,
GlbReader, ObjReader, StlReader.
What it does not do, it tells you
Every limitation becomes a ModelWarning — a code, not a sentence, because a
library has no business deciding what language its caller's users read. Each
carries an English message for callers that do not translate.
| Situation | What happens |
|---|---|
| Any STL | One part. The format stores no parts, names or colours. |
OBJ without its .mtl |
Opens colourless, and says how to bring both. |
| glTF with Draco | Refused with an instruction. Decompressing needs a decoder about the size of this package. |
| glTF with textures | Comes out in the material's flat colour. |
| glTF buffer in a sidecar file | Skipped, with a note to export as .glb. |
3MF with components |
A part assembled from others is not resolved. |
Drawing something wrong in silence is the worst possible outcome in a product viewer: the customer approves a part that is not the one that will be printed.
Two things it levels across formats
Unit. glTF measures in metres by specification; 3MF in millimetres. Without levelling them, the same product exported to both would arrive a thousand times larger in one. Everything comes out in millimetres.
Colour. glTF stores base colour in linear light, while OBJ's Kd and 3MF's
colorgroup are already sRGB. Everything comes out as the #RRGGBB a person
would recognise — a dark forest green #2D6A4F arrives as #2D6A4F, not as
the mint that a lighting calculation would hand back.
Performance
Parsing is event-driven rather than tree-based. The largest file in the archive this was measured against holds 48,000 triangles — close to 300,000 numbers — and opens in 125 ms on an iPhone, in a debug build. Building a node tree only to throw it away is what makes a modest phone stutter.
Tests
Small versioned fixtures cover each format: colour per part, absence of colour, the pose a part was arranged in, a polygon becoming triangles, and every case the reader refuses.
The fixtures also carry their own fingerprints, because a second implementation
of this reader exists in Python — in the shop this package came from — and the
two have to agree on every part's id. Reading the same bytes is what makes
that agreement real instead of two people having written the same number in two
places.
Real customer models are not in the repository, and the suite that checks against them lives outside this package.
Libraries
- mesh3d
- Reads 3D models in pure Dart —
.3mf,.glb,.objand.stl— and returns their parts, each with a name, a mesh and a colour.