撤销重做(仅使用拖拽时)

This commit is contained in:
Hector 2025-09-23 16:39:55 +08:00
parent 040fffd34e
commit ddeb40ea54
7 changed files with 727 additions and 183 deletions

570
core/Command_System.py Normal file
View 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)

View File

@ -148,7 +148,7 @@ class SelectionSystem:
# 初始更新选择框 # 初始更新选择框
#print(" 开始初始化选择框几何体...") #print(" 开始初始化选择框几何体...")
#self.updateSelectionBoxGeometry() self.updateSelectionBoxGeometry()
#print(f" ✓ 为节点 {nodePath.getName()} 创建了选择框") #print(f" ✓ 为节点 {nodePath.getName()} 创建了选择框")
@ -1900,6 +1900,37 @@ class SelectionSystem:
"""停止坐标轴拖拽""" """停止坐标轴拖拽"""
print(f"停止坐标轴拖拽 - 轴: {self.dragGizmoAxis}") 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"]: for axis_name in ["x", "y", "z"]:
self.setGizmoAxisColor(axis_name, self.gizmo_colors[axis_name]) self.setGizmoAxisColor(axis_name, self.gizmo_colors[axis_name])

View File

@ -1509,9 +1509,11 @@ class GUIManager:
video_screen.setTag("tree_item_type", "GUI_2D_VIDEO_SCREEN") video_screen.setTag("tree_item_type", "GUI_2D_VIDEO_SCREEN")
video_screen.setTag("created_by_user", "1") video_screen.setTag("created_by_user", "1")
video_screen.setTag("name",screen_name) video_screen.setTag("name",screen_name)
# 修复后
video_screen.setTag("video_path", video_path if video_path else "") if video_path is not None:
print(f"🔧 设置2D视频屏幕标签 - video_path: {video_path if video_path else ''}") video_screen.setTag("video_path", video_path)
else:
video_screen.setTag("video_path", "")
# 关键修改:预先创建一个占位符纹理,为后续视频播放做准备 # 关键修改:预先创建一个占位符纹理,为后续视频播放做准备
placeholder_texture = Texture(f"placeholder_video_texture_{len(self.gui_elements)}") 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}") print(f"开始编辑GUI元素: 类型={gui_type}, 属性={property_name}, 值={value}")
if property_name == "text": 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"]: if gui_type in ["button", "label"]:
gui_element['text'] = value gui_element['text'] = value
print(f"成功更新2D GUI文本: {value}") print(f"成功更新2D GUI文本: {value}")
# if gui_type == "button":
# self._resizeButtonToText(gui_element,value,original_frame_size)
elif gui_type == "entry": elif gui_type == "entry":
gui_element.set(value) gui_element.set(value)
print(f"成功更新输入框文本: {value}") print(f"成功更新输入框文本: {value}")
@ -2267,6 +2277,64 @@ class GUIManager:
traceback.print_exc() traceback.print_exc()
return False 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): def duplicateGUIElement(self, gui_element):
"""复制GUI元素""" """复制GUI元素"""
try: try:

View File

@ -23,6 +23,7 @@ from core.vr_manager import VRManager
from core.vr_input_handler import VRInputHandler from core.vr_input_handler import VRInputHandler
from core.alvr_streamer import ALVRStreamer from core.alvr_streamer import ALVRStreamer
from core.patrol_system import PatrolSystem from core.patrol_system import PatrolSystem
from core.Command_System import CommandManager
from gui.gui_manager import GUIManager from gui.gui_manager import GUIManager
from core.terrain_manager import TerrainManager from core.terrain_manager import TerrainManager
from scene.scene_manager import SceneManager from scene.scene_manager import SceneManager
@ -110,6 +111,8 @@ class MyWorld(CoreWorld):
self.info_panel_manager = InfoPanelManager(self) self.info_panel_manager = InfoPanelManager(self)
self.command_manager = CommandManager()
# 初始化碰撞管理器 # 初始化碰撞管理器
from core.collision_manager import CollisionManager from core.collision_manager import CollisionManager
self.collision_manager = CollisionManager(self) self.collision_manager = CollisionManager(self)
@ -239,7 +242,7 @@ class MyWorld(CoreWorld):
"""创建3D空间文本""" """创建3D空间文本"""
return self.gui_manager.createGUI3DText(pos, text, size) 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图片""" """创建3D图片"""
return self.gui_manager.createGUI3DImage(pos,text,size) return self.gui_manager.createGUI3DImage(pos,text,size)

View File

@ -2506,6 +2506,7 @@ class SceneManager:
light.radius = 1000 light.radius = 1000
light.casts_shadows = True light.casts_shadows = True
light.shadow_map_resolution = 256 light.shadow_map_resolution = 256
light.setPos(pos)
# 添加到渲染管线 # 添加到渲染管线
render_pipeline.add_light(light) render_pipeline.add_light(light)
@ -2682,177 +2683,6 @@ class SceneManager:
pass pass
return None 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 转换方法 ==================== # ==================== GLB 转换方法 ====================
@ -3762,7 +3592,7 @@ except Exception as e:
elif node_type == "CESIUM_TILESET_NODE": elif node_type == "CESIUM_TILESET_NODE":
new_node = self._recreateTilesetFromData(node_data, parent_node, unique_name) new_node = self._recreateTilesetFromData(node_data, parent_node, unique_name)
elif node_type in ["GUI_BUTTON", "GUI_LABEL", "GUI_ENTRY", "GUI_IMAGE", 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) new_node = self._recreateGUIFromData(node_data, parent_node, unique_name)
elif node_type == "IMPORTED_MODEL_NODE": elif node_type == "IMPORTED_MODEL_NODE":
new_node = self._recreateModelFromData(node_data, parent_node, unique_name) 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) new_gui_element = self.world.createGUI3DText(pos, text, scale)
elif gui_type == "3d_image" and hasattr(self.world, 'createGUI3DImage'): elif gui_type == "3d_image" and hasattr(self.world, 'createGUI3DImage'):
pos = node_data.get('pos', (0, 0, 0)) 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)) scale = node_data.get('scale', (1, 1))
if isinstance(scale, (int, float)): if isinstance(scale, (int, float)):
scale = (scale, scale) scale = (scale, scale)
@ -3956,7 +3786,17 @@ except Exception as e:
else: else:
scale = (1, 1) scale = (1, 1)
print(f"正在创建3D图片: 位置={pos}, 路径={image_path}, 大小={scale}") 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: if new_gui_element:
# 设置名称和变换 # 设置名称和变换

View File

@ -2134,12 +2134,37 @@ class MainWindow(QMainWindow):
# 添加撤销/重做功能的基础实现 # 添加撤销/重做功能的基础实现
def onUndo(self): 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): 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): def onCreateCesiumView(self):
if hasattr(self.world,'gui_manager') and self.world.gui_manager: if hasattr(self.world,'gui_manager') and self.world.gui_manager:

View File

@ -2165,7 +2165,6 @@ class CustomTreeWidget(QTreeWidget):
if hasattr(panda_node, 'getPythonTag'): if hasattr(panda_node, 'getPythonTag'):
light_object = panda_node.getPythonTag('rp_light_object') light_object = panda_node.getPythonTag('rp_light_object')
if light_object and hasattr(self.world, 'render_pipeline'): if light_object and hasattr(self.world, 'render_pipeline'):
print(f'11111111111111111111111111,{light_object.casts_shadows}')
self.world.render_pipeline.remove_light(light_object) self.world.render_pipeline.remove_light(light_object)
# 从world列表中移除 # 从world列表中移除
@ -2231,6 +2230,14 @@ class CustomTreeWidget(QTreeWidget):
print(" 尝试删除一个空的或无效的节点,操作取消。") print(" 尝试删除一个空的或无效的节点,操作取消。")
return 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() node_name_for_logging = panda_node.getName()