191 lines
5.2 KiB
Dart
191 lines
5.2 KiB
Dart
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();
|
|
}
|
|
}
|