fix:3的模型动态修改模型文件
This commit is contained in:
BIN
examples/assets/cup/new.jpg
Normal file
BIN
examples/assets/cup/new.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 720 KiB |
BIN
examples/assets/image/texture.jpg
Normal file
BIN
examples/assets/image/texture.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.2 MiB |
114
examples/flutter/animations/lib/cup.dart
Normal file
114
examples/flutter/animations/lib/cup.dart
Normal file
@@ -0,0 +1,114 @@
|
||||
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 Cup extends StatefulWidget {
|
||||
const Cup({super.key});
|
||||
|
||||
@override
|
||||
State<Cup> createState() => _CupState();
|
||||
}
|
||||
|
||||
class _CupState extends State<Cup> {
|
||||
bool _isLoading = true;
|
||||
String? _modelPath, _modelUri;
|
||||
String _statusMessage = "正在加载本地模型...";
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadLocalModel();
|
||||
}
|
||||
|
||||
// 从应用支持目录加载模型
|
||||
Future<void> _loadLocalModel() async {
|
||||
try {
|
||||
setState(() {
|
||||
_statusMessage = "获取本地模型路径...";
|
||||
});
|
||||
|
||||
final supportDir = await getApplicationSupportDirectory();
|
||||
final modelFile = File(p.join(supportDir.path, 'cup', 'model.gltf'));
|
||||
|
||||
String modelPath =
|
||||
File(p.join(supportDir.path, 'cup', 'model.gltf')).absolute.path;
|
||||
final cupDir = Directory('${supportDir.path}/cup');
|
||||
final modelUri = File('${cupDir.path}/model.gltf').uri.toString();
|
||||
print('加载本地模modelPath: $modelPath');
|
||||
print('加载本地模modelUri: $modelUri');
|
||||
|
||||
if (await modelFile.exists()) {
|
||||
setState(() {
|
||||
_modelPath = modelPath;
|
||||
_modelUri = modelUri;
|
||||
|
||||
_isLoading = false;
|
||||
_statusMessage = "本地模型加载完成";
|
||||
});
|
||||
} else {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
_statusMessage = "本地模型文件不存在";
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
print('加载本地模型失败: $e');
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
_statusMessage = "加载失败: $e";
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('本地模型查看'),
|
||||
),
|
||||
body: _isLoading
|
||||
? Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const CircularProgressIndicator(),
|
||||
const SizedBox(height: 16),
|
||||
Text(_statusMessage),
|
||||
],
|
||||
),
|
||||
)
|
||||
: _modelPath == null
|
||||
? Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(Icons.error, size: 48, color: Colors.red),
|
||||
const SizedBox(height: 16),
|
||||
Text(_statusMessage),
|
||||
const SizedBox(height: 16),
|
||||
ElevatedButton(
|
||||
onPressed: _loadLocalModel,
|
||||
child: const Text('重试'),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
: ViewerWidget(
|
||||
// assetPath: "file://$_modelPath",
|
||||
assetPath: _modelUri,
|
||||
skyboxPath: "assets/default_env_skybox.ktx",
|
||||
iblPath: "assets/default_env_ibl.ktx",
|
||||
transformToUnitCube: true,
|
||||
initialCameraPosition: Vector3(0, 0, 6),
|
||||
background: Colors.grey[200],
|
||||
manipulatorType: ManipulatorType.ORBIT,
|
||||
initial: const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ 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';
|
||||
// import 'package:thermion_dart/src/viewer/src/thermion_viewer_base.dart';
|
||||
|
||||
class CupPage1 extends StatelessWidget {
|
||||
const CupPage1({super.key});
|
||||
@@ -42,7 +43,7 @@ class CupPage extends StatefulWidget {
|
||||
|
||||
class _CupPageState extends State<CupPage> {
|
||||
bool _isLoading = true;
|
||||
String? _modelPath;
|
||||
String? _modelPath, modelUri;
|
||||
String _statusMessage = "正在加载本地模型...";
|
||||
|
||||
@override
|
||||
@@ -61,9 +62,17 @@ class _CupPageState extends State<CupPage> {
|
||||
final supportDir = await getApplicationSupportDirectory();
|
||||
final modelFile = File(p.join(supportDir.path, 'cup', 'model.gltf'));
|
||||
|
||||
String modelPath =
|
||||
File(p.join(supportDir.path, 'cup', 'model.gltf')).absolute.path;
|
||||
final cupDir = Directory('${supportDir.path}/cup');
|
||||
final modelUri = File('${cupDir.path}/model.gltf').uri;
|
||||
print('加载本地模modelPath: $modelPath');
|
||||
print('加载本地模modelUri: $modelUri');
|
||||
|
||||
if (await modelFile.exists()) {
|
||||
setState(() {
|
||||
_modelPath = modelFile.path;
|
||||
_modelPath = modelPath;
|
||||
|
||||
_isLoading = false;
|
||||
_statusMessage = "本地模型加载完成";
|
||||
});
|
||||
@@ -117,6 +126,7 @@ class _CupPageState extends State<CupPage> {
|
||||
)
|
||||
: ViewerWidget(
|
||||
assetPath: "file://$_modelPath",
|
||||
// assetPath: modelUri,
|
||||
skyboxPath: "assets/default_env_skybox.ktx",
|
||||
iblPath: "assets/default_env_ibl.ktx",
|
||||
transformToUnitCube: true,
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:animations/utils/binary_manager.dart';
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
|
||||
import 'model_viewer.dart';
|
||||
import 'cup.dart';
|
||||
|
||||
Future<void> main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
@@ -31,7 +34,7 @@ class MyApp extends StatelessWidget {
|
||||
primarySwatch: Colors.blue,
|
||||
visualDensity: VisualDensity.adaptivePlatformDensity,
|
||||
),
|
||||
home: const ModelViewerWithTextureReplace(),
|
||||
home: ModelViewerWithTextureReplace(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -52,11 +55,11 @@ class _ModelViewerWithTextureReplaceState
|
||||
int _keyValue = 0;
|
||||
String _statusMessage = "正在加载模型...";
|
||||
bool _isReplacingTexture = false;
|
||||
File? _selectedImage;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
BinaryManager.readLocalJson();
|
||||
_load3DModels();
|
||||
// _loadModel();
|
||||
}
|
||||
@@ -117,69 +120,101 @@ class _ModelViewerWithTextureReplaceState
|
||||
});
|
||||
|
||||
try {
|
||||
// 使用FilePicker选择文件
|
||||
// 选择图片文件
|
||||
FilePickerResult? result = await FilePicker.platform.pickFiles(
|
||||
type: FileType.image,
|
||||
allowMultiple: false,
|
||||
);
|
||||
|
||||
if (result != null &&
|
||||
result.files.isNotEmpty &&
|
||||
result.files.single.path != null) {
|
||||
setState(() {
|
||||
_statusMessage = "正在应用新纹理...";
|
||||
});
|
||||
|
||||
// 获取选中的文件
|
||||
PlatformFile file = result.files.first;
|
||||
File newTexture = File(file.path!);
|
||||
|
||||
// 检查文件是否存在
|
||||
if (await newTexture.exists()) {
|
||||
// 先备份旧纹理文件(可选)
|
||||
final backupTexturePath = '${_texturePath!}.backup';
|
||||
final oldTexture = File(_texturePath!);
|
||||
if (await oldTexture.exists()) {
|
||||
await oldTexture.copy(backupTexturePath);
|
||||
}
|
||||
|
||||
// 复制选中的图片到纹理路径
|
||||
await newTexture.copy(_texturePath!);
|
||||
|
||||
// 给渲染器一点时间来处理文件变化
|
||||
await Future.delayed(const Duration(milliseconds: 3000));
|
||||
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => LocalModelViewer(),
|
||||
),
|
||||
);
|
||||
|
||||
// // 强制重建ViewerWidget以应用新纹理
|
||||
// setState(() {
|
||||
// _keyValue++;
|
||||
// _statusMessage = "纹理已更新";
|
||||
// });
|
||||
|
||||
// ScaffoldMessenger.of(context).showSnackBar(
|
||||
// const SnackBar(content: Text('纹理已成功更新')),
|
||||
// );
|
||||
} else {
|
||||
setState(() {
|
||||
_statusMessage = "文件不存在";
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (result == null ||
|
||||
result.files.isEmpty ||
|
||||
result.files.single.path == null) {
|
||||
setState(() {
|
||||
_statusMessage = "取消选择";
|
||||
});
|
||||
return;
|
||||
}
|
||||
_selectedImage = File(result.files.single.path!);
|
||||
|
||||
final selectedFile = result.files.single;
|
||||
final fileExtension = p.extension(selectedFile.path!).toLowerCase();
|
||||
|
||||
// 可选:仅允许特定图片格式
|
||||
if (!['.jpg', '.jpeg', '.png'].contains(fileExtension)) {
|
||||
setState(() {
|
||||
_statusMessage = "仅支持 JPG / PNG 格式纹理";
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
final newTexture = File(selectedFile.path!);
|
||||
final supportDir = await getApplicationSupportDirectory();
|
||||
final modelDir = p.join(supportDir.path, 'cup');
|
||||
final modelPath = p.join(modelDir, 'model.gltf');
|
||||
final modelFile = File(modelPath);
|
||||
|
||||
if (!await newTexture.exists()) {
|
||||
setState(() {
|
||||
_statusMessage = "所选纹理文件不存在";
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 复制新纹理为固定名 new.jpg 到模型目录
|
||||
final newTexturePath = p.join(modelDir, 'new.jpg');
|
||||
await newTexture.copy(newTexturePath);
|
||||
_texturePath = newTexturePath;
|
||||
|
||||
// 修改 GLTF 文件
|
||||
if (await modelFile.exists()) {
|
||||
String gltfContent = await modelFile.readAsString();
|
||||
|
||||
// ✅ 修复 GLTF 中非法负号格式(例如 "- 0.123" => "-0.123")
|
||||
gltfContent = gltfContent.replaceAllMapped(
|
||||
RegExp(r'-\s+(\d+(\.\d+)?)'),
|
||||
(match) => '-${match.group(1)}',
|
||||
);
|
||||
|
||||
final gltfJson = jsonDecode(gltfContent);
|
||||
|
||||
// 安全修改 images[0].uri
|
||||
if (gltfJson['images'] != null && gltfJson['images'].isNotEmpty) {
|
||||
gltfJson['images'][0]['uri'] = './new.jpg';
|
||||
}
|
||||
|
||||
// 可选:修改 textures[0].name
|
||||
if (gltfJson['textures'] != null && gltfJson['textures'].isNotEmpty) {
|
||||
gltfJson['textures'][0]['name'] = 'new.jpg';
|
||||
}
|
||||
|
||||
// 写回更新后的内容
|
||||
final updatedContent =
|
||||
const JsonEncoder.withIndent(' ').convert(gltfJson);
|
||||
await modelFile.writeAsString(updatedContent);
|
||||
|
||||
print('GLTF文件已成功更新');
|
||||
|
||||
// 重建模型视图
|
||||
setState(() {
|
||||
_keyValue++;
|
||||
_statusMessage = "纹理已更新为 new.jpg";
|
||||
});
|
||||
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('纹理已成功更新为 new.jpg')),
|
||||
);
|
||||
} else {
|
||||
print('GLTF文件不存在: $modelPath');
|
||||
setState(() {
|
||||
_statusMessage = "GLTF文件不存在";
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
print('更换纹理失败: $e');
|
||||
setState(() {
|
||||
_statusMessage = "更换纹理失败: $e";
|
||||
});
|
||||
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('更换纹理失败: $e')),
|
||||
);
|
||||
@@ -192,6 +227,15 @@ class _ModelViewerWithTextureReplaceState
|
||||
|
||||
// 重置纹理到原始状态
|
||||
Future<void> _resetTexture() async {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => Cup(),
|
||||
// builder: (context) => CupPage(),
|
||||
),
|
||||
);
|
||||
return;
|
||||
|
||||
if (_isReplacingTexture || _texturePath == null) return;
|
||||
|
||||
setState(() {
|
||||
@@ -285,6 +329,22 @@ class _ModelViewerWithTextureReplaceState
|
||||
)
|
||||
: Column(
|
||||
children: [
|
||||
if (_selectedImage != null)
|
||||
Container(
|
||||
width: 300,
|
||||
height: 300,
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: Colors.blue, width: 2),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
child: Image.file(
|
||||
_selectedImage!,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
),
|
||||
// Expanded(
|
||||
// child: ViewerWidget(
|
||||
// key: ValueKey(_keyValue),
|
||||
@@ -317,21 +377,10 @@ class _ModelViewerWithTextureReplaceState
|
||||
),
|
||||
ElevatedButton.icon(
|
||||
icon: const Icon(Icons.refresh),
|
||||
label: const Text('重置纹理'),
|
||||
label: const Text('3d查看'),
|
||||
onPressed:
|
||||
_isReplacingTexture ? null : _resetTexture,
|
||||
),
|
||||
ElevatedButton.icon(
|
||||
icon: const Icon(Icons.camera),
|
||||
label: const Text('重置视图'),
|
||||
onPressed: _isReplacingTexture
|
||||
? null
|
||||
: () {
|
||||
setState(() {
|
||||
_keyValue++;
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'dart:convert';
|
||||
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';
|
||||
@@ -21,66 +21,84 @@ class _LocalModelViewerState extends State<LocalModelViewer> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
_initializeViewerAndLoadModel();
|
||||
ThermionFlutterPlugin.createViewer().then((viewer) async {
|
||||
_viewer = viewer;
|
||||
_loadLocalModel(); // 在 viewer 初始化之后再加载模型
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _initializeViewerAndLoadModel() async {
|
||||
Future<void> _loadLocalModel() async {
|
||||
try {
|
||||
// 获取应用支持目录并构建模型文件路径
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
_statusMessage = "获取本地模型路径...";
|
||||
});
|
||||
|
||||
final supportDir = await getApplicationSupportDirectory();
|
||||
final modelFile = File(p.join(supportDir.path, 'cup', 'model.gltf'));
|
||||
final modelDir = p.join(supportDir.path, 'cup');
|
||||
final modelPath = p.join(modelDir, 'model.gltf');
|
||||
final modelFile = File(modelPath);
|
||||
|
||||
// 检查文件是否存在
|
||||
if (!await modelFile.exists()) {
|
||||
await BinaryManager.migrate3DModels();
|
||||
await Future.delayed(const Duration(milliseconds: 3000));
|
||||
if (await modelFile.exists()) {
|
||||
// 检查二进制文件
|
||||
final binPath = p.join(modelDir, 'model.bin');
|
||||
final binFile = File(binPath);
|
||||
if (!await binFile.exists()) {
|
||||
throw Exception("二进制文件不存在: $binPath");
|
||||
}
|
||||
|
||||
// throw Exception("模型文件不存在于路径: ${modelFile.path}");
|
||||
// 检查文件大小
|
||||
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 = "本地模型文件不存在";
|
||||
});
|
||||
}
|
||||
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");
|
||||
print('加载本地模型失败: $e');
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
_hasError = true;
|
||||
@@ -115,7 +133,7 @@ class _LocalModelViewerState extends State<LocalModelViewer> {
|
||||
Text(_statusMessage, style: TextStyle(color: Colors.red)),
|
||||
SizedBox(height: 16),
|
||||
ElevatedButton(
|
||||
onPressed: _initializeViewerAndLoadModel,
|
||||
onPressed: _loadLocalModel,
|
||||
child: Text("重试"),
|
||||
),
|
||||
],
|
||||
@@ -164,7 +182,7 @@ class _LocalModelViewerState extends State<LocalModelViewer> {
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
deleteModelFile();
|
||||
// deleteModelFile();
|
||||
// 清理资源
|
||||
_viewer?.dispose();
|
||||
super.dispose();
|
||||
|
||||
@@ -114,10 +114,19 @@ class BinaryManager {
|
||||
// 迁移所有3D模型文件
|
||||
for (final modelFile in model3dFiles) {
|
||||
try {
|
||||
// if (modelFile == 'model.gltf') {
|
||||
// // 对于GLTF文件,我们需要修改内容
|
||||
// await _extractAndModifyGltfAsset(
|
||||
// 'assets/cup/$modelFile',
|
||||
// p.join(models3dDir.path, modelFile),
|
||||
// );
|
||||
// } else {
|
||||
// 对于其他文件,直接复制
|
||||
await _extractAsset(
|
||||
'assets/cup/$modelFile',
|
||||
p.join(models3dDir.path, modelFile),
|
||||
);
|
||||
// }
|
||||
} catch (e) {
|
||||
print('迁移3D模型文件失败 $modelFile: $e');
|
||||
// 继续迁移其他模型文件
|
||||
@@ -127,6 +136,30 @@ class BinaryManager {
|
||||
print('3D模型文件迁移完成');
|
||||
}
|
||||
|
||||
static Future<void> _extractAndModifyGltfAsset(
|
||||
String assetPath, String targetPath) async {
|
||||
try {
|
||||
// 读取资源文件内容
|
||||
final byteData = await rootBundle.load(assetPath);
|
||||
final buffer = byteData.buffer;
|
||||
final gltfContent = utf8.decode(
|
||||
buffer.asUint8List(byteData.offsetInBytes, byteData.lengthInBytes));
|
||||
|
||||
// 修改纹理路径(去掉"./"前缀)
|
||||
final modifiedGltfContent =
|
||||
gltfContent.replaceAll('"./texture.jpg"', '"texture.jpg"');
|
||||
|
||||
// 写入修改后的内容到目标文件
|
||||
final file = File(targetPath);
|
||||
await file.writeAsBytes(utf8.encode(modifiedGltfContent));
|
||||
|
||||
print('已修改并迁移: $assetPath -> $targetPath');
|
||||
} catch (e) {
|
||||
print('提取并修改GLTF资源失败: $e');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
static Future<void> _migrateShellScript() async {
|
||||
print('开始迁移脚本文件...');
|
||||
|
||||
|
||||
@@ -47,6 +47,7 @@ flutter:
|
||||
- assets/BusterDrone/
|
||||
- assets/BusterDrone/textures/
|
||||
- assets/cup/
|
||||
- assets/image/
|
||||
- assets/bin/mac/
|
||||
- assets/bin/windows/
|
||||
- assets/models/
|
||||
|
||||
Reference in New Issue
Block a user