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 { 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 _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 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(); } }