Exporting and importing scenes
A scene file is Orblit’s own format and it keeps everything. Exporting is for everything else: sending a scene to Blender, shipping one file instead of a folder, handing geometry to a tool that only speaks OBJ.
import 'dart:typed_data';
import 'package:orblit_scene/orblit_scene.dart';
SceneWritten export(SceneDocument scene, MaterialLibrary materials) { final written = scene.writeAs( SceneFormat.glb, name: 'old_town', materials: materials, files: const <String, Uint8List>{}, // the bytes of anything it points at );
for (final problem in written.problems) { print('not carried: $problem'); }
// written.files, in the order to save them: name and bytes each. return written;}files are handed over as bytes rather than as a way to read them,
deliberately: this package has no filesystem in it, and the same export runs
in the editor, in a cook step and in a browser. Only the caller knows which.
problems is what the format couldn’t hold, in the order it was met. Empty
is the ordinary case and means nothing was lost.
The three formats
Section titled “The three formats”| Keeps the tree | Files | For | |
|---|---|---|---|
SceneFormat.gltf |
yes | name.gltf + name.bin |
version control, reading in an editor |
SceneFormat.glb |
yes | name.glb |
shipping |
SceneFormat.obj |
no | name.obj + name.mtl |
tools that read nothing else |
files comes back in the order to save it in: the first names the export,
the rest are the sidecars it points at. SceneFormat.keepsScene is the same
distinction as a value, for a menu that needs to warn before OBJ.
OBJ is corners and faces in world space. Lights, cameras, the tree and every
component that isn’t geometry are gone, and each is listed in problems on
the way out. It’s there because everything opens it.
FBX is deliberately not a format you can write to. Reading one is worth the trouble because people have them, and Orblit does, through ufbx. Writing one is a proprietary format with no public specification, and everything that opens an FBX opens a glTF.
Models come along
Section titled “Models come along”An entity whose mesh names an imported model — MeshComponent(asset: …) — is
exported with that model’s glTF copied in whole, its roots becoming children
of the entity’s node. Meshes, materials, textures, skins and animations all
come with it. That’s what makes the export openable somewhere else rather
than a tree of empty nodes with paths in them.
It needs the bytes, which is what files is for, keyed by the project path
the scene names the model by. A model whose bytes weren’t supplied exports as
an empty node where the model goes, and says so in problems.
The bytes have to be a GLB. Only a model carrying its own buffer can be
copied into an export — a .gltf with a .bin beside it names something the
exporter has no way to follow, and is refused with a problem rather than
grafted half way.
A model placed twice is copied once. glTF already lets any number of nodes draw one mesh, so the second placement copies the nodes and nothing else — no bytes, no accessors — and a scene with two hundred of the same tree carries one tree. The exception is a model with a skin or an animation: sharing those would have both copies bent and moved by the first one’s bones, so they’re copied again in full.
Reading one back
Section titled “Reading one back”import 'dart:typed_data';
import 'package:orblit_scene/orblit_scene.dart';
SceneDocument import(Uint8List bytes) { final read = readSceneFrom(bytes, name: 'Old Town');
if (!read.wasWrittenHere) { // Someone else's glTF. Still a scene, with rather more caveats. } for (final problem in read.problems) { print(problem); }
return read.document;}Whether the bytes are a GLB or glTF JSON comes from the bytes, not from the
name. A .gltf that is really a GLB is an ordinary thing to be handed, and a
reader that trusts the extension fails on it with a JSON error nobody can act
on.
A .gltf points at its .bin by name. Pass those bytes in files, keyed by
the URI the document names them by — and note that a scene called
old town.gltf writes a buffer named old%20town.bin while the file on disk
has the space in it. readSceneFrom looks under both.
SceneFormatException is thrown only when there’s nothing to read at all:
bytes that are neither a GLB nor JSON. Everything short of that comes back in
problems, because a scene with one unreadable node is still a scene worth
opening.
The round trip is exact
Section titled “The round trip is exact”The exporter writes a full record of itself. Every node carries an
extras.orblit holding the entity’s id, whether it’s visible and each
component as the component itself serialises it; the scene carries its
settings the same way. So reading an Orblit export back is a read, not a
re-derivation from matrices and materials, and the document that comes out
encodes byte for byte to the one that went in.
import 'package:orblit_scene/orblit_scene.dart';
bool roundTrips(SceneDocument scene) { final read = readSceneFrom(scene.writeAs(SceneFormat.glb).first.bytes); return read.document.encode() == scene.encode();}That’s asserted in the test suite, not hoped for. Two consequences worth knowing:
- A component this build has never heard of survives. It comes back as an
UnknownComponentholding its JSON, so an older editor can open a scene a newer one saved, change something else and save it without quietly dropping what it didn’t understand. - Grafted models don’t become entities. The nodes copied in from an
imported model carry no
extras.orblit, and the importer skips them for exactly that reason. Without it every round trip would double the outliner.
wasWrittenHere is how you tell the two cases apart. A glTF from anywhere
else is read as best it can be — nodes become entities, transforms and
lights and cameras come across — and each thing that had to be guessed at or
dropped is listed in problems.
What isn’t there yet
Section titled “What isn’t there yet”- The Blender half isn’t automated. That an Orblit export opens correctly in Blender is checked by hand. Nothing in CI opens Blender.
- Export is API-only. There’s no menu item in the editor yet;
writeAsis the whole interface. - A second buffer isn’t read. Nothing Orblit writes has one. A foreign
glTF with several is read as far as its first, and the accessors that reach
past it say so in
problems. - Animation on an Orblit entity isn’t exported. A grafted model brings its own animation with it; an entity animated by the scene doesn’t, because there’s nowhere in the document for that yet.
