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

478 lines
18 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.

"""
导航网格编辑器
提供可视化编辑导航网格的功能
"""
from typing import List, Optional, Dict, Any
from panda3d.core import Vec3, Point3, KeyboardButton, MouseWatcher
class NavMeshEditor:
"""
导航网格编辑器
提供可视化编辑导航网格的功能
"""
def __init__(self, world, navmesh_manager):
"""
初始化导航网格编辑器
Args:
world: 3D世界对象
navmesh_manager: 导航网格管理器
"""
self.world = world
self.navmesh_manager = navmesh_manager
self.visible = False
# 编辑器状态
self.edit_mode = False
self.selected_polygon = None
self.edit_tool = "select" # select, add, delete, paint
self.brush_size = 1.0
# 编辑器GUI元素
self.gui_elements = []
self.tool_buttons = {}
self.property_controls = {}
# 鼠标交互
self.mouse_watcher = None
self.last_mouse_pos = None
# 编辑历史(用于撤销/重做)
self.edit_history = []
self.history_index = -1
self.max_history_size = 50
print("✓ 导航网格编辑器初始化完成")
def toggle_visibility(self):
"""切换编辑器可见性"""
if self.visible:
self.hide()
else:
self.show()
def show(self):
"""显示编辑器"""
if self.visible:
return
self.visible = True
self._create_editor_gui()
print("✓ 导航网格编辑器已显示")
def hide(self):
"""隐藏编辑器"""
if not self.visible:
return
self.visible = False
self._cleanup_editor_gui()
print("✓ 导航网格编辑器已隐藏")
def _create_editor_gui(self):
"""创建编辑器GUI"""
try:
if not hasattr(self.world, 'gui_manager') or not self.world.gui_manager:
print("⚠ GUI管理器不可用无法创建编辑器界面")
return
gui_manager = self.world.gui_manager
# 创建编辑器面板标题
panel = gui_manager.createGUIButton(
pos=(0.7, 0, 0.95),
text="导航网格编辑器",
size=0.06
)
self.gui_elements.append(panel)
# 创建工具按钮
tools = [
("选择", "select"),
("添加", "add"),
("删除", "delete"),
("绘制", "paint")
]
for i, (name, tool) in enumerate(tools):
button = gui_manager.createGUIButton(
pos=(0.7, 0, 0.88 - i * 0.06),
text=name,
size=0.04
)
self.gui_elements.append(button)
self.tool_buttons[tool] = button
self.world.accept(f"navmesh_tool_{tool}",
lambda t=tool: self.set_edit_tool(t))
# 创建编辑功能按钮
functions = [
("生成网格", self.generate_navmesh),
("加载网格", self.load_navmesh),
("保存网格", self.save_navmesh),
("清除网格", self.clear_navmesh),
("撤销", self.undo),
("重做", self.redo)
]
for i, (name, callback) in enumerate(functions):
button = gui_manager.createGUIButton(
pos=(0.82, 0, 0.88 - i * 0.06),
text=name,
size=0.04
)
self.gui_elements.append(button)
self.world.accept(f"navmesh_func_{name}", callback)
# 创建属性控制
self._create_property_controls()
# 显示网格
self.navmesh_manager.toggle_visibility()
except Exception as e:
print(f"⚠ 编辑器GUI创建失败: {e}")
def _create_property_controls(self):
"""创建属性控制面板"""
if not hasattr(self.world, 'gui_manager') or not self.world.gui_manager:
return
gui_manager = self.world.gui_manager
# 网格属性
properties = [
("网格大小", "cell_size", 0.1, 2.0, 0.1),
("网格高度", "cell_height", 0.1, 1.0, 0.1),
("代理高度", "agent_height", 0.5, 3.0, 0.1),
("代理半径", "agent_radius", 0.1, 2.0, 0.1),
("最大坡度", "max_slope", 0.0, 89.0, 1.0),
("笔刷大小", "brush_size", 0.1, 5.0, 0.1)
]
for i, (name, prop, min_val, max_val, step) in enumerate(properties):
y_pos = 0.5 - i * 0.055
# 属性标签
label = gui_manager.createGUIButton(
pos=(0.75, 0, y_pos),
text=f"{name}:",
size=0.03
)
self.gui_elements.append(label)
# 属性值显示
value_label = gui_manager.createGUIButton(
pos=(0.88, 0, y_pos),
text=f"{getattr(self.navmesh_manager, prop, 0.0):.2f}",
size=0.03
)
self.gui_elements.append(value_label)
# 减少按钮
dec_button = gui_manager.createGUIButton(
pos=(0.83, 0, y_pos),
text="-",
size=0.03
)
self.gui_elements.append(dec_button)
# 增加按钮
inc_button = gui_manager.createGUIButton(
pos=(0.93, 0, y_pos),
text="+",
size=0.03
)
self.gui_elements.append(inc_button)
# 注册事件
self.world.accept(f"dec_{prop}", lambda p=prop, s=step: self._decrease_property(p, s))
self.world.accept(f"inc_{prop}", lambda p=prop, s=step: self._increase_property(p, s))
# 存储控制信息
self.property_controls[prop] = {
'name': name,
'min': min_val,
'max': max_val,
'step': step,
'value_label': value_label,
'dec_button': dec_button,
'inc_button': inc_button
}
def _cleanup_editor_gui(self):
"""清理编辑器GUI"""
try:
if hasattr(self.world, 'gui_manager') and self.world.gui_manager:
gui_manager = self.world.gui_manager
for element in self.gui_elements:
gui_manager.deleteGUIElement(element)
# 清理事件监听
tools = ["select", "add", "delete", "paint"]
for tool in tools:
self.world.ignore(f"navmesh_tool_{tool}")
functions = ["生成网格", "加载网格", "保存网格", "清除网格", "撤销", "重做"]
for func in functions:
self.world.ignore(f"navmesh_func_{func}")
# 清理属性控制事件
properties = ["cell_size", "cell_height", "agent_height", "agent_radius",
"max_slope", "brush_size"]
for prop in properties:
self.world.ignore(f"dec_{prop}")
self.world.ignore(f"inc_{prop}")
self.gui_elements.clear()
self.tool_buttons.clear()
self.property_controls.clear()
except Exception as e:
print(f"⚠ 编辑器GUI清理失败: {e}")
def _decrease_property(self, prop_name: str, step: float):
"""减少属性值"""
if hasattr(self.navmesh_manager, prop_name):
current_value = getattr(self.navmesh_manager, prop_name)
control = self.property_controls[prop_name]
new_value = max(control['min'], current_value - step)
setattr(self.navmesh_manager, prop_name, new_value)
# 更新显示
if 'value_label' in control:
control['value_label'].setText(f"{new_value:.2f}")
def _increase_property(self, prop_name: str, step: float):
"""增加属性值"""
if hasattr(self.navmesh_manager, prop_name):
current_value = getattr(self.navmesh_manager, prop_name)
control = self.property_controls[prop_name]
new_value = min(control['max'], current_value + step)
setattr(self.navmesh_manager, prop_name, new_value)
# 更新显示
if 'value_label' in control:
control['value_label'].setText(f"{new_value:.2f}")
def set_edit_tool(self, tool: str):
"""设置编辑工具"""
self.edit_tool = tool
print(f"✓ 切换到 {tool} 工具")
# 更新按钮状态
for tool_name, button in self.tool_buttons.items():
if tool_name == tool:
# 高亮选中工具
button.setColor(0.5, 1.0, 0.5, 1.0) # 绿色
else:
# 恢复默认颜色
button.setColor(1.0, 1.0, 1.0, 1.0) # 白色
def generate_navmesh(self):
"""生成导航网格"""
if self.navmesh_manager:
success = self.navmesh_manager.generate_from_scene()
if success:
print("✓ 导航网格生成完成")
else:
print("✗ 导航网格生成失败")
def load_navmesh(self):
"""加载导航网格"""
# 简化实现:加载测试网格
if self.navmesh_manager:
self.navmesh_manager._create_test_navmesh()
print("✓ 导航网格已加载(测试数据)")
def save_navmesh(self):
"""保存导航网格"""
print("⚠ 保存功能需要实现文件对话框")
def clear_navmesh(self):
"""清除导航网格"""
if self.navmesh_manager:
self.navmesh_manager.polygons.clear()
self.navmesh_manager._update_navmesh_visualization()
print("✓ 导航网格已清除")
def undo(self):
"""撤销操作"""
if self.history_index >= 0:
self.history_index -= 1
print("✓ 撤销操作")
else:
print("⚠ 没有可撤销的操作")
def redo(self):
"""重做操作"""
if self.history_index < len(self.edit_history) - 1:
self.history_index += 1
print("✓ 重做操作")
else:
print("⚠ 没有可重做的操作")
def _add_to_history(self, operation: Dict[str, Any]):
"""添加操作到历史记录"""
# 移除历史记录中超过当前索引的部分
if self.history_index < len(self.edit_history) - 1:
self.edit_history = self.edit_history[:self.history_index + 1]
# 添加新操作
self.edit_history.append(operation)
# 限制历史记录大小
if len(self.edit_history) > self.max_history_size:
self.edit_history.pop(0)
else:
self.history_index += 1
def select_polygon(self, polygon_id: int):
"""选择多边形"""
self.selected_polygon = polygon_id
print(f"✓ 选择多边形: {polygon_id}")
def add_polygon(self, vertices: List[Point3]):
"""添加多边形"""
if self.navmesh_manager:
polygon_id = self.navmesh_manager.add_polygon(vertices)
print(f"✓ 添加多边形: {polygon_id}")
# 添加到历史记录
operation = {
'type': 'add_polygon',
'polygon_id': polygon_id
}
self._add_to_history(operation)
def delete_polygon(self, polygon_id: int):
"""删除多边形"""
if self.navmesh_manager:
success = self.navmesh_manager.remove_polygon(polygon_id)
if success:
print(f"✓ 删除多边形: {polygon_id}")
# 添加到历史记录
operation = {
'type': 'delete_polygon',
'polygon_id': polygon_id
}
self._add_to_history(operation)
else:
print(f"⚠ 无法删除多边形: {polygon_id}")
def paint_walkable_area(self, center: Point3):
"""绘制可行走区域"""
print(f"✓ 在 {center} 附近绘制可行走区域")
def toggle_edit_mode(self):
"""切换编辑模式"""
self.edit_mode = not self.edit_mode
if self.edit_mode:
print("✓ 进入编辑模式")
else:
print("✓ 退出编辑模式")
def set_brush_size(self, size: float):
"""设置笔刷大小"""
self.brush_size = max(0.1, size)
print(f"✓ 笔刷大小设置为: {self.brush_size:.2f}")
def get_editor_state(self) -> Dict[str, Any]:
"""获取编辑器状态"""
return {
'visible': self.visible,
'edit_mode': self.edit_mode,
'selected_tool': self.edit_tool,
'selected_polygon': self.selected_polygon,
'brush_size': self.brush_size,
'polygon_count': len(self.navmesh_manager.polygons) if self.navmesh_manager else 0
}
def set_editor_state(self, state: Dict[str, Any]):
"""设置编辑器状态"""
try:
if 'edit_mode' in state:
self.edit_mode = state['edit_mode']
if 'selected_tool' in state:
self.set_edit_tool(state['selected_tool'])
if 'brush_size' in state:
self.set_brush_size(state['brush_size'])
print("✓ 编辑器状态已恢复")
except Exception as e:
print(f"⚠ 恢复编辑器状态失败: {e}")
def handle_mouse_click(self, mouse_x: float, mouse_y: float):
"""处理鼠标点击事件"""
if not self.edit_mode:
return
# 将屏幕坐标转换为世界坐标(简化实现)
world_x = mouse_x * 20 # 假设场景大小为20x20
world_z = mouse_y * 20
click_point = Point3(world_x, 0, world_z)
if self.edit_tool == "add":
# 添加多边形(简化为正方形)
half_size = 0.5
vertices = [
Point3(world_x - half_size, 0, world_z - half_size),
Point3(world_x + half_size, 0, world_z - half_size),
Point3(world_x + half_size, 0, world_z + half_size),
Point3(world_x - half_size, 0, world_z + half_size)
]
self.add_polygon(vertices)
elif self.edit_tool == "delete":
# 删除多边形
if self.navmesh_manager:
polygon = self.navmesh_manager.get_polygon_containing_point(click_point)
if polygon:
self.delete_polygon(polygon.polygon_id)
elif self.edit_tool == "paint":
# 绘制可行走区域
self.paint_walkable_area(click_point)
elif self.edit_tool == "select":
# 选择多边形
if self.navmesh_manager:
polygon = self.navmesh_manager.get_polygon_containing_point(click_point)
if polygon:
self.select_polygon(polygon.polygon_id)
def update(self, dt: float):
"""更新编辑器状态"""
# 处理键盘输入
if self.world.mouseWatcherNode.hasMouse():
mouse_pos = self.world.mouseWatcherNode.getMouse()
# 检查鼠标按键
if self.world.mouseWatcherNode.isButtonDown(MouseWatcher.getGlobalPointer()):
if self.last_mouse_pos is None or \
(mouse_pos - self.last_mouse_pos).length() > 0.01:
self.handle_mouse_click(mouse_pos.x, mouse_pos.y)
self.last_mouse_pos = mouse_pos
# 检查键盘快捷键
if self.world.mouseWatcherNode.isButtonDown(KeyboardButton.asciiKey('e')):
self.toggle_edit_mode()
def cleanup(self):
"""清理编辑器资源"""
self.hide()
# 清理历史记录
self.edit_history.clear()
self.history_index = -1
# 清理状态
self.selected_polygon = None
self.edit_mode = False