flutter3d
Showcase Changelog 38 packages API reference

Export, read back, compare: the source

since 0.7.0 Formats and I/O

Regions the guide quotes: checked (line 37), validate (line 45), report (line 67), check (line 96).

1/// Write, read back, and compare: `ExportReport` and `validateGltfExport`.
2///
3/// Quoted by `export_validate.md` and shown whole in the Source tab.
4library;
5
6import 'package:flutter/widgets.dart';
7import 'package:flutter3d/flutter3d.dart';
8import 'package:flutter3d_showcase/src/demo/demo.dart';
9import 'package:vector_math/vector_math.dart';
11final class ExportValidateDemo extends ShowcaseDemo {
12 late final ExportReport _glb;
13 late final ExportReport _obj;
14 late final ExportReport _stl;
15 late final List<String> _boundsProblems;
17 final _document = PlainModelDocument(
18 surfaces: <ModelSurface>[
19 ModelSurface(
20 mesh: CuboidShape(size: Vector3.all(1.0)).build(),
21 materialIndex: 0,
22 ),
23 ],
24 materials: <SurfaceMaterial>[
25 SurfaceMaterial(baseColor: Vector4(0.5, 0.7, 0.9, 1.0)),
26 ],
27 // `GltfWriter` places a surface through the node graph alone, never
28 // through its own transform, so a document meant to be written as glTF
29 // needs at least one node naming it.
30 nodes: <ModelNode>[
31 ModelNode(surfaces: <int>[0]),
32 ],
33 );
35 @override
36 Future<void> prepare(DemoContext context) async {
37 // `exportChecked` writes, reads the result back through the same writer
38 // and compares. A binary format like GLB is held to a tolerance of
39 // `0.0`; OBJ, a decimal text format, rounds on the way out by design and
40 // is checked against `ObjWriter.decimals` own precision instead.
41 _glb = await exportToGlb(_document);
42 _obj = await exportToObj(_document);
43 _stl = await exportToStl(_document);
45 // `compareModelDocuments` proves the geometry round-tripped; it says
46 // nothing about whether the file's own declared bounds match the data,
47 // which is a writer's own bookkeeping and not part of a document at
48 // all. `validateGltfExport` checks that separately, straight off the
49 // bytes.
50 _boundsProblems = await validateGltfExport(_glb.files.values.first);
51 }
53 @override
54 Scene build(DemoContext context) => Scene()
55 ..add(
56 MeshNode(
57 DeviceMesh.upload(context.device, _document.surfaces.single.mesh),
58 Material(baseColor: Vector4(0.5, 0.7, 0.9, 1.0)),
59 name: 'cube',
60 ),
61 )
62 ..add(
63 LightNode(name: 'sun', intensity: 3.0)
64 ..setLocalForward(Vector3(-0.4, -1.0, -0.3)),
65 );
67 String _line(String name, ExportReport report) =>
68 '$name: ${report.isClean ? 'clean' : 'not clean'}, '
69 '${report.writerWarnings.length} warning(s), '
70 '${report.differences.length} difference(s)';
72 String _report() =>
73 '${_line('glb', _glb)}\n'
74 '${_line('obj', _obj)}\n'
75 '${_line('stl', _stl)}\n'
76 'accessor bounds: ${_boundsProblems.isEmpty ? 'match the data' : _boundsProblems.join('\n')}';
78 @override
79 Widget? customBody(BuildContext buildContext, DemoContext context) =>
80 Container(
81 color: const Color(0xFF14161A),
82 padding: const EdgeInsets.all(24),
83 alignment: Alignment.topLeft,
84 child: DefaultTextStyle(
85 style: const TextStyle(
86 color: Color(0xFFE8E8EC),
87 fontSize: 16,
88 fontFamily: 'monospace',
89 ),
90 child: Text(_report()),
91 ),
92 );
94 @override
95 void verify(Scene scene, FrameResult frame) {
96 if (!_glb.isClean) {
97 throw StateError('the GLB round trip was not clean: $_glb');
98 }
99 if (_boundsProblems.isNotEmpty) {
100 throw StateError('the GLB declared the wrong bounds: $_boundsProblems');
101 }
102 if (frame.drawCalls < 1) {
103 throw StateError('the cube was not drawn');
104 }
105 }