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, .obj and .stl — and returns their parts, each with a name, a mesh and a colour.