fix:3的模型动态修改模型文件

This commit is contained in:
jingyun
2025-08-25 15:09:07 +08:00
parent 1c07d576d3
commit d16474784d
8 changed files with 346 additions and 121 deletions

View File

@@ -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),