EG/plugins/user/particle_system/editor/preset_manager.py
2025-12-12 16:16:15 +08:00

606 lines
20 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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