convertFiles function
- Map<
String, Uint8List> files, { - required ConversionTarget to,
- String? entry,
- String assetPrefix = '',
- ModelSettings models = const ModelSettings(),
- bool materials = true,
- 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);
}
}