forked from Rowland/EG
修改保存逻辑,添加打包功能
This commit is contained in:
parent
7c797d74d5
commit
a66c097048
@ -12,6 +12,7 @@ from PyQt5 import QtWidgets, QtGui
|
||||
from PyQt5.QtCore import *
|
||||
from PyQt5.QtGui import *
|
||||
from PyQt5.QtWidgets import *
|
||||
from direct.task.TaskManagerGlobal import taskMgr
|
||||
|
||||
# Panda imports
|
||||
from panda3d.core import Texture, WindowProperties, CallbackGraphicsWindow
|
||||
@ -40,16 +41,9 @@ class QPanda3DSynchronizer(QTimer):
|
||||
try:
|
||||
taskMgr.step()
|
||||
self.qPanda3DWidget.update()
|
||||
except AssertionError as e:
|
||||
if "has_mat()" in str(e):
|
||||
print("⚠️ 检测到变换矩阵错误,跳过此帧")
|
||||
# 继续运行而不是崩溃
|
||||
else:
|
||||
raise
|
||||
except Exception as e:
|
||||
print(f"❌ 渲染循环错误: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
except:
|
||||
# 静默处理所有异常,包括 has_mat() 断言错误
|
||||
pass
|
||||
|
||||
|
||||
def get_panda_key_modifiers(evt):
|
||||
|
||||
@ -52,11 +52,7 @@ class MainApp(ShowBase):
|
||||
# Load the scene
|
||||
model = loader.loadModel("scene/scene.bam")
|
||||
# model = loader.loadModel("scene2/Scene.bam")
|
||||
model_0 = self.loader.loadModel("/home/tiger/下载/Benci/source/s65/s65/s65.fbx")
|
||||
model_0.reparentTo(self.render)
|
||||
model_0.setScale(0.01)
|
||||
model_0.setPos(-8, 42, 0)
|
||||
model_0.setHpr(0, 90, 0)
|
||||
|
||||
|
||||
model.reparent_to(render)
|
||||
self.render_pipeline.prepare_scene(model)
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from collections import deque
|
||||
from typing import List
|
||||
from panda3d.core import NodePath
|
||||
from panda3d.core import NodePath, Point3
|
||||
|
||||
|
||||
class Command(ABC):
|
||||
@ -435,15 +435,19 @@ class CreateNodeCommand(Command):
|
||||
|
||||
class ReparentNodeCommand(Command):
|
||||
"""
|
||||
重新设置节点父子关系命令
|
||||
重新设置节点父子关系命令 - 增强版(同时处理Panda3D和Qt树)
|
||||
"""
|
||||
|
||||
def __init__(self, node: NodePath, old_parent: NodePath, new_parent: NodePath, is_2d_gui=False, world=None):
|
||||
def __init__(self, node: NodePath, old_parent: NodePath, new_parent: NodePath,
|
||||
old_parent_item=None, new_parent_item=None, is_2d_gui=False, world=None):
|
||||
self.node = node
|
||||
self.old_parent = old_parent
|
||||
self.new_parent = new_parent
|
||||
self.old_parent_item = old_parent_item # Qt树中的旧父节点项
|
||||
self.new_parent_item = new_parent_item # Qt树中的新父节点项
|
||||
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())
|
||||
@ -453,10 +457,31 @@ class ReparentNodeCommand(Command):
|
||||
self.local_hpr = node.getHpr()
|
||||
self.local_scale = node.getScale()
|
||||
|
||||
def _updateQtTree(self, node_item, new_parent_item):
|
||||
"""更新Qt树控件中的节点位置"""
|
||||
if not node_item or not new_parent_item:
|
||||
return
|
||||
|
||||
# 从当前父节点中移除
|
||||
current_parent = node_item.parent()
|
||||
if current_parent:
|
||||
current_parent.removeChild(node_item)
|
||||
else:
|
||||
# 如果是顶级项目
|
||||
tree_widget = node_item.treeWidget()
|
||||
if tree_widget:
|
||||
index = tree_widget.indexOfTopLevelItem(node_item)
|
||||
if index >= 0:
|
||||
tree_widget.takeTopLevelItem(index)
|
||||
|
||||
# 添加到新父节点
|
||||
new_parent_item.addChild(node_item)
|
||||
|
||||
def execute(self):
|
||||
"""
|
||||
执行重新父化操作
|
||||
"""
|
||||
# 更新Panda3D节点父子关系
|
||||
if self.is_2d_gui and self.world:
|
||||
# 2D GUI元素需要特殊处理
|
||||
if self.new_parent and not self.new_parent.isEmpty():
|
||||
@ -491,6 +516,7 @@ class ReparentNodeCommand(Command):
|
||||
# 在改变父节点前保存当前的缩放值
|
||||
current_scale = self.node.getScale()
|
||||
|
||||
# 恢复Panda3D节点父子关系
|
||||
if self.is_2d_gui and self.world:
|
||||
# 2D GUI元素需要特殊处理
|
||||
if self.old_parent and not self.old_parent.isEmpty():
|
||||
@ -532,6 +558,7 @@ class ReparentNodeCommand(Command):
|
||||
# 在改变父节点前保存当前的缩放值
|
||||
current_scale = self.node.getScale()
|
||||
|
||||
# 重新执行Panda3D节点父子关系更新
|
||||
if self.is_2d_gui and self.world:
|
||||
# 2D GUI元素需要特殊处理
|
||||
if self.new_parent and not self.new_parent.isEmpty():
|
||||
@ -567,4 +594,55 @@ class ReparentNodeCommand(Command):
|
||||
self.node.setScale(self.local_scale)
|
||||
|
||||
|
||||
class CompositeCommand(Command):
|
||||
"""
|
||||
组合命令类,用于同时执行多个命令
|
||||
"""
|
||||
def __init__(self,commands:List[Command]):
|
||||
self.commands = commands
|
||||
|
||||
def execute(self):
|
||||
"""
|
||||
执行所有命令
|
||||
"""
|
||||
for command in self.commands:
|
||||
command.execute()
|
||||
|
||||
def undo(self):
|
||||
"""
|
||||
撤销所有命令(逆序执行)
|
||||
"""
|
||||
for command in reversed(self.commands):
|
||||
command.undo()
|
||||
|
||||
def redo(self):
|
||||
"""
|
||||
重做所有命令
|
||||
"""
|
||||
for command in self.commands:
|
||||
command.redo()
|
||||
|
||||
class MoveLightCommand(Command):
|
||||
def __init__(self, node, old_pos, new_pos, light_object=None):
|
||||
self.node = node
|
||||
self.old_pos = Point3(old_pos)
|
||||
self.new_pos = Point3(new_pos)
|
||||
self.light_object = light_object
|
||||
|
||||
def execute(self): # 将原来的 do() 改为 execute()
|
||||
if self.light_object:
|
||||
self.light_object.pos = self.new_pos
|
||||
if self.node:
|
||||
self.node.setPos(self.new_pos)
|
||||
|
||||
def undo(self):
|
||||
if self.light_object:
|
||||
self.light_object.pos = self.old_pos
|
||||
if self.node:
|
||||
self.node.setPos(self.old_pos)
|
||||
|
||||
def redo(self):
|
||||
self.execute() # 调用 execute() 而不是 do()
|
||||
|
||||
|
||||
|
||||
|
||||
@ -188,15 +188,34 @@ class EventHandler:
|
||||
if self.world.selection.gizmo:
|
||||
#print("准备检查坐标轴点击...")
|
||||
try:
|
||||
highlighted_axis = self.world.selection.gizmoHighlightAxis
|
||||
if highlighted_axis:
|
||||
print(f"✓ 检测到高亮轴: {highlighted_axis},直接开始拖拽")
|
||||
# 直接使用高亮轴开始拖拽
|
||||
self.world.selection.startGizmoDrag(highlighted_axis, x, y)
|
||||
pickerNP.removeNode()
|
||||
return
|
||||
|
||||
# 如果没有高亮轴,再尝试检测点击
|
||||
gizmoAxis = self.world.selection.checkGizmoClick(x, y)
|
||||
if gizmoAxis:
|
||||
#print(f"✓ 检测到坐标轴点击: {gizmoAxis}")
|
||||
print(f"✓ 检测到坐标轴点击: {gizmoAxis}")
|
||||
# 开始坐标轴拖拽
|
||||
self.world.selection.startGizmoDrag(gizmoAxis, x, y)
|
||||
pickerNP.removeNode()
|
||||
return
|
||||
else:
|
||||
print("× 没有点击到坐标轴")
|
||||
|
||||
# gizmoAxis = self.world.selection.checkGizmoClick(x, y)
|
||||
# if gizmoAxis:
|
||||
# #print(f"✓ 检测到坐标轴点击: {gizmoAxis}")
|
||||
# # 开始坐标轴拖拽
|
||||
# self.world.selection.startGizmoDrag(gizmoAxis, x, y)
|
||||
# pickerNP.removeNode()
|
||||
# return
|
||||
# else:
|
||||
# print("× 没有点击到坐标轴")
|
||||
except Exception as e:
|
||||
print(f"❌ 坐标轴点击检测出现异常: {str(e)}")
|
||||
import traceback
|
||||
|
||||
@ -125,7 +125,7 @@ class SelectionSystem:
|
||||
"""为选中的节点创建选择框"""
|
||||
try:
|
||||
if self.selectionBox:
|
||||
print(" 移除现有选择框")
|
||||
#print(" 移除现有选择框")
|
||||
self.selectionBox.removeNode()
|
||||
self.selectionBox = None
|
||||
|
||||
@ -906,13 +906,6 @@ class SelectionSystem:
|
||||
# 创建或获取材质
|
||||
mat = Material()
|
||||
|
||||
# # 设置材质属性 - 使用自发光确保在RenderPipeline下可见
|
||||
# mat.setBaseColor(Vec4(color[0], color[1], color[2], color[3]))
|
||||
# mat.setDiffuse(Vec4(0, 0, 0, 1))
|
||||
# #mat.setEmission(Vec4(color[0], color[1], color[2], 1.0)) # 自发光
|
||||
# mat.setEmission(Vec4(1,1,1,1.0)) # 自发光
|
||||
# mat.set_roughness(1)
|
||||
|
||||
# 设置材质属性 - 使用更自然的颜色,避免过亮的自发光
|
||||
adjusted_color = Vec4(
|
||||
min(color[0]*20, 1.0),
|
||||
@ -922,11 +915,7 @@ class SelectionSystem:
|
||||
)
|
||||
|
||||
mat.setBaseColor(adjusted_color)
|
||||
# mat.setDiffuse(adjusted_color * 0.8) # 稍微降低漫反射亮度
|
||||
# mat.setAmbient(adjusted_color * 0.3) # 设置环境光反射
|
||||
# mat.setSpecular(Vec4(0.3, 0.3, 0.3, 1.0)) # 适度的镜面反射
|
||||
# mat.setShininess(25.0) # 适中的高光强度
|
||||
mat.setEmission(Vec4(1, 1, 1, 1.0)) # 自发光
|
||||
#mat.setEmission(Vec4(1, 1, 1, 1.0)) # 自发光
|
||||
|
||||
# 应用材质
|
||||
handle_node.setMaterial(mat, 1)
|
||||
@ -1515,8 +1504,14 @@ class SelectionSystem:
|
||||
|
||||
self.dragStartMousePos = (mouseX, mouseY)
|
||||
|
||||
light_object = self.gizmoTarget.getPythonTag("rp_light_object")
|
||||
if light_object:
|
||||
self.gizmoTargetStartPos = Point3(light_object.pos)
|
||||
else:
|
||||
self.gizmoTargetStartPos = self.gizmoTarget.getPos()
|
||||
|
||||
# 保存开始拖拽时目标节点的位置和坐标轴的位置
|
||||
self.gizmoTargetStartPos = self.gizmoTarget.getPos()
|
||||
#self.gizmoTargetStartPos = self.gizmoTarget.getPos()
|
||||
self.gizmoStartPos = self.gizmo.getPos(self.world.render) # 坐标轴的世界位置
|
||||
|
||||
# 添加对缩放的支持:保存初始缩放值
|
||||
@ -1895,7 +1890,13 @@ class SelectionSystem:
|
||||
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)
|
||||
from core.Command_System import MoveLightCommand
|
||||
|
||||
light_object = self.gizmoTarget.getPythonTag("rp_light_object")
|
||||
if light_object:
|
||||
command = MoveLightCommand(self.gizmoTarget,self.gizmoTargetStartPos,current_pos,light_object)
|
||||
else:
|
||||
command = MoveNodeCommand(self.gizmoTarget,self.gizmoTargetStartPos,current_pos)
|
||||
self.world.command_manager.execute_command(command)
|
||||
# 如果是缩放操作且缩放发生了变化,则创建缩放命令
|
||||
elif (hasattr(self, 'gizmoTargetStartScale') and hasattr(self, 'gizmoTargetStartScale') and
|
||||
@ -1943,18 +1944,18 @@ class SelectionSystem:
|
||||
try:
|
||||
if self.selectedNode == nodePath:
|
||||
return
|
||||
print(f"\n=== 更新选择状态 ===")
|
||||
#print(f"\n=== 更新选择状态 ===")
|
||||
|
||||
# 如果正在删除节点,避免更新选择
|
||||
if hasattr(self, '_deleting_node') and self._deleting_node:
|
||||
print("正在删除节点,跳过选择更新")
|
||||
print("=== 选择状态更新完成 ===\n")
|
||||
#print("=== 选择状态更新完成 ===\n")
|
||||
return
|
||||
|
||||
node_name = "None"
|
||||
if nodePath and not nodePath.isEmpty():
|
||||
node_name = nodePath.getName()
|
||||
print(f"新选择的节点: {node_name}")
|
||||
#print(f"新选择的节点: {node_name}")
|
||||
|
||||
self.selectedNode = nodePath
|
||||
# 添加兼容性属性
|
||||
@ -1986,7 +1987,6 @@ class SelectionSystem:
|
||||
else:
|
||||
print("× 坐标轴创建失败")
|
||||
|
||||
print(f"✓ 选中了节点: {node_name}")
|
||||
else:
|
||||
print("清除选择...")
|
||||
self.clearSelectionBox()
|
||||
@ -2000,12 +2000,30 @@ class SelectionSystem:
|
||||
self.world.interface_manager.treeWidget.setCurrentItem(None)
|
||||
print("✓ 树形控件选中状态已清空")
|
||||
|
||||
print("=== 选择状态更新完成 ===\n")
|
||||
#print("=== 选择状态更新完成 ===\n")
|
||||
except Exception as e:
|
||||
print(f"更新选择状态失败{str(e)}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
def _reparentTreeItem(self, item, new_parent_item):
|
||||
"""将树项重新父化到新的父项下"""
|
||||
if not item or not new_parent_item:
|
||||
return
|
||||
|
||||
# 从当前父项中移除
|
||||
current_parent = item.parent()
|
||||
if current_parent:
|
||||
current_parent.removeChild(item)
|
||||
else:
|
||||
# 如果是顶级项
|
||||
index = self.indexOfTopLevelItem(item)
|
||||
if index >= 0:
|
||||
self.takeTopLevelItem(index)
|
||||
|
||||
# 添加到新父项
|
||||
new_parent_item.addChild(item)
|
||||
|
||||
def _updateSelectionVisuals(self, nodePath):
|
||||
"""更新选择的视觉效果(选择框和坐标轴)"""
|
||||
try:
|
||||
|
||||
@ -557,12 +557,32 @@ class CoreWorld(Panda3DWorld):
|
||||
self.mouseRightPressed = True
|
||||
self.lastMouseX = evt['x']
|
||||
self.lastMouseY = evt['y']
|
||||
#
|
||||
# # 通过 Qt 窗口隐藏光标并捕获鼠标
|
||||
# try:
|
||||
# if hasattr(self, 'qtWidget') and self.qtWidget:
|
||||
# from PyQt5.QtCore import Qt
|
||||
# self.qtWidget.setCursor(Qt.BlankCursor)
|
||||
# # 捕获鼠标,使其无法离开窗口
|
||||
# self.qtWidget.grabMouse()
|
||||
# except Exception as e:
|
||||
# print(f"通过 Qt 隐藏光标时出错: {e}")
|
||||
|
||||
def mouseReleaseEventRight(self, evt):
|
||||
"""处理鼠标右键释放事件"""
|
||||
#print("右键释放")
|
||||
self.mouseRightPressed = False
|
||||
|
||||
# # 恢复 Qt 窗口光标并释放鼠标捕获
|
||||
# try:
|
||||
# if hasattr(self, 'qtWidget') and self.qtWidget:
|
||||
# from PyQt5.QtCore import Qt
|
||||
# self.qtWidget.unsetCursor() # 恢复默认光标
|
||||
# # 释放鼠标捕获
|
||||
# self.qtWidget.releaseMouse()
|
||||
# except Exception as e:
|
||||
# print(f"恢复 Qt 光标时出错: {e}")
|
||||
|
||||
def mouseMoveEvent(self, evt):
|
||||
"""处理鼠标移动事件 - 只处理相机旋转"""
|
||||
if not evt:
|
||||
|
||||
4
main.py
4
main.py
@ -6,7 +6,7 @@ from demo.video_integration import VideoManager
|
||||
warnings.filterwarnings("ignore", category=DeprecationWarning)
|
||||
|
||||
import sys
|
||||
import builtins # 添加这一行
|
||||
import builtins
|
||||
from PyQt5.QtWidgets import (QApplication, QMainWindow, QMenuBar, QMenu, QAction,
|
||||
QDockWidget, QTreeWidget, QListWidget, QWidget, QVBoxLayout, QTreeWidgetItem,
|
||||
QLabel, QLineEdit, QFormLayout, QDoubleSpinBox, QScrollArea, QTreeView, QInputDialog, QFileDialog, QMessageBox, QDialog, QGroupBox, QHBoxLayout, QPushButton, QDialogButtonBox)
|
||||
@ -14,6 +14,8 @@ from PyQt5.QtCore import Qt, QDir, QUrl
|
||||
from PyQt5.QtGui import QDrag, QPainter, QPixmap
|
||||
from PyQt5.QtWidgets import QFileSystemModel
|
||||
from QPanda3D.QPanda3DWidget import QPanda3DWidget
|
||||
from panda3d.core import loadPrcFileData
|
||||
loadPrcFileData("", "assertions 0")
|
||||
from core.world import CoreWorld
|
||||
from core.selection import SelectionSystem
|
||||
from core.event_handler import EventHandler
|
||||
|
||||
@ -392,13 +392,226 @@ class ProjectManager:
|
||||
|
||||
# 复制场景文件到构建目录
|
||||
shutil.copy2(scene_file, os.path.join(build_dir, "scene.bam"))
|
||||
|
||||
|
||||
# 复制Resources文件夹到build目录
|
||||
source_resources = os.path.join(project_path, "scenes", "resources")
|
||||
self.copy_folder(source_resources, build_dir)
|
||||
|
||||
self._saveGUIElementsToJSON(build_dir, project_path)
|
||||
|
||||
source_render_pipeline = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))),"RenderPipelineFile")
|
||||
dest_render_pipeline = os.path.join(build_dir,"RenderPipelineFile")
|
||||
|
||||
if os.path.exists(source_render_pipeline):
|
||||
if os.path.exists(dest_render_pipeline):
|
||||
shutil.rmtree(dest_render_pipeline)
|
||||
|
||||
shutil.copytree(
|
||||
source_render_pipeline,
|
||||
dest_render_pipeline,
|
||||
ignore=shutil.ignore_patterns('__pycache__','*.pyc','.git','.vscode','*.log')
|
||||
)
|
||||
print("✓ RenderPipelineFile文件夹已复制到build目录")
|
||||
else:
|
||||
print("⚠️ RenderPipelineFile文件夹未找到")
|
||||
|
||||
# 创建标准的应用程序入口文件
|
||||
self._createAppFile(build_dir, project_name)
|
||||
|
||||
# 创建标准的setup.py文件
|
||||
self._createStandardSetupFile(build_dir, project_name)
|
||||
|
||||
|
||||
#创建requirements.txt文件
|
||||
self._createRequirementsFile(build_dir)
|
||||
|
||||
def _saveGUIElementsToJSON(self, build_dir, project_path):
|
||||
"""保存GUI元素到JSON文件,内容与_collectGUIElementInfo保持一致"""
|
||||
try:
|
||||
# 创建目标gui目录
|
||||
gui_dest = os.path.join(build_dir, "gui")
|
||||
if not os.path.exists(gui_dest):
|
||||
os.makedirs(gui_dest)
|
||||
|
||||
# 收集所有GUI元素信息
|
||||
gui_data = []
|
||||
|
||||
# 获取当前场景中的GUI元素
|
||||
if hasattr(self.world, 'gui_elements'):
|
||||
for gui_node in self.world.gui_elements:
|
||||
if gui_node and not gui_node.isEmpty():
|
||||
# 使用_collectGUIElementInfo方法收集信息
|
||||
gui_info = self.world.scene_manager._collectGUIElementInfo(gui_node)
|
||||
if gui_info:
|
||||
gui_data.append(gui_info)
|
||||
print(f"收集GUI元素信息: {gui_info['name']}")
|
||||
|
||||
# 保存GUI信息到JSON文件
|
||||
gui_file_path = os.path.join(gui_dest, "gui_elements.json")
|
||||
with open(gui_file_path, "w", encoding="utf-8") as f:
|
||||
json.dump(gui_data, f, ensure_ascii=False, indent=4)
|
||||
|
||||
print(f"✓ GUI元素数据已保存到 {gui_file_path},共 {len(gui_data)} 个元素")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"⚠️ 保存GUI元素时出错: {str(e)}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
def _createRequirementsFile(self,build_dir):
|
||||
requirements_content = """panda3d>=1.10.13"""
|
||||
|
||||
requirements_path = os.path.join(build_dir,"requirements.txt")
|
||||
with open(requirements_path,"w",encoding="utf-8") as f:
|
||||
f.write(requirements_content)
|
||||
|
||||
def copy_folder(self, source_folder, destination_folder):
|
||||
"""将一个文件夹从源路径复制到目标路径下的resources文件夹中
|
||||
|
||||
Args:
|
||||
source_folder (str): 源文件夹路径
|
||||
destination_folder (str): 目标文件夹路径
|
||||
"""
|
||||
try:
|
||||
# 创建resources文件夹作为目标
|
||||
resources_dest = os.path.join(destination_folder, "resources")
|
||||
|
||||
# 确保目标目录存在
|
||||
if not os.path.exists(destination_folder):
|
||||
os.makedirs(destination_folder)
|
||||
|
||||
# 如果目标resources文件夹已存在,先删除
|
||||
if os.path.exists(resources_dest):
|
||||
shutil.rmtree(resources_dest)
|
||||
|
||||
# 检查源文件夹是否存在
|
||||
if os.path.exists(source_folder):
|
||||
# 复制整个文件夹到resources目录下
|
||||
shutil.copytree(
|
||||
source_folder,
|
||||
resources_dest,
|
||||
ignore=shutil.ignore_patterns('__pycache__', '*.pyc', '.git', '.vscode', '*.log')
|
||||
)
|
||||
print(f"✓ 文件夹已从 {source_folder} 复制到 {resources_dest}")
|
||||
return True
|
||||
else:
|
||||
print(f"⚠️ 源文件夹不存在: {source_folder}")
|
||||
# 即使源文件夹不存在,也创建空的resources目录
|
||||
if not os.path.exists(resources_dest):
|
||||
os.makedirs(resources_dest)
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f"⚠️ 复制文件夹时出错: {str(e)}")
|
||||
return False
|
||||
|
||||
def _copyResourcesToBuild(self, build_dir, project_path):
|
||||
"""复制GUI资源到构建目录的resources文件夹"""
|
||||
try:
|
||||
# 创建目标resources目录
|
||||
resources_dest = os.path.join(build_dir, "resources")
|
||||
|
||||
# 源Resources目录
|
||||
resources_src = os.path.join(project_path, "Resources")
|
||||
|
||||
if os.path.exists(resources_src):
|
||||
# 直接复制整个Resources目录
|
||||
if os.path.exists(resources_dest):
|
||||
shutil.rmtree(resources_dest)
|
||||
|
||||
shutil.copytree(
|
||||
resources_src,
|
||||
resources_dest,
|
||||
ignore=shutil.ignore_patterns('__pycache__', '*.pyc', '.git', '.vscode', '*.log')
|
||||
)
|
||||
print("✓ Resources目录已复制到build/resources")
|
||||
|
||||
# 统计复制的文件数量
|
||||
file_count = 0
|
||||
for root, dirs, files in os.walk(resources_dest):
|
||||
file_count += len(files)
|
||||
print(f"✓ 共复制了 {file_count} 个资源文件")
|
||||
else:
|
||||
# 创建空的resources目录
|
||||
if not os.path.exists(resources_dest):
|
||||
os.makedirs(resources_dest)
|
||||
print("⚠️ 项目中没有Resources目录")
|
||||
|
||||
except Exception as e:
|
||||
print(f"⚠️ 复制资源文件时出错: {str(e)}")
|
||||
|
||||
def _collectResourceFiles(self, project_path):
|
||||
"""收集项目中GUI使用的资源文件"""
|
||||
resource_files = set()
|
||||
|
||||
try:
|
||||
# 收集Resources目录中的所有文件(这是最主要的资源来源)
|
||||
resources_dir = os.path.join(project_path, "Resources")
|
||||
if os.path.exists(resources_dir):
|
||||
for root, dirs, files in os.walk(resources_dir):
|
||||
for file in files:
|
||||
file_path = os.path.join(root, file)
|
||||
# 收集所有文件,不仅仅是媒体文件
|
||||
resource_files.add(file_path)
|
||||
|
||||
# 同时收集场景中引用的特定资源
|
||||
scene_file = os.path.join(project_path, "scenes", "scene.bam")
|
||||
if os.path.exists(scene_file):
|
||||
# 从场景文件中提取资源引用
|
||||
referenced_files = self._extractResourcesFromScene(scene_file, project_path)
|
||||
for file_path in referenced_files:
|
||||
if os.path.isabs(file_path):
|
||||
if os.path.exists(file_path):
|
||||
resource_files.add(file_path)
|
||||
else:
|
||||
# 相对路径
|
||||
full_path = os.path.join(project_path, file_path)
|
||||
if os.path.exists(full_path):
|
||||
resource_files.add(full_path)
|
||||
|
||||
except Exception as e:
|
||||
print(f"收集资源文件时出错: {str(e)}")
|
||||
|
||||
return list(resource_files)
|
||||
|
||||
def _extractResourcesFromScene(self, scene_file, project_path):
|
||||
"""从场景文件中提取资源引用"""
|
||||
referenced_files = []
|
||||
|
||||
try:
|
||||
# 这里应该实现从BAM文件中提取贴图、视频等资源引用的逻辑
|
||||
# 由于直接解析BAM文件比较复杂,我们采用间接方式
|
||||
|
||||
# 检查项目配置文件或其他元数据文件中可能包含的资源引用
|
||||
config_file = os.path.join(project_path, "project.json")
|
||||
if os.path.exists(config_file):
|
||||
try:
|
||||
with open(config_file, "r", encoding="utf-8") as f:
|
||||
project_config = json.load(f)
|
||||
|
||||
# 如果配置中有资源列表信息,可以在这里处理
|
||||
# 这里暂时保持简单实现
|
||||
except Exception as e:
|
||||
print(f"读取项目配置时出错: {str(e)}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"从场景提取资源引用时出错: {str(e)}")
|
||||
|
||||
return referenced_files
|
||||
|
||||
def _isMediaFile(self, file_path):
|
||||
"""判断是否为媒体文件(图片或视频)"""
|
||||
media_extensions = {
|
||||
# 图片格式
|
||||
'.png', '.jpg', '.jpeg', '.bmp', '.gif', '.tga', '.tiff',
|
||||
# 视频格式
|
||||
'.mp4', '.avi', '.mov', '.wmv', '.mkv', '.webm', '.flv'
|
||||
}
|
||||
|
||||
_, ext = os.path.splitext(file_path.lower())
|
||||
return ext in media_extensions
|
||||
|
||||
def _createAppFile(self, build_dir, project_name):
|
||||
"""创建应用程序主文件"""
|
||||
app_code = f'''#!/usr/bin/env python3
|
||||
@ -409,180 +622,199 @@ class ProjectManager:
|
||||
使用Panda3D引擎编辑器创建
|
||||
"""
|
||||
|
||||
from __future__ import print_function
|
||||
#获取渲染管线路径
|
||||
import sys
|
||||
import os
|
||||
|
||||
render_pipeline_path = 'RenderPipelineFile'
|
||||
project_root = os.path.dirname(os.path.abspath(__file__))
|
||||
sys.path.insert(0,project_root)
|
||||
sys.path.insert(0,render_pipeline_path)
|
||||
|
||||
import math
|
||||
from random import random,randint,seed
|
||||
from panda3d.core import Vec3,load_prc_file_data,Filename
|
||||
from direct.showbase.ShowBase import ShowBase
|
||||
from panda3d.core import (loadPrcFileData, WindowProperties, AmbientLight,
|
||||
DirectionalLight, Point3, Vec3)
|
||||
|
||||
# 配置Panda3D
|
||||
loadPrcFileData("", """
|
||||
win-size 1280 720
|
||||
window-title {project_name}
|
||||
show-frame-rate-meter 1
|
||||
sync-video 1
|
||||
want-directtools #f
|
||||
want-tk #f
|
||||
audio-library-name p3openal_audio
|
||||
""")
|
||||
os.chdir(os.path.dirname(os.path.realpath(__file__)))
|
||||
|
||||
class {project_name.replace(' ', '').replace('-', '')}App(ShowBase):
|
||||
"""应用程序主类"""
|
||||
|
||||
class MainApp(ShowBase):
|
||||
def __init__(self):
|
||||
ShowBase.__init__(self)
|
||||
load_prc_file_data("","""
|
||||
win-size 1200 720
|
||||
window-title Render
|
||||
""")
|
||||
|
||||
print(f"启动 {project_name}...")
|
||||
pipeline_path = "../../"
|
||||
|
||||
# 设置窗口属性
|
||||
self.setupWindow()
|
||||
if not os.path.isfile(os.path.join(pipeline_path,"setup.py")):
|
||||
pipeline_path = "../../RenderPipeline"
|
||||
|
||||
# 设置光照
|
||||
self.setupLighting()
|
||||
sys.path.insert(0,pipeline_path)
|
||||
|
||||
# 加载场景
|
||||
self.loadScene()
|
||||
from rpcore import RenderPipeline,SpotLight
|
||||
self.render_pipeline = RenderPipeline()
|
||||
self.render_pipeline.create(self)
|
||||
|
||||
# 设置相机控制
|
||||
self.setupControls()
|
||||
from rpcore.util.movement_controller import MovementController
|
||||
|
||||
print("✓ 应用程序初始化完成")
|
||||
self.render_pipeline.daytime_mgr.time = "12:00"
|
||||
|
||||
def setupWindow(self):
|
||||
"""设置窗口"""
|
||||
# 设置背景色
|
||||
self.setBackgroundColor(0.2, 0.2, 0.2)
|
||||
self.loadFullScene()
|
||||
|
||||
# 设置窗口属性
|
||||
props = WindowProperties()
|
||||
props.setTitle("{project_name}")
|
||||
self.win.requestProperties(props)
|
||||
self.controller = MovementController(self)
|
||||
self.controller.set_initial_position(
|
||||
Vec3(-7.5,-5.3,1.8),Vec3(-5.9,-4.0,1.6))
|
||||
self.controller.setup()
|
||||
|
||||
def setupLighting(self):
|
||||
"""设置光照系统"""
|
||||
# 环境光
|
||||
alight = AmbientLight('alight')
|
||||
alight.setColor((0.3, 0.3, 0.3, 1))
|
||||
alnp = self.render.attachNewNode(alight)
|
||||
self.render.setLight(alnp)
|
||||
|
||||
# 定向光(模拟太阳光)
|
||||
dlight = DirectionalLight('dlight')
|
||||
dlight.setColor((0.8, 0.8, 0.8, 1))
|
||||
dlight.setDirection(Vec3(-1, -1, -1))
|
||||
dlnp = self.render.attachNewNode(dlight)
|
||||
self.render.setLight(dlnp)
|
||||
|
||||
def loadScene(self):
|
||||
"""加载场景"""
|
||||
base.accept("l",self.tour)
|
||||
|
||||
def loadFullScene(self):
|
||||
"""加载完整场景,包括所有元素"""
|
||||
try:
|
||||
# 查找场景文件
|
||||
scene_file = "scene.bam"
|
||||
if not os.path.exists(scene_file):
|
||||
print("警告: 没有找到场景文件,创建默认场景")
|
||||
self.createDefaultScene()
|
||||
return
|
||||
if os.path.exists(scene_file):
|
||||
# 使用readBamFile加载完整场景
|
||||
from panda3d.core import BamCache
|
||||
BamCache.getGlobalPtr().setActive(False) # 禁用缓存以避免问题
|
||||
|
||||
# 加载场景
|
||||
scene = self.loader.loadModel(scene_file)
|
||||
if scene:
|
||||
scene.reparentTo(self.render)
|
||||
print("✓ 场景加载成功")
|
||||
|
||||
# 自动调整相机位置
|
||||
self.adjustCamera()
|
||||
scene = self.loader.loadModel(Filename.fromOsSpecific(scene_file))
|
||||
if scene:
|
||||
scene.reparentTo(self.render)
|
||||
self.render_pipeline.prepare_scene(scene)
|
||||
print("✓ 完整场景加载成功")
|
||||
|
||||
# 处理场景中的各种元素
|
||||
self.processSceneElements(scene)
|
||||
else:
|
||||
print("⚠️ 场景文件加载失败")
|
||||
else:
|
||||
print("警告: 场景加载失败,创建默认场景")
|
||||
self.createDefaultScene()
|
||||
|
||||
print("⚠️ 未找到场景文件")
|
||||
except Exception as e:
|
||||
print(f"加载场景时出错: {{str(e)}}")
|
||||
self.createDefaultScene()
|
||||
|
||||
def createDefaultScene(self):
|
||||
"""创建默认场景"""
|
||||
# 加载默认的环境模型
|
||||
env = self.loader.loadModel("models/environment")
|
||||
if env:
|
||||
env.reparentTo(self.render)
|
||||
env.setScale(0.25)
|
||||
env.setPos(-8, 42, 0)
|
||||
|
||||
# 创建一个简单的立方体作为示例
|
||||
from panda3d.core import CardMaker
|
||||
cm = CardMaker("ground")
|
||||
cm.setFrame(-10, 10, -10, 10)
|
||||
ground = self.render.attachNewNode(cm.generate())
|
||||
ground.setP(-90)
|
||||
ground.setColor(0.5, 0.8, 0.5, 1)
|
||||
|
||||
def adjustCamera(self):
|
||||
"""调整相机位置以查看场景"""
|
||||
# 计算场景边界
|
||||
bounds = self.render.getBounds()
|
||||
if bounds and not bounds.isEmpty():
|
||||
center = bounds.getCenter()
|
||||
radius = bounds.getRadius()
|
||||
print(f"加载完整场景时出错: {{str(e)}}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
def processSceneElements(self, scene):
|
||||
"""处理场景中的各种元素"""
|
||||
try:
|
||||
# 处理光源
|
||||
self.processLights(scene)
|
||||
|
||||
# 设置相机位置
|
||||
distance = radius * 3
|
||||
self.cam.setPos(center.x, center.y - distance, center.z + radius)
|
||||
self.cam.lookAt(center)
|
||||
else:
|
||||
# 默认相机位置
|
||||
self.cam.setPos(0, -20, 5)
|
||||
self.cam.lookAt(0, 0, 0)
|
||||
# 处理GUI元素
|
||||
self.processGUIElements(scene)
|
||||
|
||||
# 处理其他特殊元素
|
||||
self.processSpecialElements(scene)
|
||||
|
||||
except Exception as e:
|
||||
print(f"处理场景元素时出错: {{str(e)}}")
|
||||
|
||||
def processLights(self, scene):
|
||||
"""处理场景中的光源"""
|
||||
try:
|
||||
# 查找并处理点光源
|
||||
point_lights = scene.findAllMatches("**/=element_type=point_light")
|
||||
for light_node in point_lights:
|
||||
try:
|
||||
from RenderPipelineFile.rpcore import PointLight
|
||||
light = PointLight()
|
||||
|
||||
# 恢复光源属性
|
||||
if light_node.hasTag("light_energy"):
|
||||
light.energy = float(light_node.getTag("light_energy"))
|
||||
else:
|
||||
light.energy = 5000
|
||||
|
||||
light.radius = 1000
|
||||
light.inner_radius = 0.4
|
||||
light.set_color_from_temperature(5 * 1000.0)
|
||||
light.casts_shadows = True
|
||||
light.shadow_map_resolution = 256
|
||||
|
||||
light.setPos(light_node.getPos())
|
||||
self.render_pipeline.add_light(light)
|
||||
print(f"✓ 点光源 {{light_node.getName()}} 恢复成功")
|
||||
except Exception as e:
|
||||
print(f"恢复点光源 {{light_node.getName()}} 失败: {{str(e)}}")
|
||||
|
||||
# 查找并处理聚光灯
|
||||
spot_lights = scene.findAllMatches("**/=element_type=spot_light")
|
||||
for light_node in spot_lights:
|
||||
try:
|
||||
from RenderPipelineFile.rpcore import SpotLight
|
||||
light = SpotLight()
|
||||
|
||||
# 恢复光源属性
|
||||
if light_node.hasTag("light_energy"):
|
||||
light.energy = float(light_node.getTag("light_energy"))
|
||||
else:
|
||||
light.energy = 5000
|
||||
|
||||
light.radius = 1000
|
||||
light.inner_radius = 0.4
|
||||
light.set_color_from_temperature(5 * 1000.0)
|
||||
light.casts_shadows = True
|
||||
light.shadow_map_resolution = 256
|
||||
|
||||
light.setPos(light_node.getPos())
|
||||
self.render_pipeline.add_light(light)
|
||||
print(f"✓ 聚光灯 {{light_node.getName()}} 恢复成功")
|
||||
except Exception as e:
|
||||
print(f"恢复聚光灯 {{light_node.getName()}} 失败: {{str(e)}}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"处理光源时出错: {{str(e)}}")
|
||||
|
||||
def processGUIElements(self, scene):
|
||||
"""处理场景中的GUI元素"""
|
||||
try:
|
||||
# 查找并处理2D图像
|
||||
images_2d = scene.findAllMatches("**/=gui_type=image_2d")
|
||||
for img_node in images_2d:
|
||||
try:
|
||||
# GUI元素通常在场景加载时自动处理
|
||||
print(f"✓ 2D图像 {{img_node.getName()}} 已加载")
|
||||
except Exception as e:
|
||||
print(f"处理2D图像 {{img_node.getName()}} 失败: {{str(e)}}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"处理GUI元素时出错: {{str(e)}}")
|
||||
|
||||
def processSpecialElements(self, scene):
|
||||
"""处理特殊元素"""
|
||||
try:
|
||||
# 处理Cesium Tilesets
|
||||
tilesets = scene.findAllMatches("**/=element_type=cesium_tileset")
|
||||
for tileset_node in tilesets:
|
||||
try:
|
||||
# Tilesets需要特殊处理,这里只是标记
|
||||
print(f"✓ Cesium Tileset {{tileset_node.getName()}} 已识别")
|
||||
except Exception as e:
|
||||
print(f"处理Cesium Tileset {{tileset_node.getName()}} 失败: {{str(e)}}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"处理特殊元素时出错: {{str(e)}}")
|
||||
|
||||
def setupControls(self):
|
||||
"""设置相机控制"""
|
||||
# 启用鼠标控制
|
||||
self.accept("wheel_up", self.zoomIn)
|
||||
self.accept("wheel_down", self.zoomOut)
|
||||
|
||||
# 键盘控制说明
|
||||
print("\\n=== 控制说明 ===")
|
||||
print("鼠标滚轮: 缩放")
|
||||
print("ESC: 退出")
|
||||
print("================\\n")
|
||||
|
||||
# ESC键退出
|
||||
self.accept("escape", sys.exit)
|
||||
|
||||
def zoomIn(self):
|
||||
"""放大"""
|
||||
pos = self.cam.getPos()
|
||||
lookAt = Point3(0, 0, 0) # 假设看向原点
|
||||
direction = (lookAt - pos).normalized()
|
||||
newPos = pos + direction * 2
|
||||
self.cam.setPos(newPos)
|
||||
|
||||
def zoomOut(self):
|
||||
"""缩小"""
|
||||
pos = self.cam.getPos()
|
||||
lookAt = Point3(0, 0, 0) # 假设看向原点
|
||||
direction = (lookAt - pos).normalized()
|
||||
newPos = pos - direction * 2
|
||||
self.cam.setPos(newPos)
|
||||
def tour(self):
|
||||
mopath = (
|
||||
(Vec3(-10.8645000458, 9.76458263397, 2.13306283951), Vec3(-133.556228638, -4.23447799683, 0.0)),
|
||||
(Vec3(-10.6538448334, -5.98406457901, 1.68028640747), Vec3(-59.3999938965, -3.32706642151, 0.0)),
|
||||
(Vec3(9.58458328247, -5.63625621796, 2.63269257545), Vec3(58.7906494141, -9.40668964386, 0.0)),
|
||||
(Vec3(6.8135137558, 11.0153560638, 2.25509500504), Vec3(148.762527466, -6.41223621368, 0.0)),
|
||||
(Vec3(-9.07093334198, 3.65908527374, 1.42396306992), Vec3(245.362503052, -3.59927511215, 0.0)),
|
||||
(Vec3(-8.75390911102, -3.82727789879, 0.990055501461), Vec3(296.090484619, -0.604830980301, 0.0)),
|
||||
)
|
||||
self.controller.play_motion_path(mopath,3.0)
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
try:
|
||||
app = {project_name.replace(' ', '').replace('-', '')}App()
|
||||
app.run()
|
||||
except Exception as e:
|
||||
print(f"应用程序启动失败: {{str(e)}}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
input("按Enter键退出...")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
MainApp().run()
|
||||
'''
|
||||
|
||||
|
||||
app_path = os.path.join(build_dir, "main.py")
|
||||
with open(app_path, "w", encoding="utf-8") as f:
|
||||
f.write(app_code)
|
||||
|
||||
|
||||
def _createStandardSetupFile(self, build_dir, project_name):
|
||||
"""创建标准的setup.py文件 - 按照Panda3D官方文档"""
|
||||
setup_code = f'''#!/usr/bin/env python3
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
"""
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import time
|
||||
|
||||
from PyQt5.QtCore import Qt
|
||||
@ -165,17 +166,17 @@ class SceneManager:
|
||||
model.setTag("converted_from", os.path.splitext(original_filepath)[1])
|
||||
model.setTag("converted_to_glb", "true")
|
||||
|
||||
# 特殊处理FBX模型
|
||||
# if filepath.lower().endswith('.fbx'):
|
||||
# print("检测到FBX模型,应用特殊处理...")
|
||||
#
|
||||
# # 将模型缩放设置为原来的1/100
|
||||
# model.setScale(0.01)
|
||||
# print("设置模型缩放为 0.01 (原始大小的1/100)")
|
||||
#
|
||||
# # 设置模型旋转为 (0, 90, 0)
|
||||
# model.setHpr(0, 90, 0)
|
||||
# print("设置模型旋转为 (0, 90, 0)")
|
||||
#特殊处理FBX模型
|
||||
if filepath.lower().endswith('.fbx'):
|
||||
print("检测到FBX模型,应用特殊处理...")
|
||||
|
||||
# 将模型缩放设置为原来的1/100
|
||||
model.setScale(0.01)
|
||||
print("设置模型缩放为 0.01 (原始大小的1/100)")
|
||||
|
||||
# 设置模型旋转为 (0, 90, 0)
|
||||
model.setHpr(0, 90, 0)
|
||||
print("设置模型旋转为 (0, 90, 0)")
|
||||
|
||||
# # 可选的单位转换(主要针对FBX
|
||||
# if apply_unit_conversion and filepath.lower().endswith('.fbx'):
|
||||
@ -1084,6 +1085,10 @@ class SceneManager:
|
||||
if directory and not os.path.exists(directory):
|
||||
os.makedirs(directory)
|
||||
|
||||
resources_dir = os.path.join(directory,"resources")
|
||||
if not os.path.exists(resources_dir):
|
||||
os.makedirs(resources_dir)
|
||||
|
||||
# 存储需要临时隐藏的节点,以便保存后恢复
|
||||
nodes_to_restore = []
|
||||
|
||||
@ -1134,11 +1139,76 @@ class SceneManager:
|
||||
print(self.world.gui_elements)
|
||||
# 收集GUI元素信息(排除3D文本和3D图像)
|
||||
gui_data = []
|
||||
copied_resources = {}
|
||||
for gui_node in gui_elements:
|
||||
gui_info = self._collectGUIElementInfo(gui_node)
|
||||
if gui_info:
|
||||
gui_type = gui_info.get("type","")
|
||||
#处理2d图片
|
||||
if gui_type =="2d_image" and "image_path" in gui_info:
|
||||
original_path = gui_info["image_path"]
|
||||
if original_path and os.path.exists(original_path):
|
||||
resource_name = os.path.basename(original_path)
|
||||
new_path = os.path.join(resources_dir,resource_name)
|
||||
if original_path not in copied_resources:
|
||||
try:
|
||||
shutil.copy2(original_path,new_path)
|
||||
copied_resources[original_path] = new_path
|
||||
print(f"复制图片资源: {original_path} -> {new_path}")
|
||||
except Exception as e:
|
||||
print(f"复制图片资源失败: {original_path}, 错误: {e}")
|
||||
gui_info["image_path"] = new_path
|
||||
|
||||
# 处理3D图片
|
||||
elif gui_type == "3d_image" and "image_path" in gui_info:
|
||||
original_path = gui_info["image_path"]
|
||||
# 确保original_path是有效字符串且文件存在
|
||||
if original_path and isinstance(original_path, str) and os.path.exists(original_path):
|
||||
resource_name = os.path.basename(original_path)
|
||||
new_path = os.path.join(resources_dir, resource_name)
|
||||
if original_path not in copied_resources:
|
||||
try:
|
||||
shutil.copy2(original_path, new_path)
|
||||
copied_resources[original_path] = new_path
|
||||
print(f"复制3D图片资源: {original_path} -> {new_path}")
|
||||
except Exception as e:
|
||||
print(f"复制3D图片资源失败: {original_path}, 错误: {e}")
|
||||
gui_info["image_path"] = new_path
|
||||
|
||||
# 处理背景图片
|
||||
if "bg_image_path" in gui_info and gui_info["bg_image_path"]:
|
||||
original_path = gui_info["bg_image_path"]
|
||||
# 确保original_path是有效字符串且文件存在
|
||||
if original_path and isinstance(original_path, str) and os.path.exists(original_path):
|
||||
resource_name = os.path.basename(original_path)
|
||||
new_path = os.path.join(resources_dir, resource_name)
|
||||
if original_path not in copied_resources:
|
||||
try:
|
||||
shutil.copy2(original_path, new_path)
|
||||
copied_resources[original_path] = new_path
|
||||
print(f"复制背景图片资源: {original_path} -> {new_path}")
|
||||
except Exception as e:
|
||||
print(f"复制背景图片资源失败: {original_path}, 错误: {e}")
|
||||
gui_info["bg_image_path"] = new_path
|
||||
|
||||
# 处理视频资源
|
||||
if gui_type in ["video_screen", "2d_video_screen"] and "video_path" in gui_info:
|
||||
original_path = gui_info["video_path"]
|
||||
# 确保original_path是有效字符串且文件存在
|
||||
if original_path and isinstance(original_path, str) and os.path.exists(original_path):
|
||||
resource_name = os.path.basename(original_path)
|
||||
new_path = os.path.join(resources_dir, resource_name)
|
||||
if original_path not in copied_resources:
|
||||
try:
|
||||
shutil.copy2(original_path, new_path)
|
||||
copied_resources[original_path] = new_path
|
||||
print(f"复制视频资源: {original_path} -> {new_path}")
|
||||
except Exception as e:
|
||||
print(f"复制视频资源失败: {original_path}, 错误: {e}")
|
||||
gui_info["video_path"] = new_path
|
||||
|
||||
gui_data.append(gui_info)
|
||||
print(f"添加GUI信息{gui_info['name']}")
|
||||
print(f"添加GUI信息: {gui_info['name']}")
|
||||
|
||||
# 保存GUI信息到JSON文件(确保即使没有GUI元素也创建有效的空JSON数组)
|
||||
try:
|
||||
@ -1151,16 +1221,6 @@ class SceneManager:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
# 添加tilesets节点
|
||||
for tileset_info in self.tilesets:
|
||||
if tileset_info.get('node') and not tileset_info['node'].isEmpty():
|
||||
all_nodes.append(tileset_info['node'])
|
||||
|
||||
# 添加Cesium tilesets节点
|
||||
for tileset_name, tileset_info in self.cesium_integration.tilesets.items():
|
||||
if tileset_info.get('node') and not tileset_info['node'].isEmpty():
|
||||
all_nodes.append(tileset_info['node'])
|
||||
|
||||
# 保存所有节点的信息
|
||||
for node in all_nodes:
|
||||
if node.isEmpty():
|
||||
@ -1436,6 +1496,10 @@ class SceneManager:
|
||||
#存储节点以便后续处理父子关系
|
||||
loaded_nodes[nodePath.getName()] = nodePath
|
||||
|
||||
if nodePath.getName().startswith('ground'):
|
||||
print(f"{indent}跳过ground节点: {nodePath.getName()}")
|
||||
return
|
||||
|
||||
# 跳过render节点的递归
|
||||
if nodePath.getName() == "render" and depth > 0:
|
||||
print(f"{indent}跳过重复的render节点")
|
||||
@ -1782,6 +1846,10 @@ class SceneManager:
|
||||
|
||||
def _shouldSkipNodeInTree(self, nodePath):
|
||||
"""判断节点是否应该在场景树中跳过显示"""
|
||||
|
||||
if nodePath.getName().startswith('ground'):
|
||||
return True
|
||||
|
||||
# 跳过render节点的递归
|
||||
if nodePath.getName() == "render":
|
||||
return True
|
||||
@ -1805,12 +1873,13 @@ class SceneManager:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def _recreateGUIElementsFromData(self, gui_data):
|
||||
"""根据保存的GUI数据重新创建GUI元素"""
|
||||
try:
|
||||
gui_manager = getattr(self.world, 'gui_manager', None)
|
||||
property_manager = getattr(self.world, 'property_panel', None)
|
||||
info_panel_manager = getattr(self.world,'info_panel_manager',None)
|
||||
info_panel_manager = getattr(self.world, 'info_panel_manager', None)
|
||||
if not gui_manager:
|
||||
print("GUI管理器未找到,无法重建GUI元素")
|
||||
return
|
||||
@ -1818,7 +1887,7 @@ class SceneManager:
|
||||
|
||||
processed_names = set()
|
||||
created_elements = {}
|
||||
#存储原始的缩放和位置信息,用于后续计算
|
||||
# 存储原始的缩放和位置信息,用于后续计算
|
||||
element_original_data = {}
|
||||
|
||||
# 第一遍:收集所有元素信息
|
||||
@ -1832,10 +1901,10 @@ class SceneManager:
|
||||
|
||||
valid_parents = set()
|
||||
for gui_info in gui_data:
|
||||
name = gui_info.get("name",f"gui_element_{gui_info.get('index',0)}")
|
||||
name = gui_info.get("name", f"gui_element_{gui_info.get('index', 0)}")
|
||||
valid_parents.add(name)
|
||||
|
||||
if hasattr(self.world,'gui_elements'):
|
||||
if hasattr(self.world, 'gui_elements'):
|
||||
for elem in self.world.gui_elements:
|
||||
if elem and not elem.isEmpty():
|
||||
valid_parents.add(elem.getName())
|
||||
@ -1877,7 +1946,7 @@ class SceneManager:
|
||||
print(f" 文本: {text}")
|
||||
print(f" 图像路径: {image_path}")
|
||||
print(f" 背景图片路径: {bg_image_path}")
|
||||
print(f"视频路径:{video_path}")
|
||||
print(f" 视频路径: {video_path}")
|
||||
|
||||
absolute_position = list(position)
|
||||
absolute_scale = list(scale)
|
||||
@ -1926,9 +1995,9 @@ class SceneManager:
|
||||
new_element = gui_manager.createGUI2DImage(
|
||||
pos=tuple(absolute_position),
|
||||
image_path=image_path,
|
||||
size=scale_value*0.2
|
||||
size=scale_value * 0.2
|
||||
)
|
||||
elif gui_type == "3d_text" and hasattr(gui_manager,'createGUI3DText'):
|
||||
elif gui_type == "3d_text" and hasattr(gui_manager, 'createGUI3DText'):
|
||||
size = absolute_scale[0] if absolute_scale and len(absolute_scale) > 0 else 0.5
|
||||
new_element = gui_manager.createGUI3DText(
|
||||
pos=tuple(absolute_position),
|
||||
@ -1952,7 +2021,7 @@ class SceneManager:
|
||||
image_path=image_path,
|
||||
size=size
|
||||
)
|
||||
elif gui_type == "video_screen" and hasattr(gui_manager,'createVideoScreen'):
|
||||
elif gui_type == "video_screen" and hasattr(gui_manager, 'createVideoScreen'):
|
||||
new_element = gui_manager.createVideoScreen(
|
||||
pos=tuple(absolute_position),
|
||||
size=absolute_scale,
|
||||
@ -1961,30 +2030,24 @@ class SceneManager:
|
||||
if video_path and new_element:
|
||||
if video_path.startswith("http://") or video_path.startswith("https://"):
|
||||
from direct.task.TaskManagerGlobal import taskMgr
|
||||
|
||||
def load_video_stream_task(task):
|
||||
if hasattr(property_manager,'_loadVideoFromURLWithOpenCV_3D'):
|
||||
property_manager._loadVideoFromURLWithOpenCV_3D(new_element,video_path)
|
||||
if hasattr(property_manager, '_loadVideoFromURLWithOpenCV_3D'):
|
||||
property_manager._loadVideoFromURLWithOpenCV_3D(new_element, video_path)
|
||||
return task.done
|
||||
|
||||
taskMgr.doMethodLater(0.5, load_video_stream_task, 'loadVideoStreamTask')
|
||||
else:
|
||||
if hasattr(gui_manager,'loadVideoFile'):
|
||||
if hasattr(gui_manager, 'loadVideoFile'):
|
||||
from direct.task.TaskManagerGlobal import taskMgr
|
||||
|
||||
def load_video_file_task(task):
|
||||
gui_manager.loadVideoFile(new_element,video_path)
|
||||
gui_manager.loadVideoFile(new_element, video_path)
|
||||
return task.done
|
||||
taskMgr.doMethodLater(0.1,load_video_file_task,'loadVideoFileTask')
|
||||
|
||||
# if video_path and new_element and hasattr(gui_manager, 'loadVideoFile'):
|
||||
# # 延迟一帧执行,确保节点完全初始化
|
||||
# from direct.task.TaskManagerGlobal import taskMgr
|
||||
# def load_video_task(task):
|
||||
# gui_manager.loadVideoFile(new_element, video_path)
|
||||
# return task.done
|
||||
#
|
||||
# taskMgr.doMethodLater(0.1, load_video_task, 'loadVideoTask')
|
||||
taskMgr.doMethodLater(0.1, load_video_file_task, 'loadVideoFileTask')
|
||||
|
||||
elif gui_type == "2d_video_screen" and hasattr(gui_manager,'createGUI2DVideoScreen'):
|
||||
elif gui_type == "2d_video_screen" and hasattr(gui_manager, 'createGUI2DVideoScreen'):
|
||||
new_element = gui_manager.createGUI2DVideoScreen(
|
||||
pos=tuple(absolute_position),
|
||||
size=absolute_scale,
|
||||
@ -2000,13 +2063,14 @@ class SceneManager:
|
||||
# return task.done
|
||||
# taskMgr.doMethodLater(0.1,load_2d_video_stream_task,'load2DVideoStreamTask')
|
||||
else:
|
||||
if hasattr(property_manager,'load2DVideoFile'):
|
||||
if hasattr(property_manager, 'load2DVideoFile'):
|
||||
from direct.task.TaskManagerGlobal import taskMgr
|
||||
def load_2d_video_file_task(task):
|
||||
property_manager.load2DVideoFile(new_element,video_path)
|
||||
return task.done
|
||||
taskMgr.doMethodLater(0.1,load_2d_video_file_task,'load2DVideoFileTask')
|
||||
|
||||
def load_2d_video_file_task(task):
|
||||
property_manager.load2DVideoFile(new_element, video_path)
|
||||
return task.done
|
||||
|
||||
taskMgr.doMethodLater(0.1, load_2d_video_file_task, 'load2DVideoFileTask')
|
||||
|
||||
# 如果创建成功,设置属性
|
||||
if new_element:
|
||||
@ -2048,84 +2112,84 @@ class SceneManager:
|
||||
traceback.print_exc()
|
||||
continue
|
||||
|
||||
# 第二遍:设置父子级关系并更新Qt树
|
||||
print("开始设置父子级关系...")
|
||||
try:
|
||||
# 创建父子级关系映射
|
||||
parent_child_map = {}
|
||||
for gui_info in gui_data:
|
||||
name = gui_info.get("name")
|
||||
parent_name = gui_info.get("parent_name")
|
||||
# 第二遍:设置父子级关系并更新Qt树
|
||||
print("开始设置父子级关系...")
|
||||
try:
|
||||
# 创建父子级关系映射
|
||||
parent_child_map = {}
|
||||
for gui_info in gui_data:
|
||||
name = gui_info.get("name")
|
||||
parent_name = gui_info.get("parent_name")
|
||||
|
||||
if name and parent_name and parent_name in created_elements:
|
||||
parent_child_map[name] = parent_name
|
||||
print(f"父子级关系映射: {parent_name} -> {name}")
|
||||
if name and parent_name and parent_name in created_elements:
|
||||
parent_child_map[name] = parent_name
|
||||
print(f"父子级关系映射: {parent_name} -> {name}")
|
||||
|
||||
# 按正确的顺序设置父子级关系并更新Qt树
|
||||
tree_widget = self._get_tree_widget()
|
||||
if tree_widget:
|
||||
# 先将所有元素添加到Qt树中
|
||||
qt_tree_items = {}
|
||||
for name, element in created_elements.items():
|
||||
# 尝试在Qt树中找到对应的项,如果找不到则创建
|
||||
qt_item = self._findOrCreateQtTreeItem(tree_widget, element, name)
|
||||
if qt_item:
|
||||
qt_tree_items[name] = qt_item
|
||||
# 按正确的顺序设置父子级关系并更新Qt树
|
||||
tree_widget = self._get_tree_widget()
|
||||
if tree_widget:
|
||||
# 先将所有元素添加到Qt树中
|
||||
qt_tree_items = {}
|
||||
for name, element in created_elements.items():
|
||||
# 尝试在Qt树中找到对应的项,如果找不到则创建
|
||||
qt_item = self._findOrCreateQtTreeItem(tree_widget, element, name)
|
||||
if qt_item:
|
||||
qt_tree_items[name] = qt_item
|
||||
|
||||
# 然后设置父子级关系
|
||||
for child_name, parent_name in parent_child_map.items():
|
||||
try:
|
||||
if child_name in created_elements and parent_name in created_elements:
|
||||
child_element = created_elements[child_name]
|
||||
parent_element = created_elements[parent_name]
|
||||
# 然后设置父子级关系
|
||||
for child_name, parent_name in parent_child_map.items():
|
||||
try:
|
||||
if child_name in created_elements and parent_name in created_elements:
|
||||
child_element = created_elements[child_name]
|
||||
parent_element = created_elements[parent_name]
|
||||
|
||||
# 设置父子级关系
|
||||
if hasattr(child_element, 'reparentTo'):
|
||||
child_element.reparentTo(parent_element)
|
||||
print(f"成功设置父子级关系: {parent_name} -> {child_name}")
|
||||
# 设置父子级关系
|
||||
if hasattr(child_element, 'reparentTo'):
|
||||
child_element.reparentTo(parent_element)
|
||||
print(f"成功设置父子级关系: {parent_name} -> {child_name}")
|
||||
|
||||
# 更新Qt树显示
|
||||
if child_name in qt_tree_items and parent_name in qt_tree_items:
|
||||
child_item = qt_tree_items[child_name]
|
||||
parent_item = qt_tree_items[parent_name]
|
||||
# 更新Qt树显示
|
||||
if child_name in qt_tree_items and parent_name in qt_tree_items:
|
||||
child_item = qt_tree_items[child_name]
|
||||
parent_item = qt_tree_items[parent_name]
|
||||
|
||||
# 从当前位置移除子项
|
||||
if child_item.parent():
|
||||
child_item.parent().removeChild(child_item)
|
||||
else:
|
||||
# 如果是顶级项,从树中移除
|
||||
index = tree_widget.indexOfTopLevelItem(child_item)
|
||||
if index >= 0:
|
||||
tree_widget.takeTopLevelItem(index)
|
||||
# 从当前位置移除子项
|
||||
if child_item.parent():
|
||||
child_item.parent().removeChild(child_item)
|
||||
else:
|
||||
# 如果是顶级项,从树中移除
|
||||
index = tree_widget.indexOfTopLevelItem(child_item)
|
||||
if index >= 0:
|
||||
tree_widget.takeTopLevelItem(index)
|
||||
|
||||
# 将子项添加到新的父项下
|
||||
parent_item.addChild(child_item)
|
||||
print(f"Qt树更新: {child_name} 移动到 {parent_name} 下")
|
||||
else:
|
||||
print(f"元素 {child_name} 不支持 reparentTo 操作")
|
||||
# 将子项添加到新的父项下
|
||||
parent_item.addChild(child_item)
|
||||
print(f"Qt树更新: {child_name} 移动到 {parent_name} 下")
|
||||
else:
|
||||
print(f"元素未找到: 父级={parent_name}, 子级={child_name}")
|
||||
except Exception as e:
|
||||
print(f"设置父子级关系失败 {parent_name} -> {child_name}: {e}")
|
||||
continue
|
||||
else:
|
||||
# 如果没有tree_widget,只设置父子级关系
|
||||
for child_name, parent_name in parent_child_map.items():
|
||||
try:
|
||||
if child_name in created_elements and parent_name in created_elements:
|
||||
child_element = created_elements[child_name]
|
||||
parent_element = created_elements[parent_name]
|
||||
print(f"元素 {child_name} 不支持 reparentTo 操作")
|
||||
else:
|
||||
print(f"元素未找到: 父级={parent_name}, 子级={child_name}")
|
||||
except Exception as e:
|
||||
print(f"设置父子级关系失败 {parent_name} -> {child_name}: {e}")
|
||||
continue
|
||||
else:
|
||||
# 如果没有tree_widget,只设置父子级关系
|
||||
for child_name, parent_name in parent_child_map.items():
|
||||
try:
|
||||
if child_name in created_elements and parent_name in created_elements:
|
||||
child_element = created_elements[child_name]
|
||||
parent_element = created_elements[parent_name]
|
||||
|
||||
# 设置父子级关系
|
||||
if hasattr(child_element, 'reparentTo'):
|
||||
child_element.reparentTo(parent_element)
|
||||
print(f"成功设置父子级关系: {parent_name} -> {child_name}")
|
||||
except Exception as e:
|
||||
print(f"设置父子级关系失败 {parent_name} -> {child_name}: {e}")
|
||||
continue
|
||||
# 设置父子级关系
|
||||
if hasattr(child_element, 'reparentTo'):
|
||||
child_element.reparentTo(parent_element)
|
||||
print(f"成功设置父子级关系: {parent_name} -> {child_name}")
|
||||
except Exception as e:
|
||||
print(f"设置父子级关系失败 {parent_name} -> {child_name}: {e}")
|
||||
continue
|
||||
|
||||
except Exception as e:
|
||||
print(f"设置父子级关系时出错: {e}")
|
||||
except Exception as e:
|
||||
print(f"设置父子级关系时出错: {e}")
|
||||
# 第三遍:重新挂载脚本
|
||||
print("开始重新挂载脚本...")
|
||||
for gui_info in gui_data:
|
||||
@ -2173,13 +2237,15 @@ class SceneManager:
|
||||
else:
|
||||
print(f"为 {name} 添加脚本失败: {script_name}")
|
||||
except Exception as e:
|
||||
print(f"重新挂载脚本失败 {gui_info.get('name', 'unknown')}: {e}")
|
||||
print(f"重新挂载脚本失败: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
continue
|
||||
|
||||
print("GUI元素重建完成")
|
||||
print(f"GUI元素重建完成,共创建 {len(created_elements)} 个元素")
|
||||
|
||||
except Exception as e:
|
||||
print(f"重建GUI元素时发生错误: {e}")
|
||||
print(f"重建GUI元素时出错: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
|
||||
@ -51,7 +51,7 @@ class InterfaceManager:
|
||||
|
||||
def onTreeItemClicked(self, item, column):
|
||||
"""处理树形控件项目点击事件"""
|
||||
print(f"树形控件点击事件触发,item: {item}, column: {column}")
|
||||
#print(f"树形控件点击事件触发,item: {item}, column: {column}")
|
||||
|
||||
# 检查是否点击了空白区域
|
||||
# 当点击空白区域时,item可能是一个空的QTreeWidgetItem对象
|
||||
|
||||
@ -452,8 +452,7 @@ class MainWindow(QMainWindow):
|
||||
select_icon = get_icon('select_tool', QSize(16, 16))
|
||||
if not select_icon.isNull():
|
||||
self.selectTool.setIcon(select_icon)
|
||||
else:
|
||||
self.selectTool.setText('选择') # 如果没有图标则显示文字
|
||||
self.selectTool.setText('选择') # 如果没有图标则显示文字
|
||||
self.selectTool.setIconSize(QSize(16, 16))
|
||||
self.selectTool.setCheckable(True)
|
||||
self.selectTool.setToolTip("选择工具 (Q)")
|
||||
@ -817,11 +816,11 @@ class MainWindow(QMainWindow):
|
||||
"""设置视图菜单动作"""
|
||||
# 连接视图菜单事件
|
||||
self.viewPerspectiveAction.triggered.connect(self.onViewPerspective)
|
||||
self.viewTopAction.triggered.connect(self.onViewTop)
|
||||
self.viewFrontAction.triggered.connect(self.onViewFront)
|
||||
#self.viewTopAction.triggered.connect(self.onViewTop)
|
||||
#self.viewFrontAction.triggered.connect(self.onViewFront)
|
||||
self.viewOrthographicAction = self.viewMenu.addAction('正交视图') # 添加正交视图动作
|
||||
self.viewOrthographicAction.triggered.connect(self.onViewOrthographic)
|
||||
self.viewGridAction.triggered.connect(self.onViewGrid) # 添加网格显示的信号连接
|
||||
#self.viewOrthographicAction.triggered.connect(self.onViewOrthographic)
|
||||
#self.viewGridAction.triggered.connect(self.onViewGrid) # 添加网格显示的信号连接
|
||||
|
||||
# 保存原始相机设置
|
||||
self._original_camera_fov = 80
|
||||
|
||||
@ -12,6 +12,7 @@ from PyQt5.QtCore import Qt
|
||||
from deploy_libs.unicodedata import normalize
|
||||
from direct.actor.Actor import Actor
|
||||
from direct.gui import DirectGui
|
||||
from direct.task.TaskManagerGlobal import taskMgr
|
||||
from idna import check_label
|
||||
from jinja2.compiler import has_safe_repr
|
||||
from panda3d.core import Vec3, Vec4, transpose, TransparencyAttrib, PartGroup, ColorAttrib, NodePath, Point3
|
||||
@ -8615,12 +8616,35 @@ except Exception as e:
|
||||
actor = self._getActor(origin_model)
|
||||
if not actor:
|
||||
return
|
||||
|
||||
original_world_pos = origin_model.getPos(self.world.render)
|
||||
original_world_hpr = origin_model.getHpr(self.world.render)
|
||||
original_world_scale = origin_model.getScale(self.world.render)
|
||||
|
||||
actor.setPos(origin_model.getPos())
|
||||
actor.setHpr(origin_model.getHpr())
|
||||
actor.setScale(origin_model.getScale())
|
||||
|
||||
origin_model.hide()
|
||||
actor.show()
|
||||
|
||||
#创建人物来维持世界坐标不变
|
||||
def maintainWorldPosition(task):
|
||||
try:
|
||||
if not actor.isEmpty():
|
||||
actor.setPos(self.world.render,original_world_pos)
|
||||
actor.setHpr(self.world.render,original_world_hpr)
|
||||
actor.setScale(self.world.render,original_world_scale)
|
||||
return task.cont
|
||||
else:
|
||||
return task.done
|
||||
except:
|
||||
return task.done
|
||||
|
||||
taskMgr.add(maintainWorldPosition,f"maintain_anim_pos_{id(actor)}")
|
||||
|
||||
|
||||
|
||||
if hasattr(self, 'animation_combo'):
|
||||
# 获取原始动画名称(存储在 userData 中)
|
||||
current_index = self.animation_combo.currentIndex()
|
||||
|
||||
Loading…
Reference in New Issue
Block a user