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 reads 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;}You hand files over as bytes, not as a way to read them. That is on
purpose: 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. An
empty list 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,
and 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 one is listed in problems
on the way out. It is there because everything opens it.
You can’t write FBX, and that is deliberate. Reading one is worth the trouble because people have them, and Orblit does, through ufbx. Writing one means 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, and the model’s roots become
children of the entity’s node. Meshes, materials, textures, skins and
animations all come with it. That is what makes the export open properly
somewhere else instead of being 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, so it 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. A model with a skin or an animation is the exception: sharing those would have both copies bent and moved by the first one’s bones, so they are 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 a normal 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. 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 is 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 is visible and each
component as the component itself serialises it, and 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 is asserted in the test suite, not hoped for. Two consequences are 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 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 well as it can be — nodes become entities, and transforms,
lights and cameras come across — and everything 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. Somebody checks by hand that an Orblit export opens correctly in Blender. Nothing in CI opens Blender.
- Export is API-only. There is 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 is nowhere in the document for that yet.
