Files
cup_edit/examples/flutter/animations/lib/model_viewer.dart
jingyun 1c07d576d3 init
2025-08-22 15:14:09 +08:00

173 lines
4.6 KiB
Dart
Raw 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:io';
import 'package:animations/utils/binary_manager.dart';
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();
_initializeViewerAndLoadModel();
}
Future<void> _initializeViewerAndLoadModel() async {
try {
// 获取应用支持目录并构建模型文件路径
final supportDir = await getApplicationSupportDirectory();
final modelFile = File(p.join(supportDir.path, 'cup', 'model.gltf'));
// 检查文件是否存在
if (!await modelFile.exists()) {
await BinaryManager.migrate3DModels();
await Future.delayed(const Duration(milliseconds: 3000));
// throw Exception("模型文件不存在于路径: ${modelFile.path}");
}
setState(() {
_statusMessage = "创建3D查看器...";
});
// 创建ThermionViewer实例
_viewer = await ThermionFlutterPlugin.createViewer();
setState(() {
_statusMessage = "获取模型路径...";
});
setState(() {
_statusMessage = "加载环境光和天空盒...";
});
// 加载环境光和天空盒确保在pubspec.yaml中注册
await _viewer!.loadSkybox("assets/default_env_skybox.ktx");
await _viewer!.loadIbl("assets/default_env_ibl.ktx");
setState(() {
_statusMessage = "加载3D模型...";
});
// 加载glTF模型 - 使用文件URI格式
_asset = await _viewer!.loadGltf("file://${modelFile.path}");
// 可选:将模型缩放到单位立方体
await _asset!.transformToUnitCube();
// 配置场景
final camera = await _viewer!.getActiveCamera();
await camera.lookAt(Vector3(0, 0, 6));
// 开始渲染
await _viewer!.setRendering(true);
setState(() {
_isLoading = false;
_hasError = 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: _initializeViewerAndLoadModel,
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();
}
}