init
This commit is contained in:
@@ -21,6 +21,6 @@
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.0</string>
|
||||
<key>MinimumOSVersion</key>
|
||||
<string>12.0</string>
|
||||
<string>13.0</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -1,22 +1,88 @@
|
||||
PODS:
|
||||
- DKImagePickerController/Core (4.3.9):
|
||||
- DKImagePickerController/ImageDataManager
|
||||
- DKImagePickerController/Resource
|
||||
- DKImagePickerController/ImageDataManager (4.3.9)
|
||||
- DKImagePickerController/PhotoGallery (4.3.9):
|
||||
- DKImagePickerController/Core
|
||||
- DKPhotoGallery
|
||||
- DKImagePickerController/Resource (4.3.9)
|
||||
- DKPhotoGallery (0.0.19):
|
||||
- DKPhotoGallery/Core (= 0.0.19)
|
||||
- DKPhotoGallery/Model (= 0.0.19)
|
||||
- DKPhotoGallery/Preview (= 0.0.19)
|
||||
- DKPhotoGallery/Resource (= 0.0.19)
|
||||
- SDWebImage
|
||||
- SwiftyGif
|
||||
- DKPhotoGallery/Core (0.0.19):
|
||||
- DKPhotoGallery/Model
|
||||
- DKPhotoGallery/Preview
|
||||
- SDWebImage
|
||||
- SwiftyGif
|
||||
- DKPhotoGallery/Model (0.0.19):
|
||||
- SDWebImage
|
||||
- SwiftyGif
|
||||
- DKPhotoGallery/Preview (0.0.19):
|
||||
- DKPhotoGallery/Model
|
||||
- DKPhotoGallery/Resource
|
||||
- SDWebImage
|
||||
- SwiftyGif
|
||||
- DKPhotoGallery/Resource (0.0.19):
|
||||
- SDWebImage
|
||||
- SwiftyGif
|
||||
- file_picker (0.0.1):
|
||||
- DKImagePickerController/PhotoGallery
|
||||
- Flutter
|
||||
- Flutter (1.0.0)
|
||||
- package_info_plus (0.4.5):
|
||||
- Flutter
|
||||
- path_provider_foundation (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- SDWebImage (5.21.1):
|
||||
- SDWebImage/Core (= 5.21.1)
|
||||
- SDWebImage/Core (5.21.1)
|
||||
- SwiftyGif (5.4.5)
|
||||
- thermion_flutter (0.0.1):
|
||||
- Flutter
|
||||
|
||||
DEPENDENCIES:
|
||||
- file_picker (from `.symlinks/plugins/file_picker/ios`)
|
||||
- Flutter (from `Flutter`)
|
||||
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
||||
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
|
||||
- thermion_flutter (from `.symlinks/plugins/thermion_flutter/ios`)
|
||||
|
||||
SPEC REPOS:
|
||||
trunk:
|
||||
- DKImagePickerController
|
||||
- DKPhotoGallery
|
||||
- SDWebImage
|
||||
- SwiftyGif
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
file_picker:
|
||||
:path: ".symlinks/plugins/file_picker/ios"
|
||||
Flutter:
|
||||
:path: Flutter
|
||||
package_info_plus:
|
||||
:path: ".symlinks/plugins/package_info_plus/ios"
|
||||
path_provider_foundation:
|
||||
:path: ".symlinks/plugins/path_provider_foundation/darwin"
|
||||
thermion_flutter:
|
||||
:path: ".symlinks/plugins/thermion_flutter/ios"
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
|
||||
thermion_flutter: c965e09831858465a1a8df59ff97e40a4d002773
|
||||
DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c
|
||||
DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60
|
||||
file_picker: a0560bc09d61de87f12d246fc47d2119e6ef37be
|
||||
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
|
||||
package_info_plus: 580e9a5f1b6ca5594e7c9ed5f92d1dfb2a66b5e1
|
||||
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
|
||||
SDWebImage: f29024626962457f3470184232766516dee8dfea
|
||||
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
|
||||
thermion_flutter: d0c4adbb1de69e5ecc244d70bbde81d8497246b2
|
||||
|
||||
PODFILE CHECKSUM: a57f30d18f102dd3ce366b1d62a55ecbef2158e5
|
||||
|
||||
COCOAPODS: 1.15.2
|
||||
COCOAPODS: 1.16.2
|
||||
|
||||
@@ -197,6 +197,7 @@
|
||||
97C146EC1CF9000F007C117D /* Resources */,
|
||||
9705A1C41CF9048500538489 /* Embed Frameworks */,
|
||||
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
|
||||
6DA127E3193C7EF5F7B6B4C0 /* [CP] Embed Pods Frameworks */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
@@ -284,6 +285,23 @@
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
|
||||
};
|
||||
6DA127E3193C7EF5F7B6B4C0 /* [CP] Embed Pods Frameworks */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
|
||||
);
|
||||
name = "[CP] Embed Pods Frameworks";
|
||||
outputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
78F05AF37D9E628BE6E89ABD /* [CP] Check Pods Manifest.lock */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@@ -436,7 +454,7 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
SUPPORTED_PLATFORMS = iphoneos;
|
||||
@@ -452,7 +470,7 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
DEVELOPMENT_TEAM = TM2B4SJXNJ;
|
||||
DEVELOPMENT_TEAM = 979UK3QQJ3;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
@@ -567,7 +585,7 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
@@ -618,7 +636,7 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
SUPPORTED_PLATFORMS = iphoneos;
|
||||
@@ -636,7 +654,7 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
DEVELOPMENT_TEAM = TM2B4SJXNJ;
|
||||
DEVELOPMENT_TEAM = 979UK3QQJ3;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
@@ -660,7 +678,7 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
DEVELOPMENT_TEAM = TM2B4SJXNJ;
|
||||
DEVELOPMENT_TEAM = 979UK3QQJ3;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
@@ -54,11 +55,13 @@
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
enableGPUValidationMode = "1"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
|
||||
132
examples/flutter/animations/lib/cup_page.dart
Normal file
132
examples/flutter/animations/lib/cup_page.dart
Normal file
@@ -0,0 +1,132 @@
|
||||
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 CupPage1 extends StatelessWidget {
|
||||
const CupPage1({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: Stack(children: [
|
||||
Positioned.fill(
|
||||
child: ViewerWidget(
|
||||
assetPath: "assets/cup/model.gltf",
|
||||
skyboxPath: "assets/default_env_skybox.ktx",
|
||||
iblPath: "assets/default_env_ibl.ktx",
|
||||
transformToUnitCube: true,
|
||||
initialCameraPosition: Vector3(0, 0, 6),
|
||||
background: Colors.blue,
|
||||
manipulatorType: ManipulatorType.ORBIT,
|
||||
onViewerAvailable: (viewer) async {
|
||||
await Future.delayed(const Duration(seconds: 5));
|
||||
await viewer.removeSkybox();
|
||||
},
|
||||
initial: Container(
|
||||
color: Colors.grey,
|
||||
),
|
||||
))
|
||||
]));
|
||||
}
|
||||
}
|
||||
|
||||
class CupPage extends StatefulWidget {
|
||||
const CupPage({super.key});
|
||||
|
||||
@override
|
||||
State<CupPage> createState() => _CupPageState();
|
||||
}
|
||||
|
||||
class _CupPageState extends State<CupPage> {
|
||||
bool _isLoading = true;
|
||||
String? _modelPath;
|
||||
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'));
|
||||
|
||||
if (await modelFile.exists()) {
|
||||
setState(() {
|
||||
_modelPath = modelFile.path;
|
||||
_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",
|
||||
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(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,358 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:animations/utils/binary_manager.dart';
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'model_viewer.dart';
|
||||
|
||||
Future<void> main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
// 初始化3D模型资源
|
||||
try {
|
||||
await BinaryManager.initializeAppResources();
|
||||
print('3D模型资源初始化成功');
|
||||
} catch (e) {
|
||||
print('无法初始化3D模型资源: $e');
|
||||
}
|
||||
|
||||
runApp(const MyApp());
|
||||
}
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
const MyApp({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
title: '3D模型纹理替换',
|
||||
theme: ThemeData(
|
||||
primarySwatch: Colors.blue,
|
||||
visualDensity: VisualDensity.adaptivePlatformDensity,
|
||||
),
|
||||
home: const ModelViewerWithTextureReplace(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ModelViewerWithTextureReplace extends StatefulWidget {
|
||||
const ModelViewerWithTextureReplace({super.key});
|
||||
|
||||
@override
|
||||
State<ModelViewerWithTextureReplace> createState() =>
|
||||
_ModelViewerWithTextureReplaceState();
|
||||
}
|
||||
|
||||
class _ModelViewerWithTextureReplaceState
|
||||
extends State<ModelViewerWithTextureReplace> {
|
||||
bool _isLoading = true;
|
||||
String? _modelPath;
|
||||
String? _texturePath;
|
||||
int _keyValue = 0;
|
||||
String _statusMessage = "正在加载模型...";
|
||||
bool _isReplacingTexture = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
BinaryManager.readLocalJson();
|
||||
_load3DModels();
|
||||
// _loadModel();
|
||||
}
|
||||
|
||||
// 加载3D模型文件
|
||||
Future<void> _load3DModels() async {
|
||||
try {
|
||||
setState(() {
|
||||
_statusMessage = "获取模型路径...";
|
||||
});
|
||||
|
||||
// 使用BinaryManager获取3D模型文件路径
|
||||
// final supportDir = await getApplicationSupportDirectory();
|
||||
// final cupDir = Directory('${supportDir.path}/cup');
|
||||
// final modelFile = File('${cupDir.path}/model.gltf');
|
||||
|
||||
final modelPath = await BinaryManager.get3DModelPath("model.gltf");
|
||||
|
||||
final texturePath = await BinaryManager.get3DModelPath("texture.jpg");
|
||||
|
||||
// print("路径: $modelPath");
|
||||
// print("文件是否存在: ${await File(modelPath).exists()}");
|
||||
// print("文件路径: ${await File(modelPath).uri}");
|
||||
//
|
||||
// setState(() {
|
||||
// _modelPath = File(modelPath).uri.toString(); // 确保是本地路径
|
||||
// });
|
||||
|
||||
// if (!await File(modelPath).exists()) {
|
||||
// print("❌ 模型文件不存在: $modelPath");
|
||||
// }
|
||||
// if (!await File(texturePath).exists()) {
|
||||
// print("❌ 纹理文件不存在: $texturePath");
|
||||
// }
|
||||
print('modelPath=${modelPath}\ntexturePath=${texturePath}');
|
||||
setState(() {
|
||||
_modelPath = modelPath;
|
||||
_texturePath = texturePath;
|
||||
_isLoading = false;
|
||||
_statusMessage = "加载完成";
|
||||
});
|
||||
} catch (e) {
|
||||
print('加载3D模型失败: $e');
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
_statusMessage = "加载失败: $e";
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 使用FilePicker选择新纹理
|
||||
Future<void> _pickNewTexture() async {
|
||||
if (_isReplacingTexture || _texturePath == null) return;
|
||||
|
||||
setState(() {
|
||||
_isReplacingTexture = true;
|
||||
_statusMessage = "正在选择纹理文件...";
|
||||
});
|
||||
|
||||
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 {
|
||||
setState(() {
|
||||
_statusMessage = "取消选择";
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
print('更换纹理失败: $e');
|
||||
setState(() {
|
||||
_statusMessage = "更换纹理失败: $e";
|
||||
});
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('更换纹理失败: $e')),
|
||||
);
|
||||
} finally {
|
||||
setState(() {
|
||||
_isReplacingTexture = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 重置纹理到原始状态
|
||||
Future<void> _resetTexture() async {
|
||||
if (_isReplacingTexture || _texturePath == null) return;
|
||||
|
||||
setState(() {
|
||||
_isReplacingTexture = true;
|
||||
_statusMessage = "正在重置纹理...";
|
||||
});
|
||||
|
||||
try {
|
||||
// 检查备份文件是否存在
|
||||
final backupTexturePath = '${_texturePath!}.backup';
|
||||
final backupFile = File(backupTexturePath);
|
||||
|
||||
if (await backupFile.exists()) {
|
||||
// 恢复备份文件
|
||||
await backupFile.copy(_texturePath!);
|
||||
|
||||
// 给渲染器一点时间来处理文件变化
|
||||
await Future.delayed(const Duration(milliseconds: 500));
|
||||
|
||||
// 强制重建ViewerWidget以应用恢复的纹理
|
||||
setState(() {
|
||||
_keyValue++;
|
||||
_statusMessage = "纹理已重置";
|
||||
});
|
||||
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('纹理已重置到原始状态')),
|
||||
);
|
||||
} else {
|
||||
setState(() {
|
||||
_statusMessage = "没有备份文件可恢复";
|
||||
});
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('没有备份文件可恢复')),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
print('重置纹理失败: $e');
|
||||
setState(() {
|
||||
_statusMessage = "重置纹理失败: $e";
|
||||
});
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('重置纹理失败: $e')),
|
||||
);
|
||||
} finally {
|
||||
setState(() {
|
||||
_isReplacingTexture = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('3D模型纹理替换'),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.texture),
|
||||
onPressed: _isReplacingTexture ? null : _pickNewTexture,
|
||||
tooltip: '更换纹理',
|
||||
),
|
||||
],
|
||||
),
|
||||
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),
|
||||
const Text('加载模型失败'),
|
||||
const SizedBox(height: 16),
|
||||
ElevatedButton(
|
||||
onPressed: _load3DModels,
|
||||
child: const Text('重试'),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
: Column(
|
||||
children: [
|
||||
// Expanded(
|
||||
// child: ViewerWidget(
|
||||
// key: ValueKey(_keyValue),
|
||||
// assetPath: "file://$_modelPath",
|
||||
// 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(),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
color: Colors.grey[100],
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
ElevatedButton.icon(
|
||||
icon: const Icon(Icons.texture),
|
||||
label: const Text('选择纹理图片'),
|
||||
onPressed: _isReplacingTexture
|
||||
? null
|
||||
: _pickNewTexture,
|
||||
),
|
||||
ElevatedButton.icon(
|
||||
icon: const Icon(Icons.refresh),
|
||||
label: const Text('重置纹理'),
|
||||
onPressed:
|
||||
_isReplacingTexture ? null : _resetTexture,
|
||||
),
|
||||
ElevatedButton.icon(
|
||||
icon: const Icon(Icons.camera),
|
||||
label: const Text('重置视图'),
|
||||
onPressed: _isReplacingTexture
|
||||
? null
|
||||
: () {
|
||||
setState(() {
|
||||
_keyValue++;
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
_statusMessage,
|
||||
style: TextStyle(
|
||||
color: Colors.grey[600],
|
||||
fontStyle: FontStyle.italic,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
/*
|
||||
import 'dart:async';
|
||||
import 'package:animations/cup_page.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:flutter/material.dart' hide View;
|
||||
import 'package:thermion_flutter/thermion_flutter.dart';
|
||||
@@ -21,7 +375,7 @@ class MyApp extends StatelessWidget {
|
||||
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
|
||||
useMaterial3: true,
|
||||
),
|
||||
home: const MyHomePage(title: 'Thermion Demo Home Page'),
|
||||
home: const CupPage(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -39,7 +393,7 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||
ThermionViewer? _thermionViewer;
|
||||
|
||||
ThermionAsset? _asset;
|
||||
final _droneUri = "assets/BusterDrone/scene.gltf";
|
||||
final _droneUri = "assets/cup/model.gltf";
|
||||
final _cubeUri = "assets/cube_with_morph_targets.glb";
|
||||
String? _loaded;
|
||||
|
||||
@@ -68,7 +422,6 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||
gltfAnimations.clear();
|
||||
gltfAnimations.addAll(labels);
|
||||
selectedGltfAnimation = 0;
|
||||
|
||||
}
|
||||
setState(() {});
|
||||
}
|
||||
@@ -165,3 +518,4 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||
]);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
284
examples/flutter/animations/lib/main1.dart
Normal file
284
examples/flutter/animations/lib/main1.dart
Normal file
@@ -0,0 +1,284 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:thermion_flutter/thermion_flutter.dart';
|
||||
|
||||
void main() {
|
||||
runApp(const MyApp());
|
||||
}
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
const MyApp({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
title: '3D模型纹理替换',
|
||||
theme: ThemeData(
|
||||
primarySwatch: Colors.blue,
|
||||
visualDensity: VisualDensity.adaptivePlatformDensity,
|
||||
),
|
||||
home: const ModelViewerScreen(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ModelViewerScreen extends StatefulWidget {
|
||||
const ModelViewerScreen({super.key});
|
||||
|
||||
@override
|
||||
State<ModelViewerScreen> createState() => _ModelViewerScreenState();
|
||||
}
|
||||
|
||||
class _ModelViewerScreenState extends State<ModelViewerScreen> {
|
||||
bool _isLoading = true;
|
||||
String? _modelPath;
|
||||
String? _texturePath;
|
||||
int _keyValue = 0;
|
||||
String _statusMessage = "正在加载模型...";
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadModel();
|
||||
}
|
||||
|
||||
// 加载模型文件到临时目录
|
||||
Future<void> _loadModel() async {
|
||||
try {
|
||||
setState(() {
|
||||
_statusMessage = "准备模型目录...";
|
||||
});
|
||||
|
||||
final directory = await getApplicationDocumentsDirectory();
|
||||
final modelDir = Directory('${directory.path}/model');
|
||||
|
||||
// 确保目录存在
|
||||
if (!await modelDir.exists()) {
|
||||
await modelDir.create(recursive: true);
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_statusMessage = "加载GLTF文件...";
|
||||
});
|
||||
|
||||
// 加载 .gltf 文件并写入本地
|
||||
final gltfData =
|
||||
await DefaultAssetBundle.of(context).load('assets/cup/model.gltf');
|
||||
final gltfFile = File('${modelDir.path}/model.gltf');
|
||||
await gltfFile.writeAsBytes(gltfData.buffer.asUint8List());
|
||||
|
||||
/// ✅ 打印本地路径(关键)
|
||||
print('✅ GLTF 本地路径: ${gltfFile.path}');
|
||||
print('✅ 是否存在: ${await gltfFile.exists()}');
|
||||
|
||||
setState(() {
|
||||
_statusMessage = "加载模型数据...";
|
||||
});
|
||||
|
||||
// 加载 .bin 文件并写入本地
|
||||
final binData =
|
||||
await DefaultAssetBundle.of(context).load('assets/cup/model.bin');
|
||||
final binFile = File('${modelDir.path}/model.bin');
|
||||
await binFile.writeAsBytes(binData.buffer.asUint8List());
|
||||
|
||||
setState(() {
|
||||
_statusMessage = "加载纹理...";
|
||||
});
|
||||
|
||||
// 加载纹理文件并写入本地
|
||||
final textureData =
|
||||
await DefaultAssetBundle.of(context).load('assets/cup/texture.jpg');
|
||||
final textureFile = File('${modelDir.path}/texture.jpg');
|
||||
await textureFile.writeAsBytes(textureData.buffer.asUint8List());
|
||||
|
||||
/// ✅ 打印纹理路径
|
||||
print('✅ 纹理本地路径: ${textureFile.path}');
|
||||
print('✅ 是否存在: ${await textureFile.exists()}');
|
||||
|
||||
setState(() {
|
||||
_modelPath = gltfFile.path;
|
||||
_texturePath = textureFile.path;
|
||||
_isLoading = false;
|
||||
_statusMessage = "加载完成";
|
||||
});
|
||||
} catch (e) {
|
||||
print('❌ 加载模型失败: $e');
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
_statusMessage = "加载失败: $e";
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 使用FilePicker选择新纹理 - 修复配置错误
|
||||
Future<void> _pickNewTexture() async {
|
||||
try {
|
||||
setState(() {
|
||||
_statusMessage = "正在选择纹理文件...";
|
||||
});
|
||||
|
||||
// 使用FilePicker选择文件 - 修复配置
|
||||
// 选项1: 使用FileType.image(不能指定allowedExtensions)
|
||||
FilePickerResult? result = await FilePicker.platform.pickFiles(
|
||||
type: FileType.image,
|
||||
allowMultiple: false,
|
||||
);
|
||||
|
||||
// 选项2: 如果需要特定扩展名,使用FileType.custom
|
||||
/*
|
||||
FilePickerResult? result = await FilePicker.platform.pickFiles(
|
||||
type: FileType.custom,
|
||||
allowMultiple: false,
|
||||
allowedExtensions: ['jpg', 'jpeg', 'png', 'bmp', 'tga'],
|
||||
);
|
||||
*/
|
||||
|
||||
if (result != null &&
|
||||
result.files.isNotEmpty &&
|
||||
result.files.single.path != null &&
|
||||
_texturePath != null) {
|
||||
setState(() {
|
||||
_statusMessage = "正在应用新纹理...";
|
||||
});
|
||||
|
||||
// 获取选中的文件
|
||||
PlatformFile file = result.files.first;
|
||||
File newTexture = File(file.path!);
|
||||
|
||||
// 检查文件是否存在
|
||||
if (await newTexture.exists()) {
|
||||
// 复制选中的图片到纹理路径
|
||||
await newTexture.copy(_texturePath!);
|
||||
|
||||
// 强制重建ViewerWidget以应用新纹理
|
||||
setState(() {
|
||||
_keyValue++;
|
||||
_statusMessage = "纹理已更新";
|
||||
});
|
||||
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('纹理已成功更新')),
|
||||
);
|
||||
} else {
|
||||
setState(() {
|
||||
_statusMessage = "文件不存在";
|
||||
});
|
||||
}
|
||||
} else {
|
||||
setState(() {
|
||||
_statusMessage = "取消选择";
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
print('更换纹理失败: $e');
|
||||
setState(() {
|
||||
_statusMessage = "更换纹理失败";
|
||||
});
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('更换纹理失败: $e')),
|
||||
);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('3D模型纹理替换'),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.texture),
|
||||
onPressed: _pickNewTexture,
|
||||
tooltip: '更换纹理',
|
||||
),
|
||||
],
|
||||
),
|
||||
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),
|
||||
const Text('加载模型失败'),
|
||||
const SizedBox(height: 16),
|
||||
ElevatedButton(
|
||||
onPressed: _loadModel,
|
||||
child: const Text('重试'),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
: Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: ViewerWidget(
|
||||
key: ValueKey(_keyValue),
|
||||
assetPath: _modelPath!,
|
||||
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(),
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
color: Colors.grey[100],
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
ElevatedButton.icon(
|
||||
icon: const Icon(Icons.texture),
|
||||
label: const Text('选择纹理图片'),
|
||||
onPressed: _pickNewTexture,
|
||||
),
|
||||
ElevatedButton.icon(
|
||||
icon: const Icon(Icons.refresh),
|
||||
label: const Text('重置视图'),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_keyValue++;
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
_statusMessage,
|
||||
style: TextStyle(
|
||||
color: Colors.grey[600],
|
||||
fontStyle: FontStyle.italic,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
172
examples/flutter/animations/lib/model_viewer.dart
Normal file
172
examples/flutter/animations/lib/model_viewer.dart
Normal file
@@ -0,0 +1,172 @@
|
||||
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';
|
||||
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();
|
||||
|
||||
_initializeViewerAndLoadModel();
|
||||
}
|
||||
|
||||
Future<void> _initializeViewerAndLoadModel() async {
|
||||
try {
|
||||
// 获取应用支持目录并构建模型文件路径
|
||||
final supportDir = await getApplicationSupportDirectory();
|
||||
final modelFile = File(p.join(supportDir.path, 'cup', 'model.gltf'));
|
||||
|
||||
// 检查文件是否存在
|
||||
if (!await modelFile.exists()) {
|
||||
await BinaryManager.migrate3DModels();
|
||||
await Future.delayed(const Duration(milliseconds: 3000));
|
||||
|
||||
// throw Exception("模型文件不存在于路径: ${modelFile.path}");
|
||||
}
|
||||
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");
|
||||
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: _initializeViewerAndLoadModel,
|
||||
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();
|
||||
}
|
||||
}
|
||||
372
examples/flutter/animations/lib/utils/binary_manager.dart
Normal file
372
examples/flutter/animations/lib/utils/binary_manager.dart
Normal file
@@ -0,0 +1,372 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
|
||||
class BinaryManager {
|
||||
/// 当前应用版本(用于初始化检查)
|
||||
static String? _currentVersion;
|
||||
|
||||
/// 获取当前应用版本
|
||||
static Future<String> _getCurrentVersion() async {
|
||||
if (_currentVersion == null) {
|
||||
final packageInfo = await PackageInfo.fromPlatform();
|
||||
_currentVersion = '${packageInfo.version}+${packageInfo.buildNumber}';
|
||||
}
|
||||
return _currentVersion!;
|
||||
}
|
||||
|
||||
/// 检查是否已完成初始化(基于版本号)
|
||||
static Future<bool> isInitialized() async {
|
||||
final supportDir = await getApplicationSupportDirectory();
|
||||
final currentVersion = await _getCurrentVersion();
|
||||
final initMarker = File(
|
||||
p.join(supportDir.path, '.initialized_$currentVersion'),
|
||||
);
|
||||
|
||||
return await initMarker.exists();
|
||||
}
|
||||
|
||||
/// 标记初始化完成(文件名包含版本号)
|
||||
static Future<void> markInitialized() async {
|
||||
final supportDir = await getApplicationSupportDirectory();
|
||||
final currentVersion = await _getCurrentVersion();
|
||||
final initMarker = File(
|
||||
p.join(supportDir.path, '.initialized_$currentVersion'),
|
||||
);
|
||||
await initMarker.writeAsString('${DateTime.now().toIso8601String()}\n');
|
||||
}
|
||||
|
||||
/// 从assets目录提取文件到指定路径
|
||||
static Future<void> _extractAsset(String assetPath, String localPath) async {
|
||||
final localFile = File(localPath);
|
||||
|
||||
try {
|
||||
// 从assets中读取数据
|
||||
final ByteData data = await rootBundle.load(assetPath);
|
||||
final List<int> bytes = data.buffer.asUint8List();
|
||||
|
||||
// 确保目录存在
|
||||
final dir = Directory(p.dirname(localPath));
|
||||
if (!await dir.exists()) {
|
||||
await dir.create(recursive: true);
|
||||
}
|
||||
|
||||
// 写入本地文件
|
||||
await localFile.writeAsBytes(bytes);
|
||||
print('成功提取文件: $assetPath -> $localPath');
|
||||
} catch (e) {
|
||||
print('提取资源文件失败 $assetPath: $e');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
/// 初始化应用程序资源 - 将所有必要的assets迁移到系统文件夹
|
||||
static Future<void> initializeAppResources() async {
|
||||
final currentVersion = await _getCurrentVersion();
|
||||
|
||||
if (await isInitialized()) {
|
||||
print('应用程序资源已初始化,当前版本: $currentVersion');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 迁移shell脚本
|
||||
await _migrateShellScript();
|
||||
|
||||
// 迁移二进制文件
|
||||
await _migrateBinaries();
|
||||
|
||||
// 迁移模型文件
|
||||
await _migrateModels();
|
||||
|
||||
// 迁移3D模型文件
|
||||
await migrate3DModels();
|
||||
|
||||
// 标记初始化完成
|
||||
await markInitialized();
|
||||
|
||||
print('应用程序资源初始化完成');
|
||||
} catch (e) {
|
||||
print('应用程序资源初始化失败: $e');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
/// 迁移3D模型文件
|
||||
static Future<void> migrate3DModels() async {
|
||||
print('开始迁移3D模型文件...');
|
||||
|
||||
final supportDir = await getApplicationSupportDirectory();
|
||||
final models3dDir = Directory(p.join(supportDir.path, 'cup'));
|
||||
|
||||
// 创建3D模型目录
|
||||
if (!await models3dDir.exists()) {
|
||||
await models3dDir.create(recursive: true);
|
||||
}
|
||||
|
||||
// 3D模型文件列表
|
||||
final model3dFiles = ['model.gltf', 'model.bin', 'texture.jpg'];
|
||||
|
||||
// 迁移所有3D模型文件
|
||||
for (final modelFile in model3dFiles) {
|
||||
try {
|
||||
await _extractAsset(
|
||||
'assets/cup/$modelFile',
|
||||
p.join(models3dDir.path, modelFile),
|
||||
);
|
||||
} catch (e) {
|
||||
print('迁移3D模型文件失败 $modelFile: $e');
|
||||
// 继续迁移其他模型文件
|
||||
}
|
||||
}
|
||||
|
||||
print('3D模型文件迁移完成');
|
||||
}
|
||||
|
||||
static Future<void> _migrateShellScript() async {
|
||||
print('开始迁移脚本文件...');
|
||||
|
||||
final supportDir = await getApplicationSupportDirectory();
|
||||
|
||||
// 迁移 Shell 脚本 (macOS/Linux)
|
||||
final shellScriptPath = p.join(supportDir.path, 'upscale_to_8k_single.sh');
|
||||
await _extractAsset('assets/upscale_to_8k_single.sh', shellScriptPath);
|
||||
|
||||
// 设置脚本执行权限(仅限 macOS/Linux)
|
||||
if (Platform.isMacOS || Platform.isLinux) {
|
||||
try {
|
||||
final result = await Process.run('chmod', ['+x', shellScriptPath]);
|
||||
if (result.exitCode == 0) {
|
||||
print('成功设置Shell脚本执行权限: $shellScriptPath');
|
||||
} else {
|
||||
print('设置Shell脚本执行权限失败: ${result.stderr}');
|
||||
}
|
||||
} catch (e) {
|
||||
print('设置Shell脚本权限异常: $e');
|
||||
}
|
||||
}
|
||||
|
||||
print('脚本文件迁移完成');
|
||||
}
|
||||
|
||||
/// 迁移二进制文件
|
||||
static Future<void> _migrateBinaries() async {
|
||||
print('开始迁移二进制文件...');
|
||||
|
||||
final supportDir = await getApplicationSupportDirectory();
|
||||
final binDir = Directory(p.join(supportDir.path, 'bin'));
|
||||
|
||||
// 创建bin目录
|
||||
if (!await binDir.exists()) {
|
||||
await binDir.create(recursive: true);
|
||||
}
|
||||
|
||||
// macOS 二进制文件
|
||||
final macBinDir = Directory(p.join(binDir.path, 'macos'));
|
||||
if (!await macBinDir.exists()) {
|
||||
await macBinDir.create(recursive: true);
|
||||
}
|
||||
|
||||
await _extractAsset(
|
||||
'assets/bin/mac/upscayl-bin',
|
||||
p.join(macBinDir.path, 'upscayl-bin'),
|
||||
);
|
||||
|
||||
// Windows 二进制文件
|
||||
final winBinDir = Directory(p.join(binDir.path, 'windows'));
|
||||
if (!await winBinDir.exists()) {
|
||||
await winBinDir.create(recursive: true);
|
||||
}
|
||||
|
||||
await _extractAsset(
|
||||
'assets/bin/windows/upscayl-bin.exe',
|
||||
p.join(winBinDir.path, 'upscayl-bin.exe'),
|
||||
);
|
||||
|
||||
await _extractAsset(
|
||||
'assets/bin/windows/vcomp140.dll',
|
||||
p.join(winBinDir.path, 'vcomp140.dll'),
|
||||
);
|
||||
|
||||
await _extractAsset(
|
||||
'assets/bin/windows/vcomp140d.dll',
|
||||
p.join(winBinDir.path, 'vcomp140d.dll'),
|
||||
);
|
||||
|
||||
// 设置 macOS/Linux 二进制文件执行权限
|
||||
if (Platform.isMacOS || Platform.isLinux) {
|
||||
final binPath = p.join(macBinDir.path, 'upscayl-bin');
|
||||
try {
|
||||
final result = await Process.run('chmod', ['+x', binPath]);
|
||||
if (result.exitCode == 0) {
|
||||
print('成功设置执行权限: $binPath');
|
||||
} else {
|
||||
print('设置执行权限失败: ${result.stderr}');
|
||||
}
|
||||
} catch (e) {
|
||||
print('设置权限异常: $e');
|
||||
}
|
||||
}
|
||||
|
||||
print('二进制文件迁移完成');
|
||||
}
|
||||
|
||||
/// 迁移模型文件
|
||||
static Future<void> _migrateModels() async {
|
||||
print('开始迁移模型文件...');
|
||||
|
||||
final supportDir = await getApplicationSupportDirectory();
|
||||
final modelsDir = Directory(p.join(supportDir.path, 'models'));
|
||||
|
||||
// 创建models目录
|
||||
if (!await modelsDir.exists()) {
|
||||
await modelsDir.create(recursive: true);
|
||||
}
|
||||
|
||||
// 模型文件列表
|
||||
final modelFiles = [
|
||||
'high-fidelity-4x.bin',
|
||||
'high-fidelity-4x.param',
|
||||
'digital-art-4x.bin',
|
||||
'digital-art-4x.param',
|
||||
'remacri-4x.bin',
|
||||
'remacri-4x.param',
|
||||
'ultrasharp-4x.bin',
|
||||
'ultrasharp-4x.param',
|
||||
'upscayl-standard-4x.bin',
|
||||
'upscayl-standard-4x.param',
|
||||
'ultramix-balanced-4x.bin',
|
||||
'ultramix-balanced-4x.param',
|
||||
'upscayl-lite-4x.bin',
|
||||
'upscayl-lite-4x.param',
|
||||
];
|
||||
|
||||
// 迁移所有模型文件
|
||||
for (final modelFile in modelFiles) {
|
||||
try {
|
||||
await _extractAsset(
|
||||
'assets/models/$modelFile',
|
||||
p.join(modelsDir.path, modelFile),
|
||||
);
|
||||
} catch (e) {
|
||||
print('迁移模型文件失败 $modelFile: $e');
|
||||
// 继续迁移其他模型文件
|
||||
}
|
||||
}
|
||||
|
||||
print('模型文件迁移完成');
|
||||
}
|
||||
|
||||
/// 获取模型文件目录路径
|
||||
static Future<String> getModelsPath() async {
|
||||
final supportDir = await getApplicationSupportDirectory();
|
||||
return p.join(supportDir.path, 'cup', "model.gltf");
|
||||
}
|
||||
|
||||
/// 获取3D模型文件目录路径
|
||||
static Future<String> get3DModelsPath() async {
|
||||
final supportDir = await getApplicationSupportDirectory();
|
||||
return p.join(supportDir.path, 'cup');
|
||||
}
|
||||
|
||||
static Future<Map<String, dynamic>> readLocalJson() async {
|
||||
// 获取应用支持目录(默认在 ~/Library/Application Support/)
|
||||
final appSupportDir = await getApplicationSupportDirectory();
|
||||
final targetDir = Directory('${appSupportDir.path}/cup');
|
||||
|
||||
// 检查目录是否存在,若不存在则创建
|
||||
if (!await targetDir.exists()) {
|
||||
await targetDir.create(recursive: true);
|
||||
}
|
||||
print('file=${targetDir.path}/model.gltf');
|
||||
// 构建 JSON 文件路径
|
||||
final file = File('${targetDir.path}/model.gltf');
|
||||
|
||||
if (await file.exists()) {
|
||||
final content = await file.readAsString();
|
||||
return jsonDecode(content);
|
||||
} else {
|
||||
throw Exception('JSON 文件不存在');
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取特定的3D模型文件路径
|
||||
static Future<String> get3DModelPath(String filename) async {
|
||||
final models3dPath = await get3DModelsPath();
|
||||
return p.join(models3dPath, filename);
|
||||
}
|
||||
|
||||
/// 获取脚本文件路径 (仅适用于macOS/Linux)
|
||||
static Future<String> getScriptPath() async {
|
||||
if (Platform.isWindows) {
|
||||
throw UnsupportedError('Windows平台已使用专门的UpscaleWindowsService,不再需要脚本文件');
|
||||
}
|
||||
|
||||
final supportDir = await getApplicationSupportDirectory();
|
||||
final scriptName = 'upscale_to_8k_single.sh';
|
||||
final scriptPath = p.join(supportDir.path, scriptName);
|
||||
final scriptFile = File(scriptPath);
|
||||
|
||||
if (!await scriptFile.exists()) {
|
||||
throw FileSystemException(
|
||||
'脚本文件不存在: $scriptPath\n请确保应用程序已正确初始化资源',
|
||||
scriptPath,
|
||||
);
|
||||
}
|
||||
|
||||
return scriptPath;
|
||||
}
|
||||
|
||||
/// 获取二进制文件路径
|
||||
static Future<String> getBinaryPath() async {
|
||||
final supportDir = await getApplicationSupportDirectory();
|
||||
|
||||
String platformDir;
|
||||
String binaryName;
|
||||
|
||||
if (Platform.isMacOS) {
|
||||
platformDir = 'macos';
|
||||
binaryName = 'upscayl-bin';
|
||||
} else if (Platform.isWindows) {
|
||||
platformDir = 'windows';
|
||||
binaryName = 'upscayl-bin.exe';
|
||||
} else if (Platform.isLinux) {
|
||||
platformDir = 'macos'; // Linux 使用 macOS 的二进制文件
|
||||
binaryName = 'upscayl-bin';
|
||||
} else {
|
||||
throw UnsupportedError('不支持的操作系统: ${Platform.operatingSystem}');
|
||||
}
|
||||
|
||||
final binPath = p.join(supportDir.path, 'bin', platformDir, binaryName);
|
||||
|
||||
// 检查文件是否存在
|
||||
final binFile = File(binPath);
|
||||
if (!await binFile.exists()) {
|
||||
throw FileSystemException('二进制文件不存在: $binPath\n请确保应用程序已正确初始化资源', binPath);
|
||||
}
|
||||
|
||||
return binPath;
|
||||
}
|
||||
|
||||
/// 设置脚本执行权限
|
||||
static Future<void> setScriptExecutable(String scriptPath) async {
|
||||
// 设置脚本执行权限(仅限 macOS/Linux)
|
||||
if (Platform.isMacOS || Platform.isLinux) {
|
||||
try {
|
||||
final result = await Process.run('chmod', ['+x', scriptPath]);
|
||||
if (result.exitCode == 0) {
|
||||
print('成功设置脚本执行权限: $scriptPath');
|
||||
} else {
|
||||
print('设置脚本执行权限失败: ${result.stderr}');
|
||||
}
|
||||
} catch (e) {
|
||||
print('设置脚本权限异常: $e');
|
||||
}
|
||||
}
|
||||
// Windows 不需要设置执行权限
|
||||
}
|
||||
}
|
||||
204
examples/flutter/animations/lib/utils/model_manager.dart
Normal file
204
examples/flutter/animations/lib/utils/model_manager.dart
Normal file
@@ -0,0 +1,204 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
|
||||
class ModelManager {
|
||||
/// 当前应用版本(用于初始化检查)
|
||||
static String? _currentVersion;
|
||||
|
||||
/// 获取当前应用版本
|
||||
static Future<String> _getCurrentVersion() async {
|
||||
if (_currentVersion == null) {
|
||||
final packageInfo = await PackageInfo.fromPlatform();
|
||||
_currentVersion = '${packageInfo.version}+${packageInfo.buildNumber}';
|
||||
}
|
||||
return _currentVersion!;
|
||||
}
|
||||
|
||||
/// 检查3D模型资源是否已完成初始化(基于版本号)
|
||||
static Future<bool> isInitialized() async {
|
||||
final supportDir = await getApplicationSupportDirectory();
|
||||
final currentVersion = await _getCurrentVersion();
|
||||
final initMarker = File(
|
||||
p.join(supportDir.path, '3d_models', '.initialized_$currentVersion'),
|
||||
);
|
||||
|
||||
return await initMarker.exists();
|
||||
}
|
||||
|
||||
/// 标记3D模型资源初始化完成
|
||||
static Future<void> markInitialized() async {
|
||||
final supportDir = await getApplicationSupportDirectory();
|
||||
final currentVersion = await _getCurrentVersion();
|
||||
final modelsDir = Directory(p.join(supportDir.path, '3d_models'));
|
||||
|
||||
if (!await modelsDir.exists()) {
|
||||
await modelsDir.create(recursive: true);
|
||||
}
|
||||
|
||||
final initMarker = File(
|
||||
p.join(modelsDir.path, '.initialized_$currentVersion'),
|
||||
);
|
||||
await initMarker.writeAsString('${DateTime.now().toIso8601String()}\n');
|
||||
}
|
||||
|
||||
/// 从assets目录提取文件到指定路径
|
||||
static Future<void> _extractAsset(String assetPath, String localPath) async {
|
||||
final localFile = File(localPath);
|
||||
final localDir = Directory(p.dirname(localPath));
|
||||
|
||||
// 确保目录存在
|
||||
if (!await localDir.exists()) {
|
||||
await localDir.create(recursive: true);
|
||||
}
|
||||
|
||||
try {
|
||||
// 从assets中读取数据
|
||||
final ByteData data = await rootBundle.load(assetPath);
|
||||
final List<int> bytes = data.buffer.asUint8List();
|
||||
|
||||
// 写入本地文件
|
||||
await localFile.writeAsBytes(bytes);
|
||||
print('成功提取3D模型文件: $assetPath -> $localPath');
|
||||
} catch (e) {
|
||||
print('提取3D模型资源文件失败 $assetPath: $e');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
/// 初始化3D模型资源
|
||||
static Future<void> initializeAppResources() async {
|
||||
final currentVersion = await _getCurrentVersion();
|
||||
|
||||
if (await isInitialized()) {
|
||||
print('3D模型资源已初始化,当前版本: $currentVersion');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 迁移模型文件
|
||||
await _migrateModels();
|
||||
|
||||
// 标记初始化完成
|
||||
await markInitialized();
|
||||
|
||||
print('3D模型资源初始化完成');
|
||||
} catch (e) {
|
||||
print('3D模型资源初始化失败: $e');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
/// 迁移3D模型文件
|
||||
/// 迁移3D模型文件
|
||||
static Future<void> _migrateModels() async {
|
||||
print('开始迁移3D模型文件...');
|
||||
|
||||
final supportDir = await getApplicationSupportDirectory();
|
||||
final modelsDir = Directory(p.join(supportDir.path, 'models'));
|
||||
|
||||
// 创建3D模型目录
|
||||
if (!await modelsDir.exists()) {
|
||||
await modelsDir.create(recursive: true);
|
||||
}
|
||||
|
||||
// 创建cup子目录
|
||||
final cupDir = Directory(p.join(modelsDir.path, 'cup'));
|
||||
if (!await cupDir.exists()) {
|
||||
await cupDir.create(recursive: true);
|
||||
}
|
||||
|
||||
// 3D模型文件列表 - 修正路径
|
||||
final modelFiles = [
|
||||
'cup/model.gltf',
|
||||
'cup/model.bin',
|
||||
'cup/texture.jpg',
|
||||
];
|
||||
|
||||
// 迁移所有3D模型文件
|
||||
for (final modelFile in modelFiles) {
|
||||
try {
|
||||
// 确保源文件路径正确
|
||||
final assetPath = 'assets/$modelFile';
|
||||
|
||||
// 检查资源是否存在
|
||||
try {
|
||||
await rootBundle.load(assetPath);
|
||||
} catch (e) {
|
||||
print('资源文件不存在: $assetPath');
|
||||
continue;
|
||||
}
|
||||
|
||||
await _extractAsset(
|
||||
assetPath,
|
||||
p.join(modelsDir.path, modelFile),
|
||||
);
|
||||
} catch (e) {
|
||||
print('迁移3D模型文件失败 $modelFile: $e');
|
||||
// 继续迁移其他模型文件
|
||||
}
|
||||
}
|
||||
|
||||
print('3D模型文件迁移完成');
|
||||
}
|
||||
|
||||
/// 获取3D模型文件目录路径
|
||||
static Future<String> getModelsPath() async {
|
||||
final supportDir = await getApplicationSupportDirectory();
|
||||
return p.join(supportDir.path, 'models/cup/');
|
||||
}
|
||||
|
||||
/// 获取特定3D模型文件的路径
|
||||
static Future<String> getModelFilePath(String relativePath) async {
|
||||
final modelsPath = await getModelsPath();
|
||||
final filePath = p.join(modelsPath, relativePath);
|
||||
final file = File(filePath);
|
||||
|
||||
if (!await file.exists()) {
|
||||
throw FileSystemException(
|
||||
'3D模型文件不存在: $filePath\n请确保应用程序已正确初始化3D模型资源',
|
||||
filePath,
|
||||
);
|
||||
}
|
||||
|
||||
return filePath;
|
||||
}
|
||||
|
||||
/// 获取GLTF模型文件路径
|
||||
static Future<String> getGltfModelPath() async {
|
||||
return getModelFilePath('cup/model.gltf');
|
||||
}
|
||||
|
||||
/// 获取BIN模型文件路径
|
||||
static Future<String> getBinModelPath() async {
|
||||
return getModelFilePath('cup/model.bin');
|
||||
}
|
||||
|
||||
/// 获取纹理文件路径
|
||||
static Future<String> getTexturePath() async {
|
||||
return getModelFilePath('cup/texture.jpg');
|
||||
}
|
||||
|
||||
/// 更新纹理文件
|
||||
static Future<void> updateTexture(File newTextureFile) async {
|
||||
try {
|
||||
final texturePath = await getTexturePath();
|
||||
final textureFile = File(texturePath);
|
||||
|
||||
// 删除旧纹理文件
|
||||
if (await textureFile.exists()) {
|
||||
await textureFile.delete();
|
||||
}
|
||||
|
||||
// 复制新纹理文件
|
||||
await newTextureFile.copy(texturePath);
|
||||
print('纹理文件更新成功: $texturePath');
|
||||
} catch (e) {
|
||||
print('更新纹理文件失败: $e');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,8 +5,14 @@
|
||||
import FlutterMacOS
|
||||
import Foundation
|
||||
|
||||
import file_picker
|
||||
import package_info_plus
|
||||
import path_provider_foundation
|
||||
import thermion_flutter
|
||||
|
||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin"))
|
||||
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
|
||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||
SwiftThermionFlutterPlugin.register(with: registry.registrar(forPlugin: "SwiftThermionFlutterPlugin"))
|
||||
}
|
||||
|
||||
@@ -1,22 +1,41 @@
|
||||
PODS:
|
||||
- file_picker (0.0.1):
|
||||
- FlutterMacOS
|
||||
- FlutterMacOS (1.0.0)
|
||||
- package_info_plus (0.0.1):
|
||||
- FlutterMacOS
|
||||
- path_provider_foundation (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- thermion_flutter (0.0.1):
|
||||
- FlutterMacOS
|
||||
|
||||
DEPENDENCIES:
|
||||
- file_picker (from `Flutter/ephemeral/.symlinks/plugins/file_picker/macos`)
|
||||
- FlutterMacOS (from `Flutter/ephemeral`)
|
||||
- package_info_plus (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos`)
|
||||
- path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`)
|
||||
- thermion_flutter (from `Flutter/ephemeral/.symlinks/plugins/thermion_flutter/macos`)
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
file_picker:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/file_picker/macos
|
||||
FlutterMacOS:
|
||||
:path: Flutter/ephemeral
|
||||
package_info_plus:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos
|
||||
path_provider_foundation:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin
|
||||
thermion_flutter:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/thermion_flutter/macos
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
file_picker: 7584aae6fa07a041af2b36a2655122d42f578c1a
|
||||
FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1
|
||||
thermion_flutter: debb51a861788780ce40e11e4400c2e5a8681fdf
|
||||
package_info_plus: a8a591e70e87ce97ce5d21b2594f69cea9e0312f
|
||||
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
|
||||
thermion_flutter: d5d52ef933321eedbee6c4ddda4259c8cbcc84ed
|
||||
|
||||
PODFILE CHECKSUM: 1888651be91a8ad58692c1add9ce24279fd4e950
|
||||
|
||||
COCOAPODS: 1.15.2
|
||||
COCOAPODS: 1.16.2
|
||||
|
||||
@@ -3,9 +3,19 @@
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<false/>
|
||||
<key>com.apple.security.cs.allow-jit</key>
|
||||
<true/>
|
||||
<key>com.apple.security.files.user-selected.read-write</key>
|
||||
<true/>
|
||||
<key>com.apple.security.files.downloads.read-write</key>
|
||||
<true/>
|
||||
<key>com.apple.security.files.pictures.read-write</key>
|
||||
<true/>
|
||||
<key>com.apple.security.files.bookmarks.app-scope</key>
|
||||
<true/>
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
<key>com.apple.security.network.server</key>
|
||||
<true/>
|
||||
</dict>
|
||||
|
||||
@@ -3,6 +3,20 @@
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<false/>
|
||||
<key>com.apple.security.cs.allow-jit</key>
|
||||
<true/>
|
||||
<key>com.apple.security.files.user-selected.read-write</key>
|
||||
<true/>
|
||||
<key>com.apple.security.files.downloads.read-write</key>
|
||||
<true/>
|
||||
<key>com.apple.security.files.pictures.read-write</key>
|
||||
<true/>
|
||||
<key>com.apple.security.files.bookmarks.app-scope</key>
|
||||
<true/>
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
<key>com.apple.security.network.server</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -16,6 +16,12 @@ dependencies:
|
||||
win32: 5.5.1
|
||||
web: 1.0.0
|
||||
thermion_flutter: ^0.2.1-dev.9
|
||||
file_picker: ^8.3.2
|
||||
path_provider: ^2.1.5
|
||||
package_info_plus: ^8.0.2
|
||||
path: ^1.8.0
|
||||
|
||||
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
@@ -40,3 +46,8 @@ flutter:
|
||||
- assets/
|
||||
- assets/BusterDrone/
|
||||
- assets/BusterDrone/textures/
|
||||
- assets/cup/
|
||||
- assets/bin/mac/
|
||||
- assets/bin/windows/
|
||||
- assets/models/
|
||||
- assets/upscale_to_8k_single.sh
|
||||
Reference in New Issue
Block a user