""" 预设管理器 管理地形生成和编辑的预设配置 """ import json import os from typing import Dict, Any, List, Optional import time class PresetManager: """ 预设管理器 管理地形生成和编辑的预设配置,包括创建、保存、加载和应用预设 """ def __init__(self, plugin): """ 初始化预设管理器 Args: plugin: 程序化地形生成插件实例 """ self.plugin = plugin self.enabled = False self.initialized = False # 预设配置 self.preset_config = { 'presets_directory': 'presets', 'default_preset': 'default', 'auto_save_enabled': True, 'max_presets': 100 } # 预设数据 self.presets = {} self.current_preset = None # 预设类别 self.preset_categories = { 'terrain': {'name': '地形', 'description': '地形生成预设'}, 'biome': {'name': '生物群落', 'description': '生物群落分布预设'}, 'erosion': {'name': '侵蚀', 'description': '侵蚀效果预设'}, 'vegetation': {'name': '植被', 'description': '植被分布预设'}, 'editor': {'name': '编辑器', 'description': '编辑器配置预设'} } # 统计信息 self.stats = { 'presets_loaded': 0, 'presets_saved': 0, 'presets_applied': 0, 'total_load_time': 0.0, 'total_save_time': 0.0 } print("✓ 预设管理器已创建") def initialize(self) -> bool: """ 初始化预设管理器 Returns: 是否初始化成功 """ try: # 创建预设目录 presets_dir = self.preset_config['presets_directory'] if not os.path.exists(presets_dir): os.makedirs(presets_dir) # 加载默认预设 self._load_default_presets() # 加载用户预设 self._load_user_presets() self.initialized = True print("✓ 预设管理器初始化完成") return True except Exception as e: print(f"✗ 预设管理器初始化失败: {e}") import traceback traceback.print_exc() return False def enable(self) -> bool: """ 启用预设管理器 Returns: 是否启用成功 """ try: if not self.initialized: print("✗ 预设管理器未初始化") return False self.enabled = True print("✓ 预设管理器已启用") return True except Exception as e: print(f"✗ 预设管理器启用失败: {e}") import traceback traceback.print_exc() return False def disable(self): """禁用预设管理器""" try: self.enabled = False print("✓ 预设管理器已禁用") except Exception as e: print(f"✗ 预设管理器禁用失败: {e}") import traceback traceback.print_exc() def finalize(self): """清理预设管理器资源""" try: self.disable() # 自动保存当前预设 if self.preset_config['auto_save_enabled'] and self.current_preset: self.save_preset(self.current_preset) self.initialized = False print("✓ 预设管理器资源已清理") except Exception as e: print(f"✗ 预设管理器资源清理失败: {e}") import traceback traceback.print_exc() def update(self, dt: float): """ 更新预设管理器状态 Args: dt: 时间增量 """ # 处理更新逻辑 pass def _load_default_presets(self): """加载默认预设""" try: # 创建默认地形预设 self.presets['default_terrain'] = { 'name': '默认地形', 'category': 'terrain', 'description': '默认的地形生成参数', 'parameters': { 'terrain_size': 512, 'height_scale': 100.0, 'noise_scale': 50.0, 'octaves': 6, 'persistence': 0.5, 'lacunarity': 2.0 }, 'timestamp': time.time(), 'is_default': True } # 创建默认生物群落预设 self.presets['default_biome'] = { 'name': '默认生物群落', 'category': 'biome', 'description': '默认的生物群落分布参数', 'parameters': { 'humidity_scale': 20.0, 'temperature_scale': 20.0, 'height_influence': 0.3, 'humidity_influence': 0.4, 'temperature_influence': 0.3 }, 'timestamp': time.time(), 'is_default': True } # 创建默认侵蚀预设 self.presets['default_erosion'] = { 'name': '默认侵蚀', 'category': 'erosion', 'description': '默认的侵蚀效果参数', 'parameters': { 'water_erosion_rate': 0.1, 'thermal_erosion_rate': 0.05, 'wind_erosion_rate': 0.02 }, 'timestamp': time.time(), 'is_default': True } # 创建默认植被预设 self.presets['default_vegetation'] = { 'name': '默认植被', 'category': 'vegetation', 'description': '默认的植被分布参数', 'parameters': { 'density_scale': 10.0, 'clumping_factor': 0.7, 'slope_tolerance': 0.3 }, 'timestamp': time.time(), 'is_default': True } # 创建默认编辑器预设 self.presets['default_editor'] = { 'name': '默认编辑器', 'category': 'editor', 'description': '默认的编辑器配置', 'parameters': { 'brush_size': 10, 'brush_strength': 0.5, 'preview_enabled': True, 'realtime_update': True }, 'timestamp': time.time(), 'is_default': True } print("✓ 默认预设加载完成") except Exception as e: print(f"✗ 默认预设加载失败: {e}") import traceback traceback.print_exc() def _load_user_presets(self): """加载用户预设""" try: load_start_time = time.time() presets_dir = self.preset_config['presets_directory'] if not os.path.exists(presets_dir): print("✓ 用户预设目录不存在,跳过加载") return # 遍历预设目录 for filename in os.listdir(presets_dir): if filename.endswith('.json'): try: filepath = os.path.join(presets_dir, filename) with open(filepath, 'r', encoding='utf-8') as f: preset_data = json.load(f) # 使用文件名(不含扩展名)作为预设ID preset_id = os.path.splitext(filename)[0] self.presets[preset_id] = preset_data except Exception as e: print(f"✗ 预设文件加载失败 {filename}: {e}") load_time = time.time() - load_start_time self.stats['presets_loaded'] = len(self.presets) self.stats['total_load_time'] = load_time print(f"✓ 用户预设加载完成,共加载 {len(self.presets)} 个预设,耗时: {load_time:.3f}秒") except Exception as e: print(f"✗ 用户预设加载失败: {e}") import traceback traceback.print_exc() def save_preset(self, preset_id: str, preset_data: Dict[str, Any] = None) -> bool: """ 保存预设 Args: preset_id: 预设ID preset_data: 预设数据(如果为None,则保存当前配置) Returns: 是否保存成功 """ try: save_start_time = time.time() if not self.enabled: print("✗ 预设管理器未启用") return False # 如果未提供预设数据,则创建当前配置的快照 if preset_data is None: preset_data = self._create_current_preset_snapshot(preset_id) # 保存预设数据 self.presets[preset_id] = preset_data # 保存到文件 presets_dir = self.preset_config['presets_directory'] filepath = os.path.join(presets_dir, f"{preset_id}.json") with open(filepath, 'w', encoding='utf-8') as f: json.dump(preset_data, f, ensure_ascii=False, indent=2) # 更新统计信息 save_time = time.time() - save_start_time self.stats['presets_saved'] += 1 self.stats['total_save_time'] += save_time print(f"✓ 预设 '{preset_id}' 保存完成,耗时: {save_time:.3f}秒") return True except Exception as e: print(f"✗ 预设保存失败: {e}") import traceback traceback.print_exc() return False def _create_current_preset_snapshot(self, preset_id: str) -> Dict[str, Any]: """ 创建当前配置的预设快照 Args: preset_id: 预设ID Returns: 预设数据字典 """ try: # 获取当前插件配置 preset_data = { 'name': preset_id, 'category': 'terrain', # 默认类别 'description': '用户自定义预设', 'parameters': {}, 'timestamp': time.time(), 'is_default': False } # 添加地形管理器配置 if self.plugin.terrain_manager: tm = self.plugin.terrain_manager preset_data['parameters'].update({ 'terrain_size': tm.terrain_size, 'terrain_scale': tm.terrain_scale, 'height_scale': tm.height_scale }) # 添加噪声生成器配置 if self.plugin.noise_generator: ng = self.plugin.noise_generator preset_data['parameters'].update({ 'noise_seed': ng.seed, 'noise_scale': ng.fractal_params.get('scale', 50.0), 'noise_octaves': ng.fractal_params.get('octaves', 6), 'noise_persistence': ng.fractal_params.get('persistence', 0.5), 'noise_lacunarity': ng.fractal_params.get('lacunarity', 2.0) }) # 添加高度图生成器配置 if self.plugin.heightmap_generator: hg = self.plugin.heightmap_generator preset_data['parameters'].update({ 'heightmap_scale': hg.scale, 'heightmap_octaves': hg.octaves, 'heightmap_persistence': hg.persistence, 'heightmap_lacunarity': hg.lacunarity }) # 添加生物群落生成器配置 if self.plugin.biome_generator: bg = self.plugin.biome_generator preset_data['parameters'].update({ 'biome_seed': bg.seed, 'humidity_scale': bg.distribution_params.get('humidity_scale', 20.0), 'temperature_scale': bg.distribution_params.get('temperature_scale', 20.0) }) # 添加侵蚀处理器配置 if self.plugin.erosion_processor: ep = self.plugin.erosion_processor preset_data['parameters'].update({ 'erosion_seed': ep.seed, 'water_erosion_rate': ep.erosion_params['water_erosion'].get('rate', 0.1), 'thermal_erosion_rate': ep.erosion_params['thermal_erosion'].get('rate', 0.05), 'wind_erosion_rate': ep.erosion_params['wind_erosion'].get('rate', 0.02) }) # 添加植被生成器配置 if self.plugin.vegetation_generator: vg = self.plugin.vegetation_generator preset_data['parameters'].update({ 'vegetation_seed': vg.seed, 'density_scale': vg.vegetation_params.get('density_scale', 10.0), 'clumping_factor': vg.vegetation_params.get('clumping_factor', 0.7) }) return preset_data except Exception as e: print(f"✗ 预设快照创建失败: {e}") import traceback traceback.print_exc() return { 'name': preset_id, 'category': 'terrain', 'description': '默认预设', 'parameters': {}, 'timestamp': time.time(), 'is_default': False } def load_preset(self, preset_id: str) -> Optional[Dict[str, Any]]: """ 加载预设 Args: preset_id: 预设ID Returns: 预设数据字典或None """ try: if not self.enabled: print("✗ 预设管理器未启用") return None if preset_id not in self.presets: print(f"✗ 预设不存在: {preset_id}") return None preset_data = self.presets[preset_id] # 更新统计信息 self.stats['presets_applied'] += 1 self.current_preset = preset_id print(f"✓ 预设 '{preset_id}' 加载完成") return preset_data except Exception as e: print(f"✗ 预设加载失败: {e}") import traceback traceback.print_exc() return None def apply_preset(self, preset_id: str) -> bool: """ 应用预设到当前配置 Args: preset_id: 预设ID Returns: 是否应用成功 """ try: if not self.enabled: print("✗ 预设管理器未启用") return False preset_data = self.load_preset(preset_id) if preset_data is None: return False parameters = preset_data.get('parameters', {}) # 应用参数到各个组件 self._apply_parameters_to_components(parameters) # 更新统计信息 self.stats['presets_applied'] += 1 self.current_preset = preset_id print(f"✓ 预设 '{preset_id}' 应用完成") return True except Exception as e: print(f"✗ 预设应用失败: {e}") import traceback traceback.print_exc() return False def _apply_parameters_to_components(self, parameters: Dict[str, Any]): """ 应用参数到各个组件 Args: parameters: 参数字典 """ try: # 应用到地形管理器 if self.plugin.terrain_manager and 'terrain_size' in parameters: self.plugin.terrain_manager.terrain_size = parameters['terrain_size'] if self.plugin.terrain_manager and 'terrain_scale' in parameters: self.plugin.terrain_manager.terrain_scale = parameters['terrain_scale'] if self.plugin.terrain_manager and 'height_scale' in parameters: self.plugin.terrain_manager.height_scale = parameters['height_scale'] # 应用到噪声生成器 if self.plugin.noise_generator and 'noise_seed' in parameters: self.plugin.noise_generator.set_seed(parameters['noise_seed']) if self.plugin.noise_generator and 'noise_scale' in parameters: self.plugin.noise_generator.set_fractal_parameters( scale=parameters['noise_scale'] ) # 应用到高度图生成器 if self.plugin.heightmap_generator and 'heightmap_scale' in parameters: self.plugin.heightmap_generator.set_scale(parameters['heightmap_scale']) if self.plugin.heightmap_generator and 'heightmap_octaves' in parameters: self.plugin.heightmap_generator.set_octaves(parameters['heightmap_octaves']) # 应用到生物群落生成器 if self.plugin.biome_generator and 'biome_seed' in parameters: self.plugin.biome_generator.set_seed(parameters['biome_seed']) # 应用到侵蚀处理器 if self.plugin.erosion_processor and 'erosion_seed' in parameters: self.plugin.erosion_processor.set_seed(parameters['erosion_seed']) # 应用到植被生成器 if self.plugin.vegetation_generator and 'vegetation_seed' in parameters: self.plugin.vegetation_generator.set_seed(parameters['vegetation_seed']) # 应用到地形编辑器 if (self.plugin.terrain_editor and hasattr(self.plugin, 'terrain_editor') and self.plugin.terrain_editor): editor = self.plugin.terrain_editor if 'brush_size' in parameters: editor.set_brush_size(parameters['brush_size']) if 'brush_strength' in parameters: editor.set_brush_strength(parameters['brush_strength']) except Exception as e: print(f"✗ 参数应用失败: {e}") import traceback traceback.print_exc() def delete_preset(self, preset_id: str) -> bool: """ 删除预设 Args: preset_id: 预设ID Returns: 是否删除成功 """ try: if not self.enabled: print("✗ 预设管理器未启用") return False if preset_id not in self.presets: print(f"✗ 预设不存在: {preset_id}") return False # 检查是否为默认预设 if self.presets[preset_id].get('is_default', False): print(f"✗ 无法删除默认预设: {preset_id}") return False # 从内存中删除 del self.presets[preset_id] # 从文件系统中删除 presets_dir = self.preset_config['presets_directory'] filepath = os.path.join(presets_dir, f"{preset_id}.json") if os.path.exists(filepath): os.remove(filepath) # 如果删除的是当前预设,清空当前预设 if self.current_preset == preset_id: self.current_preset = None print(f"✓ 预设 '{preset_id}' 删除完成") return True except Exception as e: print(f"✗ 预设删除失败: {e}") import traceback traceback.print_exc() return False def list_presets(self, category: str = None) -> List[str]: """ 列出预设 Args: category: 预设类别(如果为None,则列出所有预设) Returns: 预设ID列表 """ try: if category is None: return list(self.presets.keys()) else: return [preset_id for preset_id, preset_data in self.presets.items() if preset_data.get('category') == category] except Exception as e: print(f"✗ 预设列表获取失败: {e}") import traceback traceback.print_exc() return [] def get_preset_info(self, preset_id: str) -> Optional[Dict[str, Any]]: """ 获取预设信息 Args: preset_id: 预设ID Returns: 预设信息字典或None """ try: if preset_id not in self.presets: return None preset_data = self.presets[preset_id] return { 'name': preset_data.get('name', preset_id), 'category': preset_data.get('category', 'unknown'), 'description': preset_data.get('description', ''), 'timestamp': preset_data.get('timestamp', 0), 'is_default': preset_data.get('is_default', False), 'parameters_count': len(preset_data.get('parameters', {})) } except Exception as e: print(f"✗ 预设信息获取失败: {e}") import traceback traceback.print_exc() return None def get_preset_categories(self) -> Dict[str, Dict[str, str]]: """ 获取预设类别 Returns: 预设类别字典 """ return self.preset_categories.copy() def create_preset_category(self, category_id: str, name: str, description: str = '') -> bool: """ 创建预设类别 Args: category_id: 类别ID name: 类别名称 description: 类别描述 Returns: 是否创建成功 """ try: self.preset_categories[category_id] = { 'name': name, 'description': description } print(f"✓ 预设类别 '{name}' 创建完成") return True except Exception as e: print(f"✗ 预设类别创建失败: {e}") import traceback traceback.print_exc() return False def export_preset(self, preset_id: str, filepath: str) -> bool: """ 导出预设到文件 Args: preset_id: 预设ID filepath: 导出文件路径 Returns: 是否导出成功 """ try: if preset_id not in self.presets: print(f"✗ 预设不存在: {preset_id}") return False preset_data = self.presets[preset_id] # 保存到指定文件 with open(filepath, 'w', encoding='utf-8') as f: json.dump(preset_data, f, ensure_ascii=False, indent=2) print(f"✓ 预设 '{preset_id}' 导出到 {filepath}") return True except Exception as e: print(f"✗ 预设导出失败: {e}") import traceback traceback.print_exc() return False def import_preset(self, filepath: str, preset_id: str = None) -> bool: """ 从文件导入预设 Args: filepath: 导入文件路径 preset_id: 预设ID(如果为None,则使用文件名) Returns: 是否导入成功 """ try: # 读取预设文件 with open(filepath, 'r', encoding='utf-8') as f: preset_data = json.load(f) # 确定预设ID if preset_id is None: preset_id = os.path.splitext(os.path.basename(filepath))[0] # 保存预设 self.presets[preset_id] = preset_data # 保存到预设目录 presets_dir = self.preset_config['presets_directory'] save_filepath = os.path.join(presets_dir, f"{preset_id}.json") with open(save_filepath, 'w', encoding='utf-8') as f: json.dump(preset_data, f, ensure_ascii=False, indent=2) print(f"✓ 预设从 {filepath} 导入完成,ID: {preset_id}") return True except Exception as e: print(f"✗ 预设导入失败: {e}") import traceback traceback.print_exc() return False def set_preset_config(self, config: Dict[str, Any]): """ 设置预设配置 Args: config: 配置字典 """ self.preset_config.update(config) print(f"✓ 预设配置已更新: {self.preset_config}") def get_stats(self) -> Dict[str, Any]: """ 获取统计信息 Returns: 统计信息字典 """ return self.stats.copy() def reset_stats(self): """重置统计信息""" self.stats = { 'presets_loaded': 0, 'presets_saved': 0, 'presets_applied': 0, 'total_load_time': 0.0, 'total_save_time': 0.0 } print("✓ 预设管理器统计信息已重置") def get_current_preset(self) -> Optional[str]: """ 获取当前预设 Returns: 当前预设ID或None """ return self.current_preset def set_current_preset(self, preset_id: str): """ 设置当前预设 Args: preset_id: 预设ID """ if preset_id in self.presets: self.current_preset = preset_id print(f"✓ 当前预设设置为: {preset_id}") else: print(f"✗ 预设不存在: {preset_id}") def create_terrain_preset_from_current(self, preset_name: str, description: str = '') -> str: """ 从当前地形创建预设 Args: preset_name: 预设名称 description: 预设描述 Returns: 预设ID """ try: # 生成预设ID preset_id = preset_name.lower().replace(' ', '_') # 创建预设数据 preset_data = { 'name': preset_name, 'category': 'terrain', 'description': description, 'parameters': {}, 'timestamp': time.time(), 'is_default': False } # 获取当前地形参数 if self.plugin.terrain_manager: tm = self.plugin.terrain_manager preset_data['parameters'].update({ 'terrain_size': tm.terrain_size, 'terrain_scale': tm.terrain_scale, 'height_scale': tm.height_scale, 'seed': getattr(tm, 'seed', 12345) }) # 保存预设 self.save_preset(preset_id, preset_data) print(f"✓ 从当前地形创建预设: {preset_name}") return preset_id except Exception as e: print(f"✗ 从当前地形创建预设失败: {e}") import traceback traceback.print_exc() return '' def batch_apply_presets(self, preset_ids: List[str]) -> bool: """ 批量应用预设 Args: preset_ids: 预设ID列表 Returns: 是否应用成功 """ try: if not self.enabled: print("✗ 预设管理器未启用") return False success_count = 0 for preset_id in preset_ids: if self.apply_preset(preset_id): success_count += 1 print(f"✓ 批量应用预设完成: {success_count}/{len(preset_ids)} 个预设应用成功") return success_count == len(preset_ids) except Exception as e: print(f"✗ 批量应用预设失败: {e}") import traceback traceback.print_exc() return False def search_presets(self, keyword: str, category: str = None) -> List[str]: """ 搜索预设 Args: keyword: 搜索关键词 category: 预设类别(可选) Returns: 匹配的预设ID列表 """ try: matching_presets = [] for preset_id, preset_data in self.presets.items(): # 检查类别筛选 if category and preset_data.get('category') != category: continue # 检查关键词匹配 if (keyword.lower() in preset_data.get('name', '').lower() or keyword.lower() in preset_data.get('description', '').lower()): matching_presets.append(preset_id) return matching_presets except Exception as e: print(f"✗ 预设搜索失败: {e}") import traceback traceback.print_exc() return [] def duplicate_preset(self, source_preset_id: str, new_preset_id: str, new_name: str = None) -> bool: """ 复制预设 Args: source_preset_id: 源预设ID new_preset_id: 新预设ID new_name: 新预设名称(如果为None,则使用源预设名称) Returns: 是否复制成功 """ try: if source_preset_id not in self.presets: print(f"✗ 源预设不存在: {source_preset_id}") return False # 复制预设数据 preset_data = self.presets[source_preset_id].copy() # 更新名称 if new_name: preset_data['name'] = new_name else: preset_data['name'] = f"{preset_data['name']} (副本)" # 更新时间戳 preset_data['timestamp'] = time.time() # 标记为非默认预设 preset_data['is_default'] = False # 保存新预设 return self.save_preset(new_preset_id, preset_data) except Exception as e: print(f"✗ 预设复制失败: {e}") import traceback traceback.print_exc() return False def update_preset_parameters(self, preset_id: str, parameters: Dict[str, Any]) -> bool: """ 更新预设参数 Args: preset_id: 预设ID parameters: 要更新的参数 Returns: 是否更新成功 """ try: if preset_id not in self.presets: print(f"✗ 预设不存在: {preset_id}") return False # 更新参数 self.presets[preset_id]['parameters'].update(parameters) # 更新时间戳 self.presets[preset_id]['timestamp'] = time.time() # 保存到文件 presets_dir = self.preset_config['presets_directory'] filepath = os.path.join(presets_dir, f"{preset_id}.json") with open(filepath, 'w', encoding='utf-8') as f: json.dump(self.presets[preset_id], f, ensure_ascii=False, indent=2) print(f"✓ 预设 '{preset_id}' 参数更新完成") return True except Exception as e: print(f"✗ 预设参数更新失败: {e}") import traceback traceback.print_exc() return False def get_preset_parameters(self, preset_id: str) -> Optional[Dict[str, Any]]: """ 获取预设参数 Args: preset_id: 预设ID Returns: 参数字典或None """ try: if preset_id not in self.presets: return None return self.presets[preset_id].get('parameters', {}).copy() except Exception as e: print(f"✗ 预设参数获取失败: {e}") import traceback traceback.print_exc() return None def validate_preset(self, preset_id: str) -> bool: """ 验证预设完整性 Args: preset_id: 预设ID Returns: 预设是否有效 """ try: if preset_id not in self.presets: return False preset_data = self.presets[preset_id] # 检查必需字段 required_fields = ['name', 'category', 'parameters'] for field in required_fields: if field not in preset_data: print(f"✗ 预设 '{preset_id}' 缺少必需字段: {field}") return False # 检查类别有效性 if preset_data['category'] not in self.preset_categories: print(f"✗ 预设 '{preset_id}' 包含无效类别: {preset_data['category']}") return False return True except Exception as e: print(f"✗ 预设验证失败: {e}") import traceback traceback.print_exc() return False def backup_presets(self, backup_dir: str = 'presets_backup') -> bool: """ 备份所有预设 Args: backup_dir: 备份目录 Returns: 是否备份成功 """ try: if not os.path.exists(backup_dir): os.makedirs(backup_dir) backup_count = 0 for preset_id, preset_data in self.presets.items(): try: filepath = os.path.join(backup_dir, f"{preset_id}.json") with open(filepath, 'w', encoding='utf-8') as f: json.dump(preset_data, f, ensure_ascii=False, indent=2) backup_count += 1 except Exception as e: print(f"✗ 预设 '{preset_id}' 备份失败: {e}") print(f"✓ 预设备份完成: {backup_count}/{len(self.presets)} 个预设已备份到 {backup_dir}") return True except Exception as e: print(f"✗ 预设备份失败: {e}") import traceback traceback.print_exc() return False def restore_presets(self, backup_dir: str = 'presets_backup') -> bool: """ 从备份恢复预设 Args: backup_dir: 备份目录 Returns: 是否恢复成功 """ try: if not os.path.exists(backup_dir): print(f"✗ 备份目录不存在: {backup_dir}") return False restore_count = 0 for filename in os.listdir(backup_dir): if filename.endswith('.json'): try: filepath = os.path.join(backup_dir, filename) with open(filepath, 'r', encoding='utf-8') as f: preset_data = json.load(f) preset_id = os.path.splitext(filename)[0] self.presets[preset_id] = preset_data restore_count += 1 except Exception as e: print(f"✗ 预设文件 '{filename}' 恢复失败: {e}") print(f"✓ 预设恢复完成: {restore_count} 个预设已从 {backup_dir} 恢复") return True except Exception as e: print(f"✗ 预设恢复失败: {e}") import traceback traceback.print_exc() return False