撤销重做(仅使用拖拽时)
This commit is contained in:
parent
040fffd34e
commit
ddeb40ea54
570
core/Command_System.py
Normal file
570
core/Command_System.py
Normal file
@ -0,0 +1,570 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from collections import deque
|
||||
from typing import List
|
||||
from panda3d.core import NodePath
|
||||
|
||||
|
||||
class Command(ABC):
|
||||
"""
|
||||
抽象命令类,所有具体命令都需要继承此类
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def execute(self):
|
||||
"""
|
||||
执行命令
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def undo(self):
|
||||
"""
|
||||
撤销命令
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def redo(self):
|
||||
"""
|
||||
重做命令
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class CommandManager:
|
||||
"""
|
||||
命令管理器,负责管理命令的执行、撤销和重做
|
||||
"""
|
||||
|
||||
def __init__(self, max_history: int = 100):
|
||||
# 用于存储已执行的命令的历史记录
|
||||
self._undo_stack: deque = deque(maxlen=max_history)
|
||||
# 用于存储已撤销的命令,支持重做
|
||||
self._redo_stack: deque = deque(maxlen=max_history)
|
||||
# 最大历史记录数
|
||||
self._max_history = max_history
|
||||
|
||||
def execute_command(self, command: Command):
|
||||
"""
|
||||
执行命令,并将其添加到撤销栈中
|
||||
"""
|
||||
try:
|
||||
command.execute()
|
||||
self._undo_stack.append(command)
|
||||
# 清空重做栈,因为执行新命令后就无法重做之前的命令了
|
||||
self._redo_stack.clear()
|
||||
except Exception as e:
|
||||
print(f"执行命令时出错: {e}")
|
||||
raise
|
||||
|
||||
def undo(self) -> bool:
|
||||
"""
|
||||
撤销上一个命令
|
||||
返回是否成功撤销
|
||||
"""
|
||||
if not self._undo_stack:
|
||||
return False
|
||||
|
||||
try:
|
||||
command = self._undo_stack.pop()
|
||||
command.undo()
|
||||
self._redo_stack.append(command)
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"撤销命令时出错: {e}")
|
||||
# 如果撤销失败,将命令放回撤销栈
|
||||
self._undo_stack.append(command)
|
||||
return False
|
||||
|
||||
def redo(self) -> bool:
|
||||
"""
|
||||
重做上一个被撤销的命令
|
||||
返回是否成功重做
|
||||
"""
|
||||
if not self._redo_stack:
|
||||
return False
|
||||
|
||||
try:
|
||||
command = self._redo_stack.pop()
|
||||
command.redo()
|
||||
self._undo_stack.append(command)
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"重做命令时出错: {e}")
|
||||
# 如果重做失败,将命令放回重做栈
|
||||
self._redo_stack.append(command)
|
||||
return False
|
||||
|
||||
def can_undo(self) -> bool:
|
||||
"""
|
||||
检查是否可以撤销
|
||||
"""
|
||||
return len(self._undo_stack) > 0
|
||||
|
||||
def can_redo(self) -> bool:
|
||||
"""
|
||||
检查是否可以重做
|
||||
"""
|
||||
return len(self._redo_stack) > 0
|
||||
|
||||
def clear_history(self):
|
||||
"""
|
||||
清空所有历史记录
|
||||
"""
|
||||
self._undo_stack.clear()
|
||||
self._redo_stack.clear()
|
||||
|
||||
def get_undo_count(self) -> int:
|
||||
"""
|
||||
获取可撤销的命令数量
|
||||
"""
|
||||
return len(self._undo_stack)
|
||||
|
||||
def get_redo_count(self) -> int:
|
||||
"""
|
||||
获取可重做的命令数量
|
||||
"""
|
||||
return len(self._redo_stack)
|
||||
|
||||
|
||||
# 示例命令实现
|
||||
class MoveNodeCommand(Command):
|
||||
"""
|
||||
移动节点命令示例
|
||||
"""
|
||||
|
||||
def __init__(self, node: NodePath, old_pos, new_pos):
|
||||
self.node = node
|
||||
self.old_pos = old_pos
|
||||
self.new_pos = new_pos
|
||||
|
||||
def execute(self):
|
||||
"""
|
||||
执行移动操作
|
||||
"""
|
||||
self.node.setPos(self.new_pos)
|
||||
|
||||
def undo(self):
|
||||
"""
|
||||
撤销移动操作
|
||||
"""
|
||||
self.node.setPos(self.old_pos)
|
||||
|
||||
def redo(self):
|
||||
"""
|
||||
重做移动操作
|
||||
"""
|
||||
self.node.setPos(self.new_pos)
|
||||
|
||||
|
||||
class DeleteNodeCommand(Command):
|
||||
"""
|
||||
删除节点命令示例
|
||||
"""
|
||||
|
||||
def __init__(self, node: NodePath, parent_node: NodePath,world=None):
|
||||
self.node = node
|
||||
self.parent_node = parent_node
|
||||
self.world = world
|
||||
|
||||
self.node_name = node.getName()
|
||||
self.node_pos = node.getPos()
|
||||
self.node_hpr = node.getHpr()
|
||||
self.node_scale = node.getScale()
|
||||
|
||||
# 保存节点类型信息
|
||||
self.node_type = "NODE"
|
||||
if node.hasTag("tree_item_type"):
|
||||
self.node_type = node.getTag("tree_item_type")
|
||||
elif node.hasTag("gui_type"):
|
||||
gui_type = node.getTag("gui_type")
|
||||
if gui_type == "button":
|
||||
self.node_type = "GUI_BUTTON"
|
||||
elif gui_type == "label":
|
||||
self.node_type = "GUI_LABEL"
|
||||
elif gui_type == "entry":
|
||||
self.node_type = "GUI_ENTRY"
|
||||
elif gui_type == "2d_image":
|
||||
self.node_type = "GUI_IMAGE"
|
||||
elif gui_type == "3d_text":
|
||||
self.node_type = "GUI_3DTEXT"
|
||||
elif gui_type == "3d_image":
|
||||
self.node_type = "GUI_3DIMAGE"
|
||||
elif gui_type == "video_screen":
|
||||
self.node_type = "GUI_VIDEO_SCREEN"
|
||||
elif gui_type == "2d_video_screen":
|
||||
self.node_type = "GUI_2D_VIDEO_SCREEN"
|
||||
elif node.hasTag("light_type"):
|
||||
self.node_type = "LIGHT_NODE"
|
||||
elif node.hasTag("element_type") and node.getTag("element_type") == "cesium_tileset":
|
||||
self.node_type = "CESIUM_TILESET_NODE"
|
||||
elif node.hasTag("is_scene_element"):
|
||||
self.node_type = "SCENE_NODE"
|
||||
|
||||
self.node_tags = {}
|
||||
if hasattr(node,'hasTag'):
|
||||
for tag_key in node.getNetTag('tags').split(',') if node.hasTag('tags') else []:
|
||||
if node.hasTag(tag_key):
|
||||
self.node_tags[tag_key] = node.getTag(tag_key)
|
||||
else:
|
||||
try:
|
||||
if hasattr(node,'getTag'):
|
||||
common_tags = ['is_scene_element','tree_item_type','gui_type','light_type',
|
||||
'element_type','file','model_path','video_path','image_path',
|
||||
'gui_text','name','created_by_user']
|
||||
for tag in common_tags:
|
||||
if node.hasTag(tag):
|
||||
self.node_tags[tag] = node.getTag(tag)
|
||||
except:
|
||||
pass
|
||||
|
||||
self.node_python_tags = {}
|
||||
if hasattr(node,'getPythonTagKeys'):
|
||||
try:
|
||||
for tag_key in node.getPythonTagKeys():
|
||||
self.node_python_tags[tag_key] = node.getPythonTag(tag_key)
|
||||
except Exception as e:
|
||||
pass
|
||||
|
||||
# 对于特定类型的节点,保存额外的数据
|
||||
self.extra_data = {}
|
||||
if self.node_type in ["GUI_BUTTON", "GUI_LABEL", "GUI_ENTRY", "GUI_IMAGE",
|
||||
"GUI_3DTEXT", "GUI_3DIMAGE", "GUI_VIDEO_SCREEN", "GUI_2D_VIDEO_SCREEN"]:
|
||||
if node.hasTag("gui_text"):
|
||||
self.extra_data["gui_text"] = node.getTag("gui_text")
|
||||
if node.hasTag("video_path"):
|
||||
self.extra_data["video_path"] = node.getTag("video_path")
|
||||
if node.hasTag("image_path"):
|
||||
self.extra_data["image_path"] = node.getTag("image_path")
|
||||
elif self.node_type == "LIGHT_NODE":
|
||||
if node.hasTag("light_type"):
|
||||
self.extra_data["light_type"] = node.getTag("light_type")
|
||||
rp_light = node.getPythonTag("rp_light_object")
|
||||
if rp_light:
|
||||
self.extra_data["light_data"] = {
|
||||
'energy': getattr(rp_light, 'energy', 5000),
|
||||
'radius': getattr(rp_light, 'radius', 1000),
|
||||
'fov': getattr(rp_light, 'fov', 70) if hasattr(rp_light, 'fov') else 70,
|
||||
'inner_radius': getattr(rp_light, 'inner_radius', 0.4) if hasattr(rp_light,
|
||||
'inner_radius') else 0.4,
|
||||
'casts_shadows': getattr(rp_light, 'casts_shadows', True),
|
||||
'shadow_map_resolution': getattr(rp_light, 'shadow_map_resolution', 256)
|
||||
}
|
||||
elif self.node_type == "CESIUM_TILESET_NODE":
|
||||
if node.hasTag("tileset_url"):
|
||||
self.extra_data["tileset_url"] = node.getTag("tileset_url")
|
||||
|
||||
def execute(self):
|
||||
"""
|
||||
执行删除操作
|
||||
"""
|
||||
# 从world的相应列表中移除节点引用
|
||||
if self.world and hasattr(self.world, 'scene_manager'):
|
||||
scene_manager = self.world.scene_manager
|
||||
if self.node_type == "LIGHT_NODE":
|
||||
if self.node.hasTag("light_type"):
|
||||
light_type = self.node.getTag("light_type")
|
||||
if light_type == "spot_light" and hasattr(scene_manager,
|
||||
'Spotlight') and self.node in scene_manager.Spotlight:
|
||||
scene_manager.Spotlight.remove(self.node)
|
||||
elif light_type == "point_light" and hasattr(scene_manager,
|
||||
'Pointlight') and self.node in scene_manager.Pointlight:
|
||||
scene_manager.Pointlight.remove(self.node)
|
||||
elif self.node_type == "IMPORTED_MODEL_NODE" and hasattr(scene_manager,
|
||||
'models') and self.node in scene_manager.models:
|
||||
scene_manager.models.remove(self.node)
|
||||
elif self.node_type.startswith("GUI_") and hasattr(self.world,
|
||||
'gui_elements') and self.node in self.world.gui_elements:
|
||||
self.world.gui_elements.remove(self.node)
|
||||
elif self.node_type == "CESIUM_TILESET_NODE":
|
||||
# 从tilesets列表中移除
|
||||
if hasattr(scene_manager, 'tilesets'):
|
||||
tilesets_to_remove = []
|
||||
for i, tileset_info in enumerate(scene_manager.tilesets):
|
||||
if tileset_info.get('node') == self.node:
|
||||
tilesets_to_remove.append(i)
|
||||
for i in reversed(tilesets_to_remove):
|
||||
del scene_manager.tilesets[i]
|
||||
|
||||
# 从场景图中移除节点
|
||||
if self.node and not self.node.isEmpty():
|
||||
self.node.removeNode()
|
||||
|
||||
def undo(self):
|
||||
"""
|
||||
撤销删除操作(重新创建节点)
|
||||
"""
|
||||
try:
|
||||
# 使用场景管理器重建节点
|
||||
if self.world and hasattr(self.world, 'scene_manager'):
|
||||
scene_manager = self.world.scene_manager
|
||||
|
||||
# 创建节点数据字典
|
||||
node_data = {
|
||||
'name': self.node_name,
|
||||
'node_type': self.node_type,
|
||||
'pos': (self.node_pos.x, self.node_pos.y, self.node_pos.z),
|
||||
'hpr': (self.node_hpr.x, self.node_hpr.y, self.node_hpr.z),
|
||||
'scale': (self.node_scale.x, self.node_scale.y, self.node_scale.z),
|
||||
'tags': self.node_tags
|
||||
}
|
||||
|
||||
# 添加额外数据
|
||||
if self.extra_data:
|
||||
if self.node_type.startswith("GUI_"):
|
||||
node_data['gui_data'] = self.extra_data
|
||||
elif self.node_type == "LIGHT_NODE":
|
||||
node_data['light_data'] = self.extra_data.get('light_data', {})
|
||||
elif self.node_type == "CESIUM_TILESET_NODE":
|
||||
node_data['tileset_url'] = self.extra_data.get('tileset_url', '')
|
||||
|
||||
# 重建节点
|
||||
new_node = scene_manager.recreateNodeFromData(node_data, self.parent_node)
|
||||
|
||||
if new_node:
|
||||
print(f"✅ 成功撤销删除操作,节点 {self.node_name} 已恢复")
|
||||
# 更新节点引用
|
||||
self.node = new_node
|
||||
else:
|
||||
print(f"❌ 撤销删除操作失败,无法重建节点 {self.node_name}")
|
||||
else:
|
||||
print("❌ 无法撤销删除操作,缺少场景管理器引用")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 撤销删除操作时出错: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
def redo(self):
|
||||
"""
|
||||
重做删除操作
|
||||
"""
|
||||
self.execute()
|
||||
|
||||
|
||||
class RotateNodeCommand(Command):
|
||||
"""
|
||||
旋转节点命令
|
||||
"""
|
||||
|
||||
def __init__(self, node: NodePath, old_hpr, new_hpr):
|
||||
self.node = node
|
||||
self.old_hpr = old_hpr
|
||||
self.new_hpr = new_hpr
|
||||
|
||||
def execute(self):
|
||||
"""
|
||||
执行旋转操作
|
||||
"""
|
||||
self.node.setHpr(self.new_hpr)
|
||||
|
||||
def undo(self):
|
||||
"""
|
||||
撤销旋转操作
|
||||
"""
|
||||
self.node.setHpr(self.old_hpr)
|
||||
|
||||
def redo(self):
|
||||
"""
|
||||
重做旋转操作
|
||||
"""
|
||||
self.node.setHpr(self.new_hpr)
|
||||
|
||||
|
||||
class ScaleNodeCommand(Command):
|
||||
"""
|
||||
缩放节点命令
|
||||
"""
|
||||
|
||||
def __init__(self, node: NodePath, old_scale, new_scale):
|
||||
self.node = node
|
||||
self.old_scale = old_scale
|
||||
self.new_scale = new_scale
|
||||
|
||||
def execute(self):
|
||||
"""
|
||||
执行缩放操作
|
||||
"""
|
||||
self.node.setScale(self.new_scale)
|
||||
|
||||
def undo(self):
|
||||
"""
|
||||
撤销缩放操作
|
||||
"""
|
||||
self.node.setScale(self.old_scale)
|
||||
|
||||
def redo(self):
|
||||
"""
|
||||
重做缩放操作
|
||||
"""
|
||||
self.node.setScale(self.new_scale)
|
||||
|
||||
|
||||
class CreateNodeCommand(Command):
|
||||
"""
|
||||
创建节点命令
|
||||
"""
|
||||
|
||||
def __init__(self, node_creator_func,parent_node, *args, **kwargs):
|
||||
self.node_creator_func = node_creator_func
|
||||
self.parent_node = parent_node
|
||||
self.args = args
|
||||
self.kwargs = kwargs
|
||||
self.created_node = None
|
||||
|
||||
def execute(self):
|
||||
"""
|
||||
执行创建节点操作
|
||||
"""
|
||||
self.created_node = self.node_creator_func(self.parent_node,*self.args, **self.kwargs)
|
||||
return self.created_node
|
||||
|
||||
def undo(self):
|
||||
"""
|
||||
撤销创建节点操作
|
||||
"""
|
||||
if self.created_node:
|
||||
self.created_node.removeNode()
|
||||
|
||||
def redo(self):
|
||||
"""
|
||||
重做创建节点操作
|
||||
"""
|
||||
self.execute()
|
||||
|
||||
|
||||
class ReparentNodeCommand(Command):
|
||||
"""
|
||||
重新设置节点父子关系命令
|
||||
"""
|
||||
|
||||
def __init__(self, node: NodePath, old_parent: NodePath, new_parent: NodePath, is_2d_gui=False, world=None):
|
||||
self.node = node
|
||||
self.old_parent = old_parent
|
||||
self.new_parent = new_parent
|
||||
self.is_2d_gui = is_2d_gui
|
||||
self.world = world
|
||||
# 保存节点在操作前的世界坐标和局部坐标,以便在撤销/重做时保持位置不变
|
||||
self.world_pos = node.getPos(self.world.render if self.world else node.getParent())
|
||||
self.world_hpr = node.getHpr(self.world.render if self.world else node.getParent())
|
||||
self.world_scale = node.getScale(self.world.render if self.world else node.getParent())
|
||||
# 同时保存局部坐标,因为在父节点改变后可能需要恢复
|
||||
self.local_pos = node.getPos()
|
||||
self.local_hpr = node.getHpr()
|
||||
self.local_scale = node.getScale()
|
||||
|
||||
def execute(self):
|
||||
"""
|
||||
执行重新父化操作
|
||||
"""
|
||||
if self.is_2d_gui and self.world:
|
||||
# 2D GUI元素需要特殊处理
|
||||
if self.new_parent and not self.new_parent.isEmpty():
|
||||
if hasattr(self.new_parent, 'getTag') and self.new_parent.getTag("is_gui_element") == "1":
|
||||
# 目标是GUI元素,直接重新父化
|
||||
self.node.wrtReparentTo(self.new_parent)
|
||||
else:
|
||||
# 目标是3D节点,保持GUI特性,重新父化到aspect2d
|
||||
self.node.wrtReparentTo(self.world.aspect2d)
|
||||
print(f"2D GUI元素保持在aspect2d下")
|
||||
else:
|
||||
# 如果新父节点为None,重新父化到aspect2d
|
||||
self.node.wrtReparentTo(self.world.aspect2d)
|
||||
print(f"2D GUI元素重新父化到aspect2d")
|
||||
else:
|
||||
# 普通3D节点的处理
|
||||
if self.new_parent and not self.new_parent.isEmpty():
|
||||
self.node.wrtReparentTo(self.new_parent)
|
||||
else:
|
||||
# 如果新父节点为空,将其父化到render节点
|
||||
if self.world:
|
||||
self.node.wrtReparentTo(self.world.render)
|
||||
else:
|
||||
# 备用方案
|
||||
from panda3d.core import NodePath
|
||||
self.node.wrtReparentTo(NodePath("render"))
|
||||
|
||||
def undo(self):
|
||||
"""
|
||||
撤销重新父化操作
|
||||
"""
|
||||
# 在改变父节点前保存当前的缩放值
|
||||
current_scale = self.node.getScale()
|
||||
|
||||
if self.is_2d_gui and self.world:
|
||||
# 2D GUI元素需要特殊处理
|
||||
if self.old_parent and not self.old_parent.isEmpty():
|
||||
if hasattr(self.old_parent, 'getTag') and self.old_parent.getTag("is_gui_element") == "1":
|
||||
# 原父节点是GUI元素,直接重新父化
|
||||
self.node.wrtReparentTo(self.old_parent)
|
||||
else:
|
||||
# 原父节点是3D节点,保持GUI特性,重新父化到aspect2d
|
||||
self.node.wrtReparentTo(self.world.aspect2d)
|
||||
print(f"2D GUI元素恢复到aspect2d下")
|
||||
else:
|
||||
# 如果原父节点为空,重新父化到aspect2d
|
||||
self.node.wrtReparentTo(self.world.aspect2d)
|
||||
print(f"2D GUI元素恢复到aspect2d")
|
||||
else:
|
||||
# 普通3D节点的处理
|
||||
if self.old_parent and not self.old_parent.isEmpty():
|
||||
self.node.wrtReparentTo(self.old_parent)
|
||||
else:
|
||||
# 如果原父节点为空,将其父化到render节点
|
||||
if self.world:
|
||||
self.node.wrtReparentTo(self.world.render)
|
||||
else:
|
||||
# 备用方案
|
||||
from panda3d.core import NodePath
|
||||
self.node.wrtReparentTo(NodePath("render"))
|
||||
|
||||
# 恢复局部坐标(不是世界坐标),因为父节点已经改变
|
||||
self.node.setPos(self.local_pos)
|
||||
self.node.setHpr(self.local_hpr)
|
||||
# 特别处理缩放,确保GUI元素的缩放不会异常变化
|
||||
if not self.is_2d_gui or abs(current_scale.length() - self.local_scale.length()) > 0.001:
|
||||
self.node.setScale(self.local_scale)
|
||||
|
||||
def redo(self):
|
||||
"""
|
||||
重做重新父化操作
|
||||
"""
|
||||
# 在改变父节点前保存当前的缩放值
|
||||
current_scale = self.node.getScale()
|
||||
|
||||
if self.is_2d_gui and self.world:
|
||||
# 2D GUI元素需要特殊处理
|
||||
if self.new_parent and not self.new_parent.isEmpty():
|
||||
if hasattr(self.new_parent, 'getTag') and self.new_parent.getTag("is_gui_element") == "1":
|
||||
# 目标是GUI元素,直接重新父化
|
||||
self.node.wrtReparentTo(self.new_parent)
|
||||
else:
|
||||
# 目标是3D节点,保持GUI特性,重新父化到aspect2d
|
||||
self.node.wrtReparentTo(self.world.aspect2d)
|
||||
print(f"2D GUI元素保持在aspect2d下")
|
||||
else:
|
||||
# 如果新父节点为None,重新父化到aspect2d
|
||||
self.node.wrtReparentTo(self.world.aspect2d)
|
||||
print(f"2D GUI元素重新父化到aspect2d")
|
||||
else:
|
||||
# 普通3D节点的处理
|
||||
if self.new_parent and not self.new_parent.isEmpty():
|
||||
self.node.wrtReparentTo(self.new_parent)
|
||||
else:
|
||||
# 如果新父节点为空,将其父化到render节点
|
||||
if self.world:
|
||||
self.node.wrtReparentTo(self.world.render)
|
||||
else:
|
||||
# 备用方案
|
||||
from panda3d.core import NodePath
|
||||
self.node.wrtReparentTo(NodePath("render"))
|
||||
|
||||
# 恢复局部坐标(不是世界坐标),因为父节点已经改变
|
||||
self.node.setPos(self.local_pos)
|
||||
self.node.setHpr(self.local_hpr)
|
||||
# 特别处理缩放,确保GUI元素的缩放不会异常变化
|
||||
if not self.is_2d_gui or abs(current_scale.length() - self.local_scale.length()) > 0.001:
|
||||
self.node.setScale(self.local_scale)
|
||||
|
||||
|
||||
|
||||
@ -148,7 +148,7 @@ class SelectionSystem:
|
||||
|
||||
# 初始更新选择框
|
||||
#print(" 开始初始化选择框几何体...")
|
||||
#self.updateSelectionBoxGeometry()
|
||||
self.updateSelectionBoxGeometry()
|
||||
|
||||
#print(f" ✓ 为节点 {nodePath.getName()} 创建了选择框")
|
||||
|
||||
@ -1900,6 +1900,37 @@ class SelectionSystem:
|
||||
"""停止坐标轴拖拽"""
|
||||
print(f"停止坐标轴拖拽 - 轴: {self.dragGizmoAxis}")
|
||||
|
||||
if hasattr(self.world,'command_manager') and self.world.command_manager and self.gizmoTarget:
|
||||
current_pos = self.gizmoTarget.getPos()
|
||||
|
||||
if (hasattr(self,'gizmoTargetStartPos') and self.gizmoTargetStartPos and
|
||||
(abs(current_pos.x-self.gizmoTargetStartPos.x)>0.001 or
|
||||
abs(current_pos.y-self.gizmoTargetStartPos.y)>0.001 or
|
||||
abs(current_pos.z-self.gizmoTargetStartPos.z)>0.001)):
|
||||
from core.Command_System import MoveNodeCommand
|
||||
command = MoveNodeCommand(self.gizmoTarget,self.gizmoTargetStartPos,current_pos)
|
||||
self.world.command_manager.execute_command(command)
|
||||
# 如果是缩放操作且缩放发生了变化,则创建缩放命令
|
||||
elif (hasattr(self, 'gizmoTargetStartScale') and hasattr(self, 'gizmoTargetStartScale') and
|
||||
self.gizmoTargetStartScale):
|
||||
current_scale = self.gizmoTarget.getScale()
|
||||
if (abs(current_scale.x - self.gizmoTargetStartScale.x) > 0.001 or
|
||||
abs(current_scale.y - self.gizmoTargetStartScale.y) > 0.001 or
|
||||
abs(current_scale.z - self.gizmoTargetStartScale.z) > 0.001):
|
||||
from core.Command_System import ScaleNodeCommand
|
||||
command = ScaleNodeCommand(self.gizmoTarget, self.gizmoTargetStartScale, current_scale)
|
||||
self.world.command_manager.execute_command(command)
|
||||
# 如果是旋转操作且旋转发生了变化,则创建旋转命令
|
||||
elif (hasattr(self, 'gizmoTargetStartHpr') and hasattr(self, 'gizmoTargetStartHpr') and
|
||||
self.gizmoTargetStartHpr):
|
||||
current_hpr = self.gizmoTarget.getHpr()
|
||||
if (abs(current_hpr.x - self.gizmoTargetStartHpr.x) > 0.001 or
|
||||
abs(current_hpr.y - self.gizmoTargetStartHpr.y) > 0.001 or
|
||||
abs(current_hpr.z - self.gizmoTargetStartHpr.z) > 0.001):
|
||||
from core.Command_System import RotateNodeCommand
|
||||
command = RotateNodeCommand(self.gizmoTarget, self.gizmoTargetStartHpr, current_hpr)
|
||||
self.world.command_manager.execute_command(command)
|
||||
|
||||
# 恢复所有轴的颜色
|
||||
for axis_name in ["x", "y", "z"]:
|
||||
self.setGizmoAxisColor(axis_name, self.gizmo_colors[axis_name])
|
||||
|
||||
@ -1509,9 +1509,11 @@ class GUIManager:
|
||||
video_screen.setTag("tree_item_type", "GUI_2D_VIDEO_SCREEN")
|
||||
video_screen.setTag("created_by_user", "1")
|
||||
video_screen.setTag("name",screen_name)
|
||||
|
||||
video_screen.setTag("video_path", video_path if video_path else "")
|
||||
print(f"🔧 设置2D视频屏幕标签 - video_path: {video_path if video_path else '空'}")
|
||||
# 修复后
|
||||
if video_path is not None:
|
||||
video_screen.setTag("video_path", video_path)
|
||||
else:
|
||||
video_screen.setTag("video_path", "")
|
||||
|
||||
# 关键修改:预先创建一个占位符纹理,为后续视频播放做准备
|
||||
placeholder_texture = Texture(f"placeholder_video_texture_{len(self.gui_elements)}")
|
||||
@ -2186,9 +2188,17 @@ class GUIManager:
|
||||
print(f"开始编辑GUI元素: 类型={gui_type}, 属性={property_name}, 值={value}")
|
||||
|
||||
if property_name == "text":
|
||||
original_frame_size = None
|
||||
if hasattr(gui_element,'getFrameSize'):
|
||||
try:
|
||||
original_frame_size = gui_element.getFrameSize()
|
||||
except:
|
||||
pass
|
||||
if gui_type in ["button", "label"]:
|
||||
gui_element['text'] = value
|
||||
print(f"成功更新2D GUI文本: {value}")
|
||||
# if gui_type == "button":
|
||||
# self._resizeButtonToText(gui_element,value,original_frame_size)
|
||||
elif gui_type == "entry":
|
||||
gui_element.set(value)
|
||||
print(f"成功更新输入框文本: {value}")
|
||||
@ -2267,6 +2277,64 @@ class GUIManager:
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
def _resizeButtonToText(self, button, text, original_frame_size=None):
|
||||
"""
|
||||
根据文本内容调整按钮大小
|
||||
|
||||
Args:
|
||||
button: DirectButton 对象
|
||||
text: 新的文本内容
|
||||
original_frame_size: 原始frameSize,用于计算合适的padding
|
||||
"""
|
||||
try:
|
||||
# 获取按钮当前的文本缩放
|
||||
text_scale = 0.03 # 默认文本缩放
|
||||
padding = 0.2 # 默认边距
|
||||
|
||||
# 尝试从按钮获取实际的文本缩放值
|
||||
if hasattr(button, 'getTextScale'):
|
||||
text_scale = button.getTextScale()
|
||||
elif hasattr(button, 'textScale'):
|
||||
text_scale = button.textScale
|
||||
|
||||
# 根据原始frameSize计算合适的padding
|
||||
if original_frame_size and len(original_frame_size) >= 4:
|
||||
# 基于原始按钮宽度计算合适的padding
|
||||
padding = max((original_frame_size[1] - original_frame_size[0]) * 0.15, 0.1)
|
||||
|
||||
# 更精确的文本尺寸估算
|
||||
# 考虑中文字符和英文字符的不同宽度
|
||||
chinese_chars = len([c for c in text if ord(c) > 127])
|
||||
english_chars = len(text) - chinese_chars
|
||||
|
||||
# 中文字符通常比英文字符宽
|
||||
char_width = text_scale * 0.6
|
||||
text_width = (chinese_chars * char_width * 1.5) + (english_chars * char_width)
|
||||
text_height = text_scale * 1.2 # 文本高度
|
||||
|
||||
# 计算新的frameSize,确保有足够的边距
|
||||
half_width = max(text_width * 0.5 + padding, 0.3) # 最小宽度0.3
|
||||
half_height = max(text_height * 0.5 + padding * 0.5, 0.15) # 最小高度0.15
|
||||
|
||||
# 正确设置frameSize - 使用字典方式设置
|
||||
new_frame = (-half_width, half_width, -half_height, half_height)
|
||||
button['frameSize'] = new_frame # 使用字典方式设置
|
||||
|
||||
print(f"按钮大小已调整: {new_frame} (文本: {text})")
|
||||
|
||||
except Exception as e:
|
||||
print(f"调整按钮大小失败: {e}")
|
||||
# 如果自动调整失败,保持原有大小或设置一个合理的默认大小
|
||||
try:
|
||||
if original_frame_size:
|
||||
# 保持原有大小
|
||||
button['frameSize'] = original_frame_size
|
||||
else:
|
||||
# 设置一个合理的默认大小
|
||||
button['frameSize'] = (-0.5, 0.5, -0.15, 0.15)
|
||||
except:
|
||||
pass
|
||||
|
||||
def duplicateGUIElement(self, gui_element):
|
||||
"""复制GUI元素"""
|
||||
try:
|
||||
|
||||
5
main.py
5
main.py
@ -23,6 +23,7 @@ from core.vr_manager import VRManager
|
||||
from core.vr_input_handler import VRInputHandler
|
||||
from core.alvr_streamer import ALVRStreamer
|
||||
from core.patrol_system import PatrolSystem
|
||||
from core.Command_System import CommandManager
|
||||
from gui.gui_manager import GUIManager
|
||||
from core.terrain_manager import TerrainManager
|
||||
from scene.scene_manager import SceneManager
|
||||
@ -110,6 +111,8 @@ class MyWorld(CoreWorld):
|
||||
|
||||
self.info_panel_manager = InfoPanelManager(self)
|
||||
|
||||
self.command_manager = CommandManager()
|
||||
|
||||
# 初始化碰撞管理器
|
||||
from core.collision_manager import CollisionManager
|
||||
self.collision_manager = CollisionManager(self)
|
||||
@ -239,7 +242,7 @@ class MyWorld(CoreWorld):
|
||||
"""创建3D空间文本"""
|
||||
return self.gui_manager.createGUI3DText(pos, text, size)
|
||||
|
||||
def createGUI3DImage(self,pos=(0,0,0),text="3D图片",size=(2,2)):
|
||||
def createGUI3DImage(self,pos=(0,0,0),text="3D图片",size=(1,1)):
|
||||
"""创建3D图片"""
|
||||
return self.gui_manager.createGUI3DImage(pos,text,size)
|
||||
|
||||
|
||||
@ -2506,6 +2506,7 @@ class SceneManager:
|
||||
light.radius = 1000
|
||||
light.casts_shadows = True
|
||||
light.shadow_map_resolution = 256
|
||||
light.setPos(pos)
|
||||
|
||||
# 添加到渲染管线
|
||||
render_pipeline.add_light(light)
|
||||
@ -2682,177 +2683,6 @@ class SceneManager:
|
||||
pass
|
||||
return None
|
||||
|
||||
# def createSpotLight(self, pos=(0, 0, 0)):
|
||||
# """创建聚光灯 - 使用统一的create_item方法"""
|
||||
# try:
|
||||
# # 调用CustomTreeWidget的create_item方法创建聚光灯节点
|
||||
# if hasattr(self.world, 'interface_manager') and hasattr(self.world.interface_manager, 'treeWidget'):
|
||||
# tree_widget = self.world.interface_manager.treeWidget
|
||||
# if tree_widget and hasattr(tree_widget, 'create_item'):
|
||||
# # 创建聚光灯节点
|
||||
# created_nodes = tree_widget.create_item("spot_light")
|
||||
#
|
||||
# if created_nodes:
|
||||
# # 获取创建的节点
|
||||
# light_np, qt_item = created_nodes[0]
|
||||
#
|
||||
# # 设置位置(如果指定了非默认位置)
|
||||
# if pos != (0, 0, 0):
|
||||
# light_np.setPos(*pos)
|
||||
# # 同时更新光源对象的位置
|
||||
# light_obj = light_np.getPythonTag("rp_light_object")
|
||||
# if light_obj:
|
||||
# light_obj.setPos(*pos)
|
||||
#
|
||||
# print(f"✅ 通过create_item创建聚光灯成功: {light_np.getName()}")
|
||||
# return light_np
|
||||
# else:
|
||||
# print("❌ create_item创建聚光灯失败")
|
||||
# return None
|
||||
# else:
|
||||
# print("❌ 无法访问树形控件的create_item方法")
|
||||
# return None
|
||||
# else:
|
||||
# print("❌ 无法访问界面管理器或树形控件")
|
||||
# return None
|
||||
#
|
||||
# except Exception as e:
|
||||
# print(f"❌ 创建聚光灯时发生错误: {str(e)}")
|
||||
# import traceback
|
||||
# traceback.print_exc()
|
||||
# return None
|
||||
|
||||
# def createSpotLight(self, pos=(0, 0, 0)):
|
||||
# from RenderPipelineFile.rpcore import SpotLight, RenderPipeline
|
||||
# from panda3d.core import Vec3,NodePath
|
||||
#
|
||||
# render_pipeline = get_render_pipeline()
|
||||
#
|
||||
# # 创建一个挂载节点(你控制的)
|
||||
# light_np = NodePath("SpotlightAttachNode")
|
||||
# light_np.reparentTo(self.world.render)
|
||||
# #light_np.setPos(*pos)
|
||||
#
|
||||
# self.half_energy = 5000
|
||||
# self.lamp_fov = 70
|
||||
# self.lamp_radius = 1000
|
||||
#
|
||||
# light = SpotLight()
|
||||
# light.direction = Vec3(0, 0, -1) # 光照方向
|
||||
# light.fov = self.lamp_fov # 光源角度(类似手电筒)
|
||||
# light.set_color_from_temperature(5 * 1000.0) # 色温(K)
|
||||
# light.energy = self.half_energy # 光照强度
|
||||
# light.radius = self.lamp_radius # 影响范围
|
||||
# light.casts_shadows = True # 是否投射阴影
|
||||
# light.shadow_map_resolution = 256 # 阴影分辨率
|
||||
# light.setPos(*pos)
|
||||
# #light_np.setPos(*pos)
|
||||
#
|
||||
# #light_np = render_pipeline.add_light(light, parent=self.world.render)
|
||||
# render_pipeline.add_light(light) # 添加到渲染管线
|
||||
#
|
||||
# light_name = f"Spotlight_{len(self.Spotlight)}"
|
||||
#
|
||||
# light_np.setName(light_name) # 设置唯一名称
|
||||
# #light_np.reparentTo(self.world.render) # 挂载到场景根节点
|
||||
#
|
||||
# light_np.setTag("light_type", "spot_light")
|
||||
# light_np.setTag("is_scene_element", "1")
|
||||
# light_np.setTag("light_energy", str(light.energy))
|
||||
#
|
||||
# light_np.setPythonTag("rp_light_object", light)
|
||||
#
|
||||
# self.Spotlight.append(light_np)
|
||||
#
|
||||
# if hasattr(self.world, 'updateSceneTree'):
|
||||
# self.world.updateSceneTree()
|
||||
#
|
||||
# #print("nikan"+light_np.getHpr())
|
||||
|
||||
# def createPointLight(self, pos=(0, 0, 0)):
|
||||
# """创建点光源 - 使用统一的create_item方法"""
|
||||
# try:
|
||||
# # 调用CustomTreeWidget的create_item方法创建点光源节点
|
||||
# if hasattr(self.world, 'interface_manager') and hasattr(self.world.interface_manager, 'treeWidget'):
|
||||
# tree_widget = self.world.interface_manager.treeWidget
|
||||
# if tree_widget and hasattr(tree_widget, 'create_item'):
|
||||
# # 创建点光源节点
|
||||
# created_nodes = tree_widget.create_item("point_light")
|
||||
#
|
||||
# if created_nodes:
|
||||
# # 获取创建的节点
|
||||
# light_np, qt_item = created_nodes[0]
|
||||
#
|
||||
# # 设置位置(如果指定了非默认位置)
|
||||
# if pos != (0, 0, 0):
|
||||
# light_np.setPos(*pos)
|
||||
# # 同时更新光源对象的位置
|
||||
# light_obj = light_np.getPythonTag("rp_light_object")
|
||||
# if light_obj:
|
||||
# light_obj.setPos(*pos)
|
||||
#
|
||||
# print(f"✅ 通过create_item创建点光源成功: {light_np.getName()}")
|
||||
# return light_np
|
||||
# else:
|
||||
# print("❌ create_item创建点光源失败")
|
||||
# return None
|
||||
# else:
|
||||
# print("❌ 无法访问树形控件的create_item方法")
|
||||
# return None
|
||||
# else:
|
||||
# print("❌ 无法访问界面管理器或树形控件")
|
||||
# return None
|
||||
#
|
||||
# except Exception as e:
|
||||
# print(f"❌ 创建点光源时发生错误: {str(e)}")
|
||||
# import traceback
|
||||
# traceback.print_exc()
|
||||
# return None
|
||||
|
||||
# def createPointLight(self, pos=(0, 0, 0)):
|
||||
# from RenderPipelineFile.rpcore import PointLight, RenderPipeline
|
||||
# from panda3d.core import Vec3, NodePath
|
||||
#
|
||||
# render_pipeline = get_render_pipeline()
|
||||
#
|
||||
# # 创建一个挂载节点(你控制的)
|
||||
# light_np = NodePath("PointlightAttachNode")
|
||||
# light_np.reparentTo(self.world.render)
|
||||
#
|
||||
#
|
||||
# light = PointLight()
|
||||
# light.setPos(*pos)
|
||||
# light_np.setPos(*pos)
|
||||
# light.energy = 5000
|
||||
# light.radius = 1000
|
||||
# light.inner_radius = 0.4
|
||||
# light.set_color_from_temperature(5 * 1000.0) # 色温(K)
|
||||
# light.casts_shadows = True # 是否投射阴影
|
||||
# light.shadow_map_resolution = 256 # 阴影分辨率
|
||||
#
|
||||
# render_pipeline.add_light(light) # 添加到渲染管线
|
||||
#
|
||||
# light_name = f"Pointlight{len(self.Pointlight)}"
|
||||
#
|
||||
# light_np.setName(light_name) # 设置唯一名称
|
||||
#
|
||||
# #light_np = NodePath(f"PointLight_{len(self.Pointlight)}")
|
||||
# #light_np.reparentTo(self.world.render)
|
||||
# #light_np.setPos(*pos)
|
||||
#
|
||||
# light_np.setTag("light_type", "point_light")
|
||||
# light_np.setTag("is_scene_element", "1")
|
||||
# light_np.setTag("light_energy", str(light.energy))
|
||||
#
|
||||
# # 保存光源对象引用(重要!用于属性面板)
|
||||
# light_np.setPythonTag("rp_light_object", light)
|
||||
#
|
||||
# self.Pointlight.append(light_np)
|
||||
#
|
||||
# if hasattr(self.world, 'updateSceneTree'):
|
||||
# self.world.updateSceneTree()
|
||||
#
|
||||
# return light,light_np
|
||||
|
||||
# ==================== GLB 转换方法 ====================
|
||||
|
||||
@ -3762,7 +3592,7 @@ except Exception as e:
|
||||
elif node_type == "CESIUM_TILESET_NODE":
|
||||
new_node = self._recreateTilesetFromData(node_data, parent_node, unique_name)
|
||||
elif node_type in ["GUI_BUTTON", "GUI_LABEL", "GUI_ENTRY", "GUI_IMAGE",
|
||||
"GUI_3DTEXT", "GUI_3DIMAGE", "GUI_VIRTUAL_SCREEN"]:
|
||||
"GUI_3DTEXT", "GUI_3DIMAGE", "GUI_VIDEO_SCREEN","GUI_2D_VIDEO_SCREEN"]:
|
||||
new_node = self._recreateGUIFromData(node_data, parent_node, unique_name)
|
||||
elif node_type == "IMPORTED_MODEL_NODE":
|
||||
new_node = self._recreateModelFromData(node_data, parent_node, unique_name)
|
||||
@ -3947,7 +3777,7 @@ except Exception as e:
|
||||
new_gui_element = self.world.createGUI3DText(pos, text, scale)
|
||||
elif gui_type == "3d_image" and hasattr(self.world, 'createGUI3DImage'):
|
||||
pos = node_data.get('pos', (0, 0, 0))
|
||||
image_path = node_data.get('tags').get('image_path', '')
|
||||
image_path = node_data.get('tags').get('gui_image_path', '')
|
||||
scale = node_data.get('scale', (1, 1))
|
||||
if isinstance(scale, (int, float)):
|
||||
scale = (scale, scale)
|
||||
@ -3956,7 +3786,17 @@ except Exception as e:
|
||||
else:
|
||||
scale = (1, 1)
|
||||
print(f"正在创建3D图片: 位置={pos}, 路径={image_path}, 大小={scale}")
|
||||
new_gui_element = self.world.createGUI3DImage(pos, image_path, scale)
|
||||
new_gui_element = self.world.gui_manager.createGUI3DImage(pos, image_path, scale)
|
||||
elif gui_type == "video_screen" and hasattr(self.world.gui_manager, 'createVideoScreen'):
|
||||
pos = node_data.get('pos', (0, 0, 0))
|
||||
video_path = node_data.get('tags').get('video_path', '')
|
||||
scale = node_data.get('scale', (1, 1,1))
|
||||
new_gui_element = self.world.gui_manager.createVideoScreen(pos,scale,video_path)
|
||||
elif gui_type == "2d_video_screen" and hasattr(self.world.gui_manager, 'createGUI2DVideoScreen'):
|
||||
pos = node_data.get('pos', (0, 0, 0))
|
||||
video_path = node_data.get('tags').get('video_path', '')
|
||||
scale = node_data.get('scale', (1, 1, 1))
|
||||
new_gui_element = self.world.gui_manager.createGUI2DVideoScreen(pos,scale,video_path)
|
||||
|
||||
if new_gui_element:
|
||||
# 设置名称和变换
|
||||
|
||||
@ -2134,12 +2134,37 @@ class MainWindow(QMainWindow):
|
||||
# 添加撤销/重做功能的基础实现
|
||||
def onUndo(self):
|
||||
"""撤销操作"""
|
||||
QMessageBox.information(self, "提示", "撤销功能将在后续版本中实现")
|
||||
if hasattr(self.world,'command_manager'):
|
||||
if self.world.command_manager.can_undo():
|
||||
success = self.world.command_manager.undo()
|
||||
if success:
|
||||
print("成功操作")
|
||||
else:
|
||||
print("撤销失败")
|
||||
QMessageBox.information(self,"提示","撤销操作失败")
|
||||
else:
|
||||
print("没有可撤销的操作")
|
||||
QMessageBox.information(self,"提示","没有可撤销的操作")
|
||||
else:
|
||||
print("命令管理器未初始化")
|
||||
QMessageBox.information(self,"提示","命令系统未初始化")
|
||||
|
||||
def onRedo(self):
|
||||
"""重做操作"""
|
||||
QMessageBox.information(self, "提示", "重做功能将在后续版本中实现")
|
||||
|
||||
if hasattr(self.world,'command_manager'):
|
||||
if self.world.command_manager.can_redo():
|
||||
success = self.world.command_manager.redo()
|
||||
if success:
|
||||
print("成功重做")
|
||||
else:
|
||||
print("重做失败")
|
||||
QMessageBox.information(self,"提示","重做操作失败")
|
||||
else:
|
||||
print("没有可重做的操作")
|
||||
QMessageBox.information(self,"提示","没有可重做的操作")
|
||||
else:
|
||||
print("命令管理器未初始化")
|
||||
QMessageBox.information(self,"提示","命令系统未初始化")
|
||||
|
||||
def onCreateCesiumView(self):
|
||||
if hasattr(self.world,'gui_manager') and self.world.gui_manager:
|
||||
|
||||
@ -2165,7 +2165,6 @@ class CustomTreeWidget(QTreeWidget):
|
||||
if hasattr(panda_node, 'getPythonTag'):
|
||||
light_object = panda_node.getPythonTag('rp_light_object')
|
||||
if light_object and hasattr(self.world, 'render_pipeline'):
|
||||
print(f'11111111111111111111111111,{light_object.casts_shadows}')
|
||||
self.world.render_pipeline.remove_light(light_object)
|
||||
|
||||
# 从world列表中移除
|
||||
@ -2231,6 +2230,14 @@ class CustomTreeWidget(QTreeWidget):
|
||||
print("ℹ️ 尝试删除一个空的或无效的节点,操作取消。")
|
||||
return
|
||||
|
||||
# #如果有命令管理系统,则使用命令系统
|
||||
# if hasattr(self.world,'command_manager') and self.world.command_manager:
|
||||
# from core.Command_System import DeleteNodeCommand
|
||||
# parent_node = panda_node.getParent()
|
||||
# command = DeleteNodeCommand(panda_node,parent_node)
|
||||
# self.world.command_manager.execute_command(command)
|
||||
# return
|
||||
|
||||
# --- 关键修复:在操作前,安全地获取节点名字 ---
|
||||
node_name_for_logging = panda_node.getName()
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user