""" 预设管理器 负责粒子效果预设的保存、加载和管理 """ import os import json from typing import Dict, List, Optional, Any from datetime import datetime from panda3d.core import Vec3 class PresetManager: """ 预设管理器 管理粒子效果的预设配置 """ def __init__(self, plugin_dir: str = None): """ 初始化预设管理器 Args: plugin_dir: 插件目录路径 """ if plugin_dir is None: plugin_dir = os.path.dirname(os.path.dirname(__file__)) self.plugin_dir = plugin_dir self.presets_dir = os.path.join(plugin_dir, "presets") # 确保预设目录存在 os.makedirs(self.presets_dir, exist_ok=True) # 预设缓存 self.presets_cache: Dict[str, Dict] = {} self.cache_dirty = True # 内置预设 self.builtin_presets = self._create_builtin_presets() print(f"✓ 预设管理器初始化完成,预设目录: {self.presets_dir}") def _create_builtin_presets(self) -> Dict[str, Dict]: """创建内置预设""" return { "经典火焰": { "metadata": { "name": "经典火焰", "description": "经典的火焰效果", "author": "EG Team", "version": "1.0", "created": "2024-10-28", "builtin": True }, "config": { "emitter_type": "fire", "emission_rate": 50, "particle_lifetime": 2.0, "start_color": [1.0, 0.3, 0.0], "end_color": [1.0, 0.0, 0.0], "start_size": 0.2, "end_size": 0.8, "velocity": [0, 0, 2], "velocity_variation": 0.5, "gravity_scale": -0.2, "air_resistance": 0.02, "emitter_shape": "circle", "emitter_radius": 0.3 } }, "浓密烟雾": { "metadata": { "name": "浓密烟雾", "description": "浓密的烟雾效果", "author": "EG Team", "version": "1.0", "created": "2024-10-28", "builtin": True }, "config": { "emitter_type": "smoke", "emission_rate": 30, "particle_lifetime": 4.0, "start_color": [0.8, 0.8, 0.8], "end_color": [0.3, 0.3, 0.3], "start_size": 0.3, "end_size": 1.5, "velocity": [0, 0, 1], "velocity_variation": 0.3, "gravity_scale": -0.1, "air_resistance": 0.05, "emitter_shape": "circle", "emitter_radius": 0.5 } }, "强力爆炸": { "metadata": { "name": "强力爆炸", "description": "强力的爆炸效果", "author": "EG Team", "version": "1.0", "created": "2024-10-28", "builtin": True }, "config": { "emitter_type": "explosion", "emission_rate": 200, "burst_count": 100, "particle_lifetime": 1.5, "start_color": [1.0, 0.8, 0.0], "end_color": [1.0, 0.2, 0.0], "start_size": 0.1, "end_size": 0.5, "velocity": [0, 0, 0], "velocity_variation": 2.0, "gravity_scale": 0.5, "duration": 0.2, "emitter_shape": "sphere", "emitter_radius": 0.1 } }, "清澈水花": { "metadata": { "name": "清澈水花", "description": "清澈的水花效果", "author": "EG Team", "version": "1.0", "created": "2024-10-28", "builtin": True }, "config": { "emitter_type": "water", "emission_rate": 80, "particle_lifetime": 3.0, "start_color": [0.2, 0.6, 1.0], "end_color": [0.1, 0.3, 0.8], "start_size": 0.1, "end_size": 0.05, "velocity": [0, 0, 3], "velocity_variation": 1.0, "gravity_scale": 2.0, "bounce_factor": 0.3, "air_resistance": 0.01, "emitter_shape": "circle", "emitter_radius": 0.2 } }, "魔法光球": { "metadata": { "name": "魔法光球", "description": "神秘的魔法光球效果", "author": "EG Team", "version": "1.0", "created": "2024-10-28", "builtin": True }, "config": { "emitter_type": "magic", "emission_rate": 40, "particle_lifetime": 3.0, "start_color": [0.5, 0.2, 1.0], "end_color": [0.8, 0.6, 1.0], "start_size": 0.15, "end_size": 0.05, "velocity": [0, 0, 0.5], "velocity_variation": 0.8, "gravity_scale": -0.5, "rotation_enabled": True, "rotation_speed": 2.0, "emitter_shape": "sphere", "emitter_radius": 0.8 } }, "飘落雪花": { "metadata": { "name": "飘落雪花", "description": "冬季飘落的雪花效果", "author": "EG Team", "version": "1.0", "created": "2024-10-28", "builtin": True }, "config": { "emitter_type": "snow", "emission_rate": 60, "particle_lifetime": 8.0, "start_color": [0.9, 0.9, 1.0], "end_color": [0.9, 0.9, 1.0], "start_size": 0.05, "end_size": 0.05, "velocity": [0, 0, -1], "velocity_variation": 0.5, "gravity_scale": 0.8, "air_resistance": 0.1, "bounce_factor": 0.2, "emitter_shape": "box", "emitter_dimensions": [20, 20, 1] } } } def save_preset(self, name: str, config: Dict[str, Any], description: str = "", author: str = "User") -> bool: """ 保存预设 Args: name: 预设名称 config: 预设配置 description: 预设描述 author: 作者 Returns: 是否保存成功 """ try: # 创建预设数据 preset_data = { "metadata": { "name": name, "description": description, "author": author, "version": "1.0", "created": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), "builtin": False }, "config": config } # 保存到文件 filename = self._sanitize_filename(name) + ".json" filepath = os.path.join(self.presets_dir, filename) with open(filepath, 'w', encoding='utf-8') as f: json.dump(preset_data, f, indent=2, ensure_ascii=False, default=self._json_serializer) # 更新缓存 self.presets_cache[name] = preset_data print(f"✓ 预设已保存: {name} -> {filepath}") return True except Exception as e: print(f"⚠ 预设保存失败 '{name}': {e}") return False def _json_serializer(self, obj): """JSON序列化器,处理特殊对象""" if isinstance(obj, Vec3): return [obj.x, obj.y, obj.z] raise TypeError(f"Object of type {type(obj)} is not JSON serializable") def load_preset(self, name: str) -> Optional[Dict[str, Any]]: """ 加载预设 Args: name: 预设名称 Returns: 预设数据,如果不存在返回None """ try: # 检查内置预设 if name in self.builtin_presets: return self.builtin_presets[name] # 检查缓存 if name in self.presets_cache and not self.cache_dirty: return self.presets_cache[name] # 从文件加载 filename = self._sanitize_filename(name) + ".json" filepath = os.path.join(self.presets_dir, filename) if os.path.exists(filepath): with open(filepath, 'r', encoding='utf-8') as f: preset_data = json.load(f) # 更新缓存 self.presets_cache[name] = preset_data self.cache_dirty = False print(f"✓ 预设已加载: {name}") return preset_data else: print(f"⚠ 预设文件不存在: {filepath}") return None except Exception as e: print(f"⚠ 预设加载失败 '{name}': {e}") return None def delete_preset(self, name: str) -> bool: """ 删除预设 Args: name: 预设名称 Returns: 是否删除成功 """ try: # 不能删除内置预设 if name in self.builtin_presets: print(f"⚠ 不能删除内置预设: {name}") return False # 删除文件 filename = self._sanitize_filename(name) + ".json" filepath = os.path.join(self.presets_dir, filename) if os.path.exists(filepath): os.remove(filepath) # 从缓存移除 if name in self.presets_cache: del self.presets_cache[name] print(f"✓ 预设已删除: {name}") return True except Exception as e: print(f"⚠ 预设删除失败 '{name}': {e}") return False def list_presets(self) -> List[str]: """ 获取所有预设名称列表 Returns: 预设名称列表 """ presets = [] # 添加内置预设 presets.extend(self.builtin_presets.keys()) # 添加用户预设 try: if os.path.exists(self.presets_dir): for filename in os.listdir(self.presets_dir): if filename.endswith('.json'): name = filename[:-5] # 移除.json扩展名 # 尝试从文件名恢复原始名称 original_name = self._restore_filename(name) if original_name not in presets: presets.append(original_name) except Exception as e: print(f"⚠ 列出预设失败: {e}") return sorted(presets) def get_preset_info(self, name: str) -> Optional[Dict[str, Any]]: """ 获取预设信息 Args: name: 预设名称 Returns: 预设元数据 """ preset_data = self.load_preset(name) if preset_data and 'metadata' in preset_data: return preset_data['metadata'] return None def get_preset_config(self, name: str) -> Optional[Dict[str, Any]]: """ 获取预设配置 Args: name: 预设名称 Returns: 预设配置 """ preset_data = self.load_preset(name) if preset_data and 'config' in preset_data: return preset_data['config'] return None def export_preset(self, name: str, filepath: str) -> bool: """ 导出预设到文件 Args: name: 预设名称 filepath: 导出文件路径 Returns: 是否导出成功 """ try: preset_data = self.load_preset(name) if not preset_data: print(f"⚠ 预设不存在: {name}") return False with open(filepath, 'w', encoding='utf-8') as f: json.dump(preset_data, f, indent=2, ensure_ascii=False, default=self._json_serializer) print(f"✓ 预设已导出: {name} -> {filepath}") return True except Exception as e: print(f"⚠ 预设导出失败 '{name}': {e}") return False def import_preset(self, filepath: str) -> bool: """ 从文件导入预设 Args: filepath: 预设文件路径 Returns: 是否导入成功 """ try: if not os.path.exists(filepath): print(f"⚠ 预设文件不存在: {filepath}") return False with open(filepath, 'r', encoding='utf-8') as f: preset_data = json.load(f) # 验证预设数据格式 if not self._validate_preset_data(preset_data): print(f"⚠ 预设文件格式无效: {filepath}") return False # 获取预设名称 name = preset_data['metadata']['name'] # 保存预设 return self.save_preset( name, preset_data['config'], preset_data['metadata'].get('description', ''), preset_data['metadata'].get('author', 'Unknown') ) except Exception as e: print(f"⚠ 预设导入失败: {e}") return False def _sanitize_filename(self, name: str) -> str: """清理文件名,移除非法字符""" import re # 移除或替换非法字符 sanitized = re.sub(r'[<>:"/\\|?*]', '_', name) # 限制长度 if len(sanitized) > 100: sanitized = sanitized[:100] return sanitized def _restore_filename(self, filename: str) -> str: """尝试恢复原始文件名""" # 简单实现:直接返回文件名 # 实际应用中可能需要更复杂的映射 return filename def _validate_preset_data(self, data: Dict) -> bool: """验证预设数据格式""" try: # 检查必需字段 if 'metadata' not in data or 'config' not in data: return False metadata = data['metadata'] if 'name' not in metadata: return False # 基本验证通过 return True except: return False def clear_cache(self): """清空预设缓存""" self.presets_cache.clear() self.cache_dirty = True print("✓ 预设缓存已清空") def get_cache_stats(self) -> Dict[str, Any]: """获取缓存统计信息""" return { 'cached_presets': len(self.presets_cache), 'builtin_presets': len(self.builtin_presets), 'cache_dirty': self.cache_dirty } def refresh_presets(self): """刷新预设列表""" self.cache_dirty = True print("✓ 预设列表已刷新") def create_preset_from_config(self, name: str, config: Dict[str, Any], description: str = "", author: str = "User") -> bool: """ 从配置创建预设 Args: name: 预设名称 config: 预设配置 description: 预设描述 author: 作者 Returns: 是否创建成功 """ return self.save_preset(name, config, description, author) def duplicate_preset(self, source_name: str, new_name: str) -> bool: """ 复制预设 Args: source_name: 源预设名称 new_name: 新预设名称 Returns: 是否复制成功 """ preset_data = self.load_preset(source_name) if not preset_data: print(f"⚠ 源预设不存在: {source_name}") return False # 更新元数据 preset_data['metadata']['name'] = new_name preset_data['metadata']['created'] = datetime.now().strftime("%Y-%m-%d %H:%M:%S") preset_data['metadata']['builtin'] = False return self.save_preset( new_name, preset_data['config'], preset_data['metadata'].get('description', ''), preset_data['metadata'].get('author', 'User') ) def update_preset_description(self, name: str, description: str) -> bool: """ 更新预设描述 Args: name: 预设名称 description: 新描述 Returns: 是否更新成功 """ preset_data = self.load_preset(name) if not preset_data: print(f"⚠ 预设不存在: {name}") return False if preset_data['metadata'].get('builtin', False): print(f"⚠ 不能修改内置预设: {name}") return False preset_data['metadata']['description'] = description return self.save_preset( name, preset_data['config'], description, preset_data['metadata'].get('author', 'User') ) def search_presets(self, keyword: str) -> List[str]: """ 搜索预设 Args: keyword: 搜索关键词 Returns: 匹配的预设名称列表 """ all_presets = self.list_presets() keyword = keyword.lower() matching_presets = [] for preset_name in all_presets: # 检查名称和描述 if keyword in preset_name.lower(): matching_presets.append(preset_name) else: info = self.get_preset_info(preset_name) if info and keyword in info.get('description', '').lower(): matching_presets.append(preset_name) return matching_presets