606 lines
20 KiB
Python
606 lines
20 KiB
Python
"""
|
||
预设管理器
|
||
负责粒子效果预设的保存、加载和管理
|
||
"""
|
||
|
||
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
|
||
|