convertFiles function

Future<ConversionResult> convertFiles(
  1. Map<String, Uint8List> files, {
  2. required ConversionTarget to,
  3. String? entry,
  4. String assetPrefix = '',
  5. ModelSettings models = const ModelSettings(),
  6. bool materials = true,
  7. bool bundle = true,
})

Converts files — one upload, or every file of an unpacked Unity, Godot or USD bundle, by its path inside the bundle — and returns the outputs to asks for, with the reports.

entry names the file to convert, by its key in files; null converts every file a directory walk of flutter3d convert would. Every other file is there to be found: the .meta files and models a Unity prefab names, a Godot scene's res:// paths (the bundle's root holds project.godot, or is taken as res://), a USD layer's references and textures.

No external program runs. FBX, .blend and a binary USD layer are reported with ConvertOutcome.missingTool and the reason. Paths in level documents start with assetPrefix.

One .f3d per input by default (bundle): the model with its lights and cameras, its materials, the .f3dmat programs, the level documents as prefabs, and every other file a scene names carried whole (see F3dDocument.programs, prefabs and files). ConversionTarget then picks among bundles, .f3dsplat trees and nothing else. With bundle: false the outputs are the sidecar layout of flutter3d convert --split: <name>.f3d, materials/*.fmat, *.f3dmat, textures/*, models/*.f3d and <name>.level.json.

A document reads only the files that came with it. A glTF buffer, an OBJ material library, a USD reference or texture that climbs out of the upload with .. or names an absolute path is refused and reported, never read.

Throws ArgumentError for a key that is absolute or climbs out with .., and for an entry that is not a key.

Implementation

Future<ConversionResult> convertFiles(
  Map<String, Uint8List> files, {
  required ConversionTarget to,
  String? entry,
  String assetPrefix = '',
  ModelSettings models = const ModelSettings(),
  bool materials = true,
  bool bundle = true,
}) async {
  for (final key in files.keys) {
    final parts = key.replaceAll(r'\', '/').split('/');
    if (key.startsWith('/') || key.contains(':') || parts.contains('..')) {
      throw ArgumentError.value(key, 'files', 'a path inside the bundle');
    }
  }
  if (entry != null && !files.containsKey(entry)) {
    throw ArgumentError.value(entry, 'entry', 'not one of the files');
  }
  // The readers resolve a prefab's model through `.meta` files and a
  // scene's `res://` paths on a directory tree, so the bundle is laid out
  // as one, privately, for the length of the call.
  final work = Directory.systemTemp.createTempSync('flutter3d_convert_');
  try {
    final input = Directory('${work.path}/in')..createSync();
    for (final MapEntry(:key, :value) in files.entries) {
      File('${input.path}/${key.replaceAll(r'\', '/')}')
        ..parent.createSync(recursive: true)
        ..writeAsBytesSync(value);
    }
    final inputs = entry != null
        ? <String>['${input.path}/$entry']
        : <String>[
            for (final key in (files.keys.toList()..sort()))
              if (isConvertibleFile('${input.path}/$key')) '${input.path}/$key',
          ];
    final plan = OutputPlan('${work.path}/out');
    final context = ConvertContext(
      plan: plan,
      assetPrefix: assetPrefix,
      models: models,
      writeMaterials: materials,
      externalTools: false,
      // The keys are checked above; this holds the references inside the
      // documents to the upload too.
      root: input.path,
    );
    final reports = await convertInputs(inputs, context);
    if (bundle) bundleOutputs(plan, reports);
    final prefix = '${input.path}/';
    return ConversionResult(
      <String, Uint8List>{
        for (final write in plan.files)
          if (to.keeps(write.path)) write.path: write.bytes,
      },
      <ConvertReport>[for (final report in reports) _rebased(report, prefix)],
    );
  } finally {
    work.deleteSync(recursive: true);
  }
}