1
0
forked from Rowland/EG

修改保存逻辑,添加打包功能

This commit is contained in:
Hector 2025-09-25 16:59:06 +08:00
parent 7c797d74d5
commit a66c097048
12 changed files with 764 additions and 316 deletions

View File

@ -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):

View File

@ -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)

View File

@ -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()

View File

@ -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

View File

@ -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:

View File

@ -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:

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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对象

View File

@ -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

View File

@ -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()