buildScenes function
void
buildScenes({})
Converts glTF (.glb) source assets to flutter_scene's .fsceneb package
format and writes the result into outputDirectory (resolved relative to
BuildInput.packageRoot).
Call this from a consuming app's hook/build.dart:
import 'package:hooks/hooks.dart';
import 'package:flutter_scene/build_hooks.dart';
void main(List<String> args) {
build(args, (config, output) async {
buildScenes(
buildInput: config,
buildOutput: output,
assetMode: SceneAssetMode.dataAssetsIfAvailable,
);
});
}
Load the result by source path with loadScene. When inputFilePaths is
omitted, every .glb under discoveryRoot (default assets/, relative to
the package root) is discovered; each generated .fsceneb is written under
outputDirectory and, in a DataAssets mode, registered as a DataAsset (key
packages/<package>/flutter_scene/scene/<name>.fsceneb); the source .glb
is declared as a build dependency so re-exporting it retriggers the build
(and hot reload). Conversion runs in-process (no subprocess, no native
binary).
Implementation
void buildScenes({
required BuildInput buildInput,
required BuildOutputBuilder buildOutput,
List<String>? inputFilePaths,
String outputDirectory = 'build/scenes/',
String discoveryRoot = 'assets/',
SceneAssetMode assetMode = SceneAssetMode.legacyOnly,
bool compressTextures = false,
}) {
final dataAssetsAvailable = buildInput.config.buildDataAssets;
if (assetMode == SceneAssetMode.dataAssetsRequired && !dataAssetsAvailable) {
throw UnsupportedError(_dataAssetsUnavailableMessage);
}
final emitDataAssets =
assetMode != SceneAssetMode.legacyOnly && dataAssetsAvailable;
final packageRoot = buildInput.packageRoot;
final inputs =
inputFilePaths ??
discoverGlbSources(packageRoot, discoveryRoot: discoveryRoot);
if (inputs.isEmpty) {
return;
}
final scenesRoot = packageRoot.resolve(outputDirectory);
for (final inputFilePath in inputs) {
if (!inputFilePath.endsWith('.glb')) {
throw Exception(
'Input file must be a .glb file. Given file path: $inputFilePath',
);
}
if (inputFilePath.startsWith('../') || inputFilePath.contains('/../')) {
throw Exception(
'Scene source must be inside the package: $inputFilePath. Place it '
'under the package (for example in assets/), using a symlink if needed.',
);
}
final relativeScenePath =
'${inputFilePath.substring(0, inputFilePath.length - '.glb'.length)}'
'.fsceneb';
final outputSceneUri = scenesRoot.resolve(relativeScenePath);
Directory.fromUri(outputSceneUri.resolve('.')).createSync(recursive: true);
// Skip the conversion when the source and settings are unchanged since
// the output was produced, so a hook rerun for an unrelated edit does not
// re-import every scene. Set FLUTTER_SCENE_DISABLE_BUILD_CACHE to always
// convert.
final sourceHash = contentHash(
File(packageRoot.resolve(inputFilePath).toFilePath()).readAsBytesSync(),
);
final stamp =
'rev=$buildCacheRevision scene compress=$compressTextures '
'src=$sourceHash';
final stampFile = File('${outputSceneUri.toFilePath()}.inputs');
if (!isBuildCacheFresh(stampFile, stamp, [
File(outputSceneUri.toFilePath()),
])) {
importGltfToFsceneb(
inputFilePath,
outputSceneUri.toFilePath(),
workingDirectory: packageRoot.toFilePath(),
compressTextures: compressTextures,
);
stampFile.writeAsStringSync(stamp);
}
buildOutput.dependencies.add(packageRoot.resolve(inputFilePath));
if (emitDataAssets) {
buildOutput.assets.data.add(
DataAsset(
package: buildInput.packageName,
name: sceneDataAssetName(relativeScenePath),
file: outputSceneUri,
),
);
}
}
}