buildScenes function

void buildScenes({
  1. required BuildInput buildInput,
  2. required BuildOutputBuilder buildOutput,
  3. List<String>? inputFilePaths,
  4. String outputDirectory = 'build/scenes/',
  5. String discoveryRoot = 'assets/',
  6. SceneAssetMode assetMode = SceneAssetMode.legacyOnly,
  7. bool compressTextures = false,
})

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,
        ),
      );
    }
  }
}