Files
cup_edit/examples/flutter/animations/lib/model_viewer.dart
2025-08-25 15:09:07 +08:00

191 lines
5.2 KiB
Dart
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'dart:convert';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:path/path.dart' as p;
import 'package:path_provider/path_provider.dart';
import 'package:thermion_flutter/thermion_flutter.dart';
class LocalModelViewer extends StatefulWidget {
@override
_LocalModelViewerState createState() => _LocalModelViewerState();
}
class _LocalModelViewerState extends State<LocalModelViewer> {
ThermionViewer? _viewer;
ThermionAsset? _asset;
bool _isLoading = true;
bool _hasError = false;
String _statusMessage = "初始化中...";
@override
void initState() {
super.initState();
ThermionFlutterPlugin.createViewer().then((viewer) async {
_viewer = viewer;
_loadLocalModel(); // 在 viewer 初始化之后再加载模型
});
}
Future<void> _loadLocalModel() async {
try {
setState(() {
_isLoading = true;
_statusMessage = "获取本地模型路径...";
});
final supportDir = await getApplicationSupportDirectory();
final modelDir = p.join(supportDir.path, 'cup');
final modelPath = p.join(modelDir, 'model.gltf');
final modelFile = File(modelPath);
if (await modelFile.exists()) {
// 检查二进制文件
final binPath = p.join(modelDir, 'model.bin');
final binFile = File(binPath);
if (!await binFile.exists()) {
throw Exception("二进制文件不存在: $binPath");
}
// 检查文件大小
final binSize = await binFile.length();
if (binSize == 0) {
throw Exception("二进制文件为空");
}
// 检查纹理文件
final texturePath = p.join(modelDir, 'texture.jpg');
final textureFile = File(texturePath);
if (!await textureFile.exists()) {
throw Exception("纹理文件不存在: $texturePath");
}
setState(() {
_statusMessage = "读取和修改模型数据...";
});
// 读取GLTF文件内容
String gltfContent = await modelFile.readAsString();
// 修改纹理路径(去掉"./"前缀)
gltfContent = gltfContent.replaceAll('"./texture.jpg"', '"new.jpg"');
// 转换为字节数组
Uint8List gltfBytes = Uint8List.fromList(utf8.encode(gltfContent));
// 使用绝对路径作为资源URI
final resourceUri = "file://${Directory(modelDir).absolute.path}";
setState(() {
_statusMessage = "加载模型中...";
});
final asset = await _viewer?.loadGltfFromBuffer(
gltfBytes,
resourceUri: resourceUri,
addToScene: true,
);
setState(() {
_asset = asset;
_isLoading = false;
_statusMessage = "模型加载完成";
});
} else {
setState(() {
_isLoading = false;
_statusMessage = "本地模型文件不存在";
});
}
} catch (e) {
print('加载本地模型失败: $e');
setState(() {
_isLoading = false;
_hasError = true;
_statusMessage = "加载失败: $e";
});
}
}
@override
Widget build(BuildContext context) {
if (_isLoading) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CircularProgressIndicator(),
SizedBox(height: 16),
Text(_statusMessage),
],
),
);
}
if (_hasError || _viewer == null) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.error_outline, color: Colors.red, size: 48),
SizedBox(height: 16),
Text("加载3D模型失败"),
Text(_statusMessage, style: TextStyle(color: Colors.red)),
SizedBox(height: 16),
ElevatedButton(
onPressed: _loadLocalModel,
child: Text("重试"),
),
],
),
);
}
return Scaffold(
appBar: AppBar(
title: const Text('本地模型查看'),
),
body: ThermionListenerWidget(
inputHandler: DelegateInputHandler.fixedOrbit(_viewer!),
child: ThermionWidget(
viewer: _viewer!,
showFpsCounter: true, // 可选显示FPS计数器
),
),
);
// 使用ThermionWidget显示3D内容
return ThermionListenerWidget(
inputHandler: DelegateInputHandler.fixedOrbit(_viewer!),
child: ThermionWidget(
viewer: _viewer!,
showFpsCounter: true, // 可选显示FPS计数器
),
);
}
Future<void> deleteModelFile() async {
// 获取应用支持目录
final supportDir = await getApplicationSupportDirectory();
// 构建 model.gltf 的完整路径
final modelFile = File(p.join(supportDir.path, 'cup', 'model.gltf'));
// 检查文件是否存在,然后删除
if (await modelFile.exists()) {
await modelFile.delete();
print('model.gltf 已删除');
} else {
print('model.gltf 文件不存在');
}
}
@override
void dispose() {
// deleteModelFile();
// 清理资源
_viewer?.dispose();
super.dispose();
}
}