flutter3d
Showcase Changelog 38 packages API reference

Export, read back, compare

since 0.7.0 Formats and I/O

A writer that draws a nice picture in a viewer proves nothing about whether it round-trips. exportChecked writes a document, reads the result straight back through the matching loader, and reports what changed. This page runs it against three formats at once.

Step 1: Write, read back, and compare #

exportToGlb, exportToObj and exportToStl are all the same function, exportChecked, with a writer already chosen. Each answers an ExportReport: the files it wrote, what the writer already knew it could not carry, and what compareModelDocuments found reading the file back.

// `exportChecked` writes, reads the result back through the same writer
// and compares. A binary format like GLB is held to a tolerance of
// `0.0`; OBJ, a decimal text format, rounds on the way out by design and
// is checked against `ObjWriter.decimals` own precision instead.
_glb = await exportToGlb(_document);
_obj = await exportToObj(_document);
_stl = await exportToStl(_document);

Note. A clean report with warnings in it is a writer working as documented, dropping only what it already said it would. Warnings with more differences than they explain is a writer with a real bug.

Step 2: Check the file's own bookkeeping #

Reading a file back proves the geometry survived, but a glTF accessor also declares its own min/max, and a reader trusts that declaration rather than recomputing it from the data. validateGltfExport checks the one thing every reader silently relies on a writer to get right.

// `compareModelDocuments` proves the geometry round-tripped; it says
// nothing about whether the file's own declared bounds match the data,
// which is a writer's own bookkeeping and not part of a document at
// all. `validateGltfExport` checks that separately, straight off the
// bytes.
_boundsProblems = await validateGltfExport(_glb.files.values.first);

Step 3: Put the numbers where they can be read #

Three formats, three reports. The page prints each one as a short line: clean or not, how many warnings, how many differences.

String _line(String name, ExportReport report) =>
    '$name: ${report.isClean ? 'clean' : 'not clean'}, '
    '${report.writerWarnings.length} warning(s), '
    '${report.differences.length} difference(s)';

String _report() =>
    '${_line('glb', _glb)}\n'
    '${_line('obj', _obj)}\n'
    '${_line('stl', _stl)}\n'
    'accessor bounds: ${_boundsProblems.isEmpty ? 'match the data' : _boundsProblems.join('\n')}';

Step 4: Check the claim this page makes #

A cube is about as simple as a document gets, so this page's own claim is strict: the GLB round trip has to be clean, and its declared bounds have to match its data.

if (!_glb.isClean) {
  throw StateError('the GLB round trip was not clean: $_glb');
}
if (_boundsProblems.isNotEmpty) {
  throw StateError('the GLB declared the wrong bounds: $_boundsProblems');
}
if (frame.drawCalls < 1) {
  throw StateError('the cube was not drawn');
}