fix:3的模型动态修改模型文件
This commit is contained in:
@@ -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),
|
||||
|
||||
Reference in New Issue
Block a user