跨平台路径标准化,坐标系问题修改,缩放后子节点移动问题修复
This commit is contained in:
parent
ee0fe2741d
commit
3261163ec8
File diff suppressed because one or more lines are too long
@ -13,6 +13,7 @@ from panda3d.core import (Vec3, Point3, Point2, LineSegs, ColorAttrib, RenderSta
|
||||
CollisionNode, CollisionRay, GeomNode, BitMask32, Material, LColor, DepthWriteAttrib,
|
||||
TransparencyAttrib)
|
||||
from direct.task.TaskManagerGlobal import taskMgr
|
||||
import math
|
||||
|
||||
|
||||
class SelectionSystem:
|
||||
@ -49,14 +50,14 @@ class SelectionSystem:
|
||||
# 高亮相关
|
||||
self.gizmoHighlightAxis = None
|
||||
self.gizmo_colors = {
|
||||
"x": (1, 0, 0, 1), # 红色
|
||||
"y": (0, 1, 0, 1), # 绿色
|
||||
"z": (0, 0, 1, 1) # 蓝色
|
||||
"x": (1*10, 0, 0, 1), # 红色
|
||||
"y": (0, 1*10, 0, 1), # 绿色
|
||||
"z": (0, 0, 1*10, 1) # 蓝色
|
||||
}
|
||||
self.gizmo_highlight_colors = {
|
||||
"x": (1.5, 1.5, 0, 1), # 黄色高亮
|
||||
"y": (1.5, 1.5, 0, 1), # 黄色高亮
|
||||
"z": (1.5, 1.5, 0, 1) # 黄色高亮
|
||||
"x": (1.5*20, 1.5*20, 0, 1), # 黄色高亮
|
||||
"y": (1.5*20, 1.5*20, 0, 1), # 黄色高亮
|
||||
"z": (1.5*20, 1.5*20, 0, 1) # 黄色高亮
|
||||
}
|
||||
|
||||
print("✓ 选择和变换系统初始化完成")
|
||||
@ -595,7 +596,7 @@ class SelectionSystem:
|
||||
print(f"❌ 最后备用方案也失败: {e}")
|
||||
|
||||
def updateGizmoTask(self, task):
|
||||
"""坐标轴更新任务"""
|
||||
"""坐标轴更新任务 - 包含固定大小功能"""
|
||||
try:
|
||||
if not self.gizmo or not self.gizmoTarget:
|
||||
return task.done
|
||||
@ -632,12 +633,58 @@ class SelectionSystem:
|
||||
# 顶级模型:使用世界坐标系朝向
|
||||
self.gizmo.setHpr(0, 0, 0)
|
||||
|
||||
return task.cont
|
||||
# 【新功能】:动态调整坐标轴大小,保持固定的屏幕大小
|
||||
self._updateGizmoScreenSize()
|
||||
|
||||
return task.cont
|
||||
|
||||
except Exception as e:
|
||||
print(f"坐标轴更新任务出错: {str(e)}")
|
||||
return task.done
|
||||
|
||||
def _updateGizmoScreenSize(self):
|
||||
"""动态调整坐标轴大小,保持固定的屏幕大小"""
|
||||
try:
|
||||
if not self.gizmo or not self.gizmoTarget:
|
||||
return
|
||||
|
||||
# 计算相机到坐标轴的距离
|
||||
gizmo_world_pos = self.gizmo.getPos(self.world.render)
|
||||
cam_pos = self.world.cam.getPos()
|
||||
distance_to_gizmo = (cam_pos - gizmo_world_pos).length()
|
||||
|
||||
# 获取相机视野角度和窗口尺寸
|
||||
fov = self.world.cam.node().getLens().getFov()[0] # 水平视野角度
|
||||
fov_radians = math.radians(fov)
|
||||
winWidth, winHeight = self.world.getWindowSize()
|
||||
|
||||
# 计算一个像素在坐标轴距离处对应的世界坐标大小
|
||||
pixel_to_world_ratio = distance_to_gizmo * math.tan(fov_radians / 2) / (winWidth / 2)
|
||||
|
||||
# 设定坐标轴在屏幕上的期望像素长度(固定值)
|
||||
desired_screen_length = 120 # 像素
|
||||
|
||||
# 计算世界坐标系中的轴长度
|
||||
world_axis_length = desired_screen_length * pixel_to_world_ratio
|
||||
|
||||
# 计算缩放比例(相对于基础轴长度)
|
||||
scale_factor = world_axis_length / self.axis_length
|
||||
|
||||
# 应用缩放到坐标轴
|
||||
self.gizmo.setScale(scale_factor)
|
||||
|
||||
# 限制缩放范围,避免过大或过小
|
||||
min_scale = 0.1
|
||||
max_scale = 10.0
|
||||
final_scale = max(min_scale, min(max_scale, scale_factor))
|
||||
|
||||
if final_scale != scale_factor:
|
||||
self.gizmo.setScale(final_scale)
|
||||
|
||||
except Exception as e:
|
||||
# 静默处理错误,避免频繁输出
|
||||
pass
|
||||
|
||||
def clearGizmo(self):
|
||||
"""清除坐标轴"""
|
||||
if self.gizmo:
|
||||
@ -1247,8 +1294,38 @@ class SelectionSystem:
|
||||
# 使用透视投影公式:world_size = screen_size * distance * tan(fov/2) / (screen_width/2)
|
||||
pixel_to_world_ratio = distance_to_object * math.tan(fov_radians / 2) / (winWidth / 2)
|
||||
|
||||
# 使用动态比例因子
|
||||
scale_factor = pixel_to_world_ratio * 0.5 # 0.5是调整因子,可以根据需要调整
|
||||
# 【改进修复】:智能缩放补偿,区分继承缩放和本体缩放
|
||||
# 计算父节点链的累积缩放(不包括目标节点本身)
|
||||
parent_cumulative_scale = 1.0
|
||||
current_node = self.gizmoTarget.getParent()
|
||||
while current_node and current_node != self.world.render:
|
||||
node_scale = current_node.getScale()
|
||||
# 使用缩放的几何平均值作为累积因子
|
||||
scale_magnitude = (abs(node_scale.x) * abs(node_scale.y) * abs(node_scale.z)) ** (1.0/3.0)
|
||||
parent_cumulative_scale *= scale_magnitude
|
||||
current_node = current_node.getParent()
|
||||
|
||||
# 获取目标节点自身的缩放
|
||||
target_scale = self.gizmoTarget.getScale()
|
||||
target_scale_magnitude = (abs(target_scale.x) * abs(target_scale.y) * abs(target_scale.z)) ** (1.0/3.0)
|
||||
|
||||
# 智能补偿策略:
|
||||
# 1. 只对父节点链的小缩放进行完全补偿(这通常是单位转换导致的)
|
||||
# 2. 对目标节点自身的缩放进行部分补偿(避免大模型缩小后移动过快)
|
||||
parent_compensation = 1.0 / parent_cumulative_scale if parent_cumulative_scale > 0 else 1.0
|
||||
|
||||
# 对目标节点自身的缩放使用平方根补偿,减少过度补偿
|
||||
target_compensation = 1.0 / math.sqrt(target_scale_magnitude) if target_scale_magnitude > 0 else 1.0
|
||||
|
||||
# 限制目标补偿的最大值,避免移动过快
|
||||
target_compensation = min(target_compensation, 10.0) # 最大10倍补偿
|
||||
|
||||
# 综合补偿因子
|
||||
total_compensation = parent_compensation * target_compensation
|
||||
scale_factor = pixel_to_world_ratio * 0.5 * total_compensation
|
||||
|
||||
print(f"智能缩放补偿: 父链缩放={parent_cumulative_scale:.4f}, 目标缩放={target_scale_magnitude:.4f}")
|
||||
print(f"父链补偿={parent_compensation:.2f}, 目标补偿={target_compensation:.2f}, 总补偿={total_compensation:.2f}")
|
||||
|
||||
# 【关键修复】:在正确的坐标系中计算移动向量
|
||||
# 计算移动距离(标量)
|
||||
@ -1282,6 +1359,8 @@ class SelectionSystem:
|
||||
newPos = self.gizmoTargetStartPos + movement
|
||||
self.gizmoTarget.setPos(newPos)
|
||||
|
||||
self.world.property_panel.refreshModelValues(self.gizmoTarget)
|
||||
|
||||
# 每次拖拽都输出调试信息(但限制频率)
|
||||
if not hasattr(self, '_last_drag_debug_time'):
|
||||
self._last_drag_debug_time = 0
|
||||
|
||||
22
main.py
22
main.py
@ -391,7 +391,27 @@ class MyWorld(CoreWorld):
|
||||
# 模型导入和处理方法 - 代理到scene_manager
|
||||
def importModel(self, filepath):
|
||||
"""导入模型到场景"""
|
||||
return self.scene_manager.importModel(filepath)
|
||||
# 检查是否是FBX文件,如果是则询问用户是否要转换为GLB
|
||||
if filepath.lower().endswith('.fbx'):
|
||||
try:
|
||||
from PyQt5.QtWidgets import QMessageBox
|
||||
reply = QMessageBox.question(
|
||||
None,
|
||||
'格式转换选择',
|
||||
'FBX文件检测到!\n\n是否要尝试转换为GLB格式以获得更好的动画支持?\n\n点击"是":尝试转换为GLB格式(需要安装转换工具)\n点击"否":直接使用原始FBX格式导入',
|
||||
QMessageBox.Yes | QMessageBox.No,
|
||||
QMessageBox.Yes
|
||||
)
|
||||
auto_convert = (reply == QMessageBox.Yes)
|
||||
except ImportError:
|
||||
# 如果没有PyQt5,默认不转换
|
||||
print("检测到FBX文件,由于GUI不可用,将直接使用原始格式导入")
|
||||
auto_convert = False
|
||||
else:
|
||||
# 非FBX文件,保持原有逻辑
|
||||
auto_convert = True
|
||||
|
||||
return self.scene_manager.importModel(filepath, auto_convert_to_glb=auto_convert)
|
||||
|
||||
def importModelAsync(self, filepath):
|
||||
"""异步导入模型"""
|
||||
|
||||
@ -138,64 +138,64 @@ class SceneManager:
|
||||
print(f"导入模型失败: {str(e)}")
|
||||
return None
|
||||
|
||||
def importAnimatedFBX(self, filepath, scale=0.01, auto_play=True):
|
||||
"""导入带动画的FBX模型(使用assimp)
|
||||
|
||||
Args:
|
||||
filepath: FBX文件路径
|
||||
scale: 缩放比例(默认0.01,从厘米转换到米)
|
||||
auto_play: 是否自动播放第一个动画
|
||||
|
||||
Returns:
|
||||
Actor对象,如果加载失败返回None
|
||||
"""
|
||||
try:
|
||||
print(f"\n=== 导入动画FBX模型: {filepath} ===")
|
||||
filepath = util.normalize_model_path(filepath)
|
||||
|
||||
# 使用动画管理器加载FBX
|
||||
actor = self.animation_manager.load_fbx_with_animations(filepath, scale)
|
||||
|
||||
if actor:
|
||||
# 设置模型名称
|
||||
model_name = os.path.basename(filepath)
|
||||
actor.setName(model_name)
|
||||
|
||||
# 调整模型位置到地面
|
||||
self._adjustModelToGround(actor)
|
||||
|
||||
# 设置碰撞检测
|
||||
self.setupCollision(actor)
|
||||
|
||||
# 添加文件标签
|
||||
actor.setTag("file", model_name)
|
||||
actor.setTag("is_animated_model", "1")
|
||||
|
||||
# 添加到模型列表
|
||||
self.models.append(actor)
|
||||
|
||||
# 自动播放第一个动画
|
||||
if auto_play:
|
||||
available_anims = self.animation_manager.get_available_animations(actor)
|
||||
if available_anims:
|
||||
first_anim = available_anims[0]
|
||||
self.animation_manager.play_animation(actor, first_anim, loop=True)
|
||||
print(f"🎬 自动播放动画: {first_anim}")
|
||||
|
||||
# 更新场景树
|
||||
self.updateSceneTree()
|
||||
|
||||
print(f"=== 动画FBX模型导入成功: {model_name} ===\n")
|
||||
return actor
|
||||
else:
|
||||
print("❌ 动画FBX模型导入失败")
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
print(f"导入动画FBX模型失败: {str(e)}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return None
|
||||
# def importAnimatedFBX(self, filepath, scale=0.01, auto_play=True):
|
||||
# """导入带动画的FBX模型(使用assimp)
|
||||
#
|
||||
# Args:
|
||||
# filepath: FBX文件路径
|
||||
# scale: 缩放比例(默认0.01,从厘米转换到米)
|
||||
# auto_play: 是否自动播放第一个动画
|
||||
#
|
||||
# Returns:
|
||||
# Actor对象,如果加载失败返回None
|
||||
# """
|
||||
# try:
|
||||
# print(f"\n=== 导入动画FBX模型: {filepath} ===")
|
||||
# filepath = util.normalize_model_path(filepath)
|
||||
#
|
||||
# # 使用动画管理器加载FBX
|
||||
# actor = self.animation_manager.load_fbx_with_animations(filepath, scale)
|
||||
#
|
||||
# if actor:
|
||||
# # 设置模型名称
|
||||
# model_name = os.path.basename(filepath)
|
||||
# actor.setName(model_name)
|
||||
#
|
||||
# # 调整模型位置到地面
|
||||
# self._adjustModelToGround(actor)
|
||||
#
|
||||
# # 设置碰撞检测
|
||||
# self.setupCollision(actor)
|
||||
#
|
||||
# # 添加文件标签
|
||||
# actor.setTag("file", model_name)
|
||||
# actor.setTag("is_animated_model", "1")
|
||||
#
|
||||
# # 添加到模型列表
|
||||
# self.models.append(actor)
|
||||
#
|
||||
# # 自动播放第一个动画
|
||||
# if auto_play:
|
||||
# available_anims = self.animation_manager.get_available_animations(actor)
|
||||
# if available_anims:
|
||||
# first_anim = available_anims[0]
|
||||
# self.animation_manager.play_animation(actor, first_anim, loop=True)
|
||||
# print(f"🎬 自动播放动画: {first_anim}")
|
||||
#
|
||||
# # 更新场景树
|
||||
# self.updateSceneTree()
|
||||
#
|
||||
# print(f"=== 动画FBX模型导入成功: {model_name} ===\n")
|
||||
# return actor
|
||||
# else:
|
||||
# print("❌ 动画FBX模型导入失败")
|
||||
# return None
|
||||
#
|
||||
# except Exception as e:
|
||||
# print(f"导入动画FBX模型失败: {str(e)}")
|
||||
# import traceback
|
||||
# traceback.print_exc()
|
||||
# return None
|
||||
|
||||
def _applyMaterialsToModel(self, model):
|
||||
"""递归应用材质到模型的所有GeomNode"""
|
||||
@ -1069,7 +1069,7 @@ class SceneManager:
|
||||
if self._convertWithAssimp(filepath, glb_path, progress):
|
||||
return glb_path
|
||||
|
||||
print(f"[GLB转换] 所有转换方法都失败")
|
||||
#print(f"[GLB转换] 所有转换方法都失败,既然没有可以转换格式的工具和环境那么就用原始文件,不一定非要转换")
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
|
||||
@ -5,69 +5,71 @@ from panda3d.core import GeomNode, ModelRoot
|
||||
|
||||
class InterfaceManager:
|
||||
"""界面管理器 - 处理树形控件和UI交互"""
|
||||
|
||||
|
||||
def __init__(self, world):
|
||||
"""初始化界面管理器"""
|
||||
self.world = world
|
||||
self.treeWidget = None
|
||||
|
||||
self._expanded_paths = set()
|
||||
|
||||
def setTreeWidget(self, treeWidget):
|
||||
"""设置树形控件引用并更新场景树"""
|
||||
self.treeWidget = treeWidget
|
||||
|
||||
|
||||
# 添加右键菜单
|
||||
self.treeWidget.setContextMenuPolicy(Qt.CustomContextMenu)
|
||||
self.treeWidget.customContextMenuRequested.connect(self.showTreeContextMenu)
|
||||
|
||||
|
||||
# 更新场景树
|
||||
self.world.scene_manager.updateSceneTree()
|
||||
|
||||
|
||||
def onTreeItemClicked(self, item, column):
|
||||
"""处理树形控件项目点击事件"""
|
||||
if not item:
|
||||
return
|
||||
|
||||
|
||||
# 获取节点对象
|
||||
nodePath = item.data(0, Qt.UserRole)
|
||||
if nodePath:
|
||||
# 更新选择状态
|
||||
self.world.selected_np = nodePath
|
||||
self.world.selection.updateSelection(nodePath)
|
||||
|
||||
|
||||
# 更新属性面板
|
||||
self.world.property_panel.updatePropertyPanel(item)
|
||||
|
||||
|
||||
print(f"树形控件点击: {item.text(0)}")
|
||||
else:
|
||||
# 如果没有节点对象,清除选择
|
||||
self.world.selection.updateSelection(None)
|
||||
#self.world.property_panel.clearPropertyPanel()
|
||||
|
||||
|
||||
def showTreeContextMenu(self, position):
|
||||
"""显示树形控件的右键菜单"""
|
||||
item = self.treeWidget.itemAt(position)
|
||||
if not item:
|
||||
return
|
||||
|
||||
|
||||
# 获取节点对象
|
||||
nodePath = item.data(0, Qt.UserRole)
|
||||
if not nodePath:
|
||||
return
|
||||
|
||||
|
||||
# 创建菜单
|
||||
menu = QMenu()
|
||||
|
||||
|
||||
# 检查是否是GUI元素
|
||||
if hasattr(nodePath, 'getTag') and nodePath.getTag("gui_type"):
|
||||
# GUI元素菜单
|
||||
editAction = menu.addAction("编辑")
|
||||
editAction.triggered.connect(lambda: self.world.gui_manager.editGUIElementDialog(nodePath))
|
||||
|
||||
|
||||
deleteAction = menu.addAction("删除GUI元素")
|
||||
deleteAction.triggered.connect(lambda: self.world.gui_manager.deleteGUIElement(nodePath))
|
||||
|
||||
|
||||
duplicateAction = menu.addAction("复制")
|
||||
duplicateAction.triggered.connect(lambda: self.world.gui_manager.duplicateGUIElement(nodePath))
|
||||
|
||||
|
||||
else:
|
||||
# 为模型节点或其子节点添加删除选项
|
||||
parentItem = item.parent()
|
||||
@ -75,10 +77,10 @@ class InterfaceManager:
|
||||
if self.isModelOrChild(item):
|
||||
deleteAction = menu.addAction("删除")
|
||||
deleteAction.triggered.connect(lambda: self.deleteNode(nodePath, item))
|
||||
|
||||
|
||||
# 显示菜单
|
||||
menu.exec_(self.treeWidget.viewport().mapToGlobal(position))
|
||||
|
||||
|
||||
def isModelOrChild(self, item):
|
||||
"""检查是否是模型节点或其子节点"""
|
||||
while item and item.parent():
|
||||
@ -86,90 +88,104 @@ class InterfaceManager:
|
||||
return True
|
||||
item = item.parent()
|
||||
return False
|
||||
|
||||
|
||||
def deleteNode(self, nodePath, item):
|
||||
"""删除节点"""
|
||||
try:
|
||||
# 从场景中移除
|
||||
self.world.property_panel.removeActorForModel(nodePath)
|
||||
nodePath.removeNode()
|
||||
|
||||
|
||||
# 如果是模型根节点,从模型列表中移除
|
||||
if item.parent().text(0) == "模型":
|
||||
if nodePath in self.world.models:
|
||||
self.world.models.remove(nodePath)
|
||||
|
||||
|
||||
# 从树形控件中移除
|
||||
parentItem = item.parent()
|
||||
if parentItem:
|
||||
parentItem.removeChild(item)
|
||||
|
||||
|
||||
print(f"成功删除节点: {nodePath.getName()}")
|
||||
|
||||
|
||||
# 清空属性面板和选择框
|
||||
self.world.property_panel.clearPropertyPanel()
|
||||
self.world.selection.updateSelection(None)
|
||||
|
||||
|
||||
except Exception as e:
|
||||
print(f"删除节点失败: {str(e)}")
|
||||
|
||||
|
||||
def updateSceneTree(self):
|
||||
"""更新场景树显示 - 实际实现"""
|
||||
if not self.treeWidget:
|
||||
return
|
||||
|
||||
self._expanded_paths.clear()
|
||||
self._collect_expanded()
|
||||
|
||||
print("\n=== 更新场景树 ===")
|
||||
self.treeWidget.clear()
|
||||
|
||||
|
||||
# 创建场景根节点
|
||||
sceneRoot = QTreeWidgetItem(self.treeWidget, ['场景'])
|
||||
|
||||
|
||||
# 添加相机节点
|
||||
cameraItem = QTreeWidgetItem(sceneRoot, ['相机'])
|
||||
cameraItem.setData(0, Qt.UserRole, self.world.cam)
|
||||
print("添加相机节点")
|
||||
|
||||
|
||||
# 添加模型节点组
|
||||
modelsItem = QTreeWidgetItem(sceneRoot, ['模型'])
|
||||
print(f"模型列表中的模型数量: {len(self.world.models)}")
|
||||
|
||||
|
||||
# 添加GUI元素节点组
|
||||
guiItem = QTreeWidgetItem(sceneRoot, ['GUI元素'])
|
||||
print(f"GUI元素数量: {len(self.world.gui_elements)}")
|
||||
|
||||
lightItem = QTreeWidgetItem(sceneRoot,['灯光'])
|
||||
|
||||
|
||||
# 递归添加节点及其子节点
|
||||
def addNodeToTree(node, parentItem):
|
||||
print(f"\n处理节点: {node.getName()}")
|
||||
# 创建节点项
|
||||
BLACK_LIST={'','**','temp','collision'}
|
||||
|
||||
def should_skip(node):
|
||||
name = node.getName()
|
||||
return name in BLACK_LIST or name.startswith('__')
|
||||
|
||||
def addNodeToTree(node,parentItem,force=False):
|
||||
if not force and should_skip(node):
|
||||
return
|
||||
nodeItem = QTreeWidgetItem(parentItem, [node.getName()])
|
||||
nodeItem.setData(0, Qt.UserRole, node)
|
||||
print(f"添加节点: {node.getName()}")
|
||||
|
||||
# 递归处理所有子节点
|
||||
nodeItem.setData(0,Qt.UserRole,node)
|
||||
|
||||
for child in node.getChildren():
|
||||
# 检查是否是有效的模型节点
|
||||
if (isinstance(child.node(), GeomNode) or
|
||||
child.hasTag("file") or
|
||||
child.getName() == "RootNode" or
|
||||
isinstance(child.node(), ModelRoot)):
|
||||
print(f"处理子节点: {child.getName()}")
|
||||
addNodeToTree(child, nodeItem)
|
||||
else:
|
||||
print(f"跳过节点: {child.getName()}")
|
||||
|
||||
addNodeToTree(child,nodeItem,force=False)
|
||||
|
||||
# 递归添加节点及其子节点
|
||||
# def addNodeToTree(node, parentItem):
|
||||
# print(f"\n处理节点: {node.getName()}")
|
||||
# # 创建节点项
|
||||
# nodeItem = QTreeWidgetItem(parentItem, [node.getName()])
|
||||
# nodeItem.setData(0, Qt.UserRole, node)
|
||||
# print(f"添加节点: {node.getName()}")
|
||||
#
|
||||
# # 递归处理所有子节点
|
||||
# for child in node.getChildren():
|
||||
# # 检查是否是有效的模型节点
|
||||
# if (isinstance(child.node(), GeomNode) or
|
||||
# child.hasTag("file") or
|
||||
# child.getName() == "RootNode" or
|
||||
# isinstance(child.node(), ModelRoot)):
|
||||
# print(f"处理子节点: {child.getName()}")
|
||||
# addNodeToTree(child, nodeItem)
|
||||
# else:
|
||||
# print(f"跳过节点: {child.getName()}")
|
||||
|
||||
# 添加所有模型及其子节点
|
||||
for model in self.world.models:
|
||||
print(f"\n处理根模型: {model.getName()}")
|
||||
addNodeToTree(model, modelsItem)
|
||||
|
||||
addNodeToTree(model, modelsItem,force=True)
|
||||
|
||||
# 添加所有GUI元素
|
||||
for gui_element in self.world.gui_elements:
|
||||
gui_type = gui_element.getTag("gui_type") if hasattr(gui_element, 'getTag') else "unknown"
|
||||
gui_text = gui_element.getTag("gui_text") if hasattr(gui_element, 'getTag') else "GUI元素"
|
||||
|
||||
|
||||
display_name = f"{gui_type}: {gui_text}"
|
||||
guiElementItem = QTreeWidgetItem(guiItem, [display_name])
|
||||
guiElementItem.setData(0, Qt.UserRole, gui_element)
|
||||
@ -179,17 +195,18 @@ class InterfaceManager:
|
||||
addNodeToTree(light_element,lightItem)
|
||||
for light_element in self.world.Pointlight:
|
||||
addNodeToTree(light_element,lightItem)
|
||||
|
||||
|
||||
# 添加地板节点
|
||||
if hasattr(self.world, 'ground'):
|
||||
groundItem = QTreeWidgetItem(sceneRoot, ['地板'])
|
||||
groundItem.setData(0, Qt.UserRole, self.world.ground)
|
||||
print("添加地板节点")
|
||||
|
||||
|
||||
# 展开所有节点
|
||||
self.treeWidget.expandAll()
|
||||
#self.treeWidget.expandAll()
|
||||
self._restore_expanded()
|
||||
print("=== 场景树更新完成 ===\n")
|
||||
|
||||
|
||||
def findTreeItem(self, node, parentItem):
|
||||
"""在树形控件中查找指定的节点项"""
|
||||
for i in range(parentItem.childCount()):
|
||||
@ -202,4 +219,30 @@ class InterfaceManager:
|
||||
found = self.findTreeItem(node, item)
|
||||
if found:
|
||||
return found
|
||||
return None
|
||||
return None
|
||||
|
||||
def _collect_expanded(self,item=None,prefix=""):
|
||||
if item is None:
|
||||
root = self.treeWidget.invisibleRootItem()
|
||||
for i in range(root.childCount()):
|
||||
self._collect_expanded(root.child(i),prefix)
|
||||
return
|
||||
path = f"{prefix}/{item.text(0)}"
|
||||
if item.isExpanded():
|
||||
self._expanded_paths.add(path)
|
||||
|
||||
for i in range(item.childCount()):
|
||||
self._collect_expanded(item.child(i),path)
|
||||
|
||||
def _restore_expanded(self,item=None,prefix=""):
|
||||
if item is None:
|
||||
root = self.treeWidget.invisibleRootItem()
|
||||
for i in range(root.childCount()):
|
||||
self._restore_expanded(root.child(i),prefix)
|
||||
return
|
||||
path = f"{prefix}/{item.text(0)}"
|
||||
if path in self._expanded_paths:
|
||||
item.setExpanded(True)
|
||||
|
||||
for i in range(item.childCount()):
|
||||
self._restore_expanded(item.child(i),path)
|
||||
@ -9,6 +9,7 @@ from PyQt5.QtCore import Qt
|
||||
from deploy_libs.unicodedata import normalize
|
||||
from direct.actor.Actor import Actor
|
||||
from panda3d.core import Vec3, Vec4, transpose, TransparencyAttrib, PartGroup
|
||||
from scene import util
|
||||
|
||||
|
||||
class PropertyPanelManager:
|
||||
@ -92,6 +93,63 @@ class PropertyPanelManager:
|
||||
if propertyWidget:
|
||||
propertyWidget.update()
|
||||
|
||||
def refreshModelValues(self, nodePath):
|
||||
"""实时刷新模型所有属性数值(相对/世界位置、旋转、缩放)"""
|
||||
if not nodePath or self._propertyLayout is None:
|
||||
return
|
||||
|
||||
parent = nodePath.getParent()
|
||||
render = self.world.render # 世界根节点
|
||||
|
||||
# ---------------- 相对位置 ----------------
|
||||
relPos = nodePath.getPos(parent) if parent else nodePath.getPos()
|
||||
if hasattr(self, '_xSpin') and self._xSpin:
|
||||
self._xSpin.blockSignals(True)
|
||||
self._xSpin.setValue(relPos.x)
|
||||
self._xSpin.blockSignals(False)
|
||||
if hasattr(self, '_ySpin') and self._ySpin:
|
||||
self._ySpin.blockSignals(True)
|
||||
self._ySpin.setValue(relPos.y)
|
||||
self._ySpin.blockSignals(False)
|
||||
if hasattr(self, '_zSpin') and self._zSpin:
|
||||
self._zSpin.blockSignals(True)
|
||||
self._zSpin.setValue(relPos.z)
|
||||
self._zSpin.blockSignals(False)
|
||||
|
||||
# ---------------- 世界位置 ----------------
|
||||
worldPos = nodePath.getPos(render)
|
||||
for axis, attr in zip(('x', 'y', 'z'), ('_worldXSpin', '_worldYSpin', '_worldZSpin')):
|
||||
spin = getattr(self, attr, None)
|
||||
if spin:
|
||||
spin.blockSignals(True)
|
||||
spin.setValue(getattr(worldPos, axis))
|
||||
spin.blockSignals(False)
|
||||
|
||||
# ---------------- 旋转 ----------------
|
||||
hpr = nodePath.getHpr()
|
||||
for idx, (attr, val) in enumerate(zip(('_hSpin', '_pSpin', '_rSpin'), hpr)):
|
||||
spin = getattr(self, attr, None)
|
||||
if spin:
|
||||
spin.blockSignals(True)
|
||||
spin.setValue(val)
|
||||
spin.blockSignals(False)
|
||||
|
||||
# ---------------- 缩放 ----------------
|
||||
scale = nodePath.getScale()
|
||||
for axis, attr in zip(('x', 'y', 'z'), ('_xScaleSpin', '_yScaleSpin', '_zScaleSpin')):
|
||||
spin = getattr(self, attr, None)
|
||||
if spin:
|
||||
spin.blockSignals(True)
|
||||
spin.setValue(getattr(scale, axis))
|
||||
spin.blockSignals(False)
|
||||
def _refreshWorldPos(self,model):
|
||||
if not hasattr(self,'worldXSpin'):
|
||||
return
|
||||
world = model.getPos(self.world.render)
|
||||
self._worldXSpin.setValue(world.x)
|
||||
self._worldYSpin.setValue(world.y)
|
||||
self._worldZSpin.setValue(world.z)
|
||||
|
||||
def _updateModelPropertyPanel(self, model):
|
||||
"""更新模型属性面板"""
|
||||
# 获取父节点
|
||||
@ -100,84 +158,103 @@ class PropertyPanelManager:
|
||||
# 位置属性(相对于父节点)
|
||||
relativePos = model.getPos(parent) if parent else model.getPos()
|
||||
|
||||
xPos = QDoubleSpinBox()
|
||||
xPos.setRange(-1000, 1000)
|
||||
xPos.setValue(relativePos.getX())
|
||||
xPos.valueChanged.connect(lambda v: model.setX(parent, v) if parent else model.setX(v))
|
||||
self._propertyLayout.addRow("相对位置 X:", xPos)
|
||||
self._xSpin = QDoubleSpinBox()
|
||||
self._xSpin.setRange(-1000, 1000)
|
||||
self._xSpin.setValue(relativePos.getX())
|
||||
#self._xSpin.valueChanged.connect(lambda v: (model.setX(parent, v) if parent else model.setX(v),self._refreshWorldPos(model))[0])
|
||||
self._xSpin.valueChanged.connect(
|
||||
lambda v: (model.setX(parent, v) if parent else model.setX(v),
|
||||
self.refreshModelValues(model))[0])
|
||||
self._propertyLayout.addRow("相对位置 X:", self._xSpin)
|
||||
|
||||
yPos = QDoubleSpinBox()
|
||||
yPos.setRange(-1000, 1000)
|
||||
yPos.setValue(relativePos.getY())
|
||||
yPos.valueChanged.connect(lambda v: model.setY(parent, v) if parent else model.setY(v))
|
||||
self._propertyLayout.addRow("相对位置 Y:", yPos)
|
||||
self._ySpin = QDoubleSpinBox()
|
||||
self._ySpin.setRange(-1000, 1000)
|
||||
self._ySpin.setValue(relativePos.getY())
|
||||
#self._ySpin.valueChanged.connect(lambda v: model.setY(parent, v) if parent else model.setY(v))
|
||||
self._ySpin.valueChanged.connect(
|
||||
lambda v: (model.setY(parent, v) if parent else model.setY(v),
|
||||
self.refreshModelValues(model))[0])
|
||||
self._propertyLayout.addRow("相对位置 Y:", self._ySpin)
|
||||
|
||||
zPos = QDoubleSpinBox()
|
||||
zPos.setRange(-1000, 1000)
|
||||
zPos.setValue(relativePos.getZ())
|
||||
zPos.valueChanged.connect(lambda v: model.setZ(parent, v) if parent else model.setZ(v))
|
||||
self._propertyLayout.addRow("相对位置 Z:", zPos)
|
||||
self._zSpin = QDoubleSpinBox()
|
||||
self._zSpin.setRange(-1000, 1000)
|
||||
self._zSpin.setValue(relativePos.getZ())
|
||||
#self._zSpin.valueChanged.connect(lambda v: model.setZ(parent, v) if parent else model.setZ(v))
|
||||
self._zSpin.valueChanged.connect(
|
||||
lambda v:(model.setZ(parent,v) if parent else model.setZ(v),
|
||||
self.refreshModelValues(model))[0])
|
||||
self._propertyLayout.addRow("相对位置 Z:", self._zSpin)
|
||||
|
||||
# 世界位置(只读)
|
||||
worldPos = model.getPos(self.world.render)
|
||||
worldXPos = QDoubleSpinBox()
|
||||
worldXPos.setRange(-1000, 1000)
|
||||
worldXPos.setValue(worldPos.getX())
|
||||
worldXPos.setReadOnly(True)
|
||||
self._propertyLayout.addRow("世界位置 X:", worldXPos)
|
||||
|
||||
worldYPos = QDoubleSpinBox()
|
||||
worldYPos.setRange(-1000, 1000)
|
||||
worldYPos.setValue(worldPos.getY())
|
||||
worldYPos.setReadOnly(True)
|
||||
self._propertyLayout.addRow("世界位置 Y:", worldYPos)
|
||||
self._worldXSpin = QDoubleSpinBox()
|
||||
self._worldXSpin.setRange(-1000, 1000)
|
||||
self._worldXSpin.setDecimals(2)
|
||||
self._worldXSpin.setValue(worldPos.x)
|
||||
self._worldXSpin.setReadOnly(True)
|
||||
self._propertyLayout.addRow("世界位置 X:", self._worldXSpin)
|
||||
|
||||
worldZPos = QDoubleSpinBox()
|
||||
worldZPos.setRange(-1000, 1000)
|
||||
worldZPos.setValue(worldPos.getZ())
|
||||
worldZPos.setReadOnly(True)
|
||||
self._propertyLayout.addRow("世界位置 Z:", worldZPos)
|
||||
self._worldYSpin = QDoubleSpinBox()
|
||||
self._worldYSpin.setRange(-1000, 1000)
|
||||
self._worldYSpin.setDecimals(2)
|
||||
self._worldYSpin.setValue(worldPos.y)
|
||||
self._worldYSpin.setReadOnly(True)
|
||||
self._propertyLayout.addRow("世界位置 Y:", self._worldYSpin)
|
||||
|
||||
self._worldZSpin = QDoubleSpinBox()
|
||||
self._worldZSpin.setRange(-1000, 1000)
|
||||
self._worldZSpin.setDecimals(2)
|
||||
self._worldZSpin.setValue(worldPos.z)
|
||||
self._worldZSpin.setReadOnly(True)
|
||||
self._propertyLayout.addRow("世界位置 Z:", self._worldZSpin)
|
||||
|
||||
# 旋转属性
|
||||
hRot = QDoubleSpinBox()
|
||||
hRot.setRange(-180, 180)
|
||||
hRot.setValue(model.getH())
|
||||
hRot.valueChanged.connect(lambda v: model.setH(v))
|
||||
self._propertyLayout.addRow("旋转 H:", hRot)
|
||||
self._hSpin = QDoubleSpinBox()
|
||||
self._hSpin.setRange(-360, 360)
|
||||
self._hSpin.setDecimals(2)
|
||||
self._hSpin.setValue(model.getH())
|
||||
self._hSpin.valueChanged.connect(lambda v: model.setH(v))
|
||||
self._propertyLayout.addRow("旋转 H:", self._hSpin)
|
||||
|
||||
pRot = QDoubleSpinBox()
|
||||
pRot.setRange(-180, 180)
|
||||
pRot.setValue(model.getP())
|
||||
pRot.valueChanged.connect(lambda v: model.setP(v))
|
||||
self._propertyLayout.addRow("旋转 P:", pRot)
|
||||
self._pSpin = QDoubleSpinBox()
|
||||
self._pSpin.setRange(-360, 360)
|
||||
self._pSpin.setDecimals(2)
|
||||
self._pSpin.setValue(model.getP())
|
||||
self._pSpin.valueChanged.connect(lambda v: model.setP(v))
|
||||
self._propertyLayout.addRow("旋转 P:", self._pSpin)
|
||||
|
||||
rRot = QDoubleSpinBox()
|
||||
rRot.setRange(-180, 180)
|
||||
rRot.setValue(model.getR())
|
||||
rRot.valueChanged.connect(lambda v: model.setR(v))
|
||||
self._propertyLayout.addRow("旋转 R:", rRot)
|
||||
self._rSpin = QDoubleSpinBox()
|
||||
self._rSpin.setRange(-360, 360)
|
||||
self._rSpin.setDecimals(2)
|
||||
self._rSpin.setValue(model.getR())
|
||||
self._rSpin.valueChanged.connect(lambda v: model.setR(v))
|
||||
self._propertyLayout.addRow("旋转 R:", self._rSpin)
|
||||
|
||||
# 缩放属性
|
||||
xScale = QDoubleSpinBox()
|
||||
xScale.setRange(0.01, 100)
|
||||
xScale.setSingleStep(0.1)
|
||||
xScale.setValue(model.getScale().getX())
|
||||
xScale.valueChanged.connect(lambda v: model.setScale(v, model.getScale().getY(), model.getScale().getZ()))
|
||||
self._propertyLayout.addRow("缩放 X:", xScale)
|
||||
self._xScaleSpin = QDoubleSpinBox()
|
||||
self._xScaleSpin.setRange(0.001, 1000)
|
||||
self._xScaleSpin.setSingleStep(0.1)
|
||||
self._xScaleSpin.setDecimals(3)
|
||||
self._xScaleSpin.setValue(model.getScale().getX())
|
||||
self._xScaleSpin.valueChanged.connect(lambda v: model.setScale(v, model.getScale().getY(), model.getScale().getZ()))
|
||||
self._propertyLayout.addRow("缩放 X:", self._xScaleSpin)
|
||||
|
||||
yScale = QDoubleSpinBox()
|
||||
yScale.setRange(0.01, 100)
|
||||
yScale.setSingleStep(0.1)
|
||||
yScale.setValue(model.getScale().getY())
|
||||
yScale.valueChanged.connect(lambda v: model.setScale(model.getScale().getX(), v, model.getScale().getZ()))
|
||||
self._propertyLayout.addRow("缩放 Y:", yScale)
|
||||
self.yScaleSpin = QDoubleSpinBox()
|
||||
self.yScaleSpin.setRange(0.001, 1000)
|
||||
self.yScaleSpin.setSingleStep(0.1)
|
||||
self.yScaleSpin.setDecimals(3)
|
||||
self.yScaleSpin.setValue(model.getScale().getY())
|
||||
self.yScaleSpin.valueChanged.connect(lambda v: model.setScale(model.getScale().getX(), v, model.getScale().getZ()))
|
||||
self._propertyLayout.addRow("缩放 Y:", self.yScaleSpin)
|
||||
|
||||
zScale = QDoubleSpinBox()
|
||||
zScale.setRange(0.01, 100)
|
||||
zScale.setSingleStep(0.1)
|
||||
zScale.setValue(model.getScale().getZ())
|
||||
zScale.valueChanged.connect(lambda v: model.setScale(model.getScale().getX(), model.getScale().getY(), v))
|
||||
self._propertyLayout.addRow("缩放 Z:", zScale)
|
||||
self.zScaleSpin = QDoubleSpinBox()
|
||||
self.zScaleSpin.setRange(0.001, 1000)
|
||||
self.zScaleSpin.setSingleStep(0.1)
|
||||
self.zScaleSpin.setDecimals(3)
|
||||
self.zScaleSpin.setValue(model.getScale().getZ())
|
||||
self.zScaleSpin.valueChanged.connect(lambda v: model.setScale(model.getScale().getX(), model.getScale().getY(), v))
|
||||
self._propertyLayout.addRow("缩放 Z:", self.zScaleSpin)
|
||||
|
||||
self._addAnimationPanel(model)
|
||||
self._addSunAzimuthPanel()
|
||||
@ -434,36 +511,6 @@ class PropertyPanelManager:
|
||||
zScaleSpinBox.valueChanged.connect(lambda v: self._updateLightScale(model, 'z', v))
|
||||
self._propertyLayout.addRow("缩放 Z:", zScaleSpinBox)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# 获取父节点
|
||||
|
||||
#parent = model.getParent()
|
||||
|
||||
# 位置属性(相对于父节点)
|
||||
#relativePos = model.getPos(parent) if parent else model.getPos()
|
||||
|
||||
# xPos = QDoubleSpinBox()
|
||||
# xPos.setRange(-1000, 1000)
|
||||
# xPos.setValue(relativePos.getX())
|
||||
# xPos.valueChanged.connect(lambda v: model.setX(parent, v) if parent else model.setX(v))
|
||||
# self._propertyLayout.addRow("相对位置 X:", xPos)
|
||||
#print(f"{model} x :{model.getPos()}")
|
||||
|
||||
# yPos = QDoubleSpinBox()
|
||||
# yPos.setRange(-1000, 1000)
|
||||
# yPos.setValue(relativePos.getY())
|
||||
# yPos.valueChanged.connect(lambda v: model.setY(parent, v) if parent else model.setY(v))
|
||||
# self._propertyLayout.addRow("相对位置 Y:", yPos)
|
||||
#
|
||||
# zPos = QDoubleSpinBox()
|
||||
# zPos.setRange(-1000, 1000)
|
||||
# zPos.setValue(relativePos.getZ())
|
||||
# zPos.valueChanged.connect(lambda v: model.setZ(parent, v) if parent else model.setZ(v))
|
||||
# self._propertyLayout.addRow("相对位置 Z:", zPos)
|
||||
|
||||
# 世界位置(只读)
|
||||
worldPos = model.getPos(self.world.render)
|
||||
worldXPos = QDoubleSpinBox()
|
||||
@ -484,49 +531,6 @@ class PropertyPanelManager:
|
||||
worldZPos.setReadOnly(True)
|
||||
self._propertyLayout.addRow("世界位置 Z:", worldZPos)
|
||||
|
||||
|
||||
|
||||
# 旋转属性
|
||||
# hRot = QDoubleSpinBox()
|
||||
# hRot.setRange(-180, 180)
|
||||
# hRot.setValue(model.getH())
|
||||
# hRot.valueChanged.connect(lambda v: model.setH(v))
|
||||
# self._propertyLayout.addRow("旋转 H:", hRot)
|
||||
#
|
||||
# pRot = QDoubleSpinBox()
|
||||
# pRot.setRange(-180, 180)
|
||||
# pRot.setValue(model.getP())
|
||||
# pRot.valueChanged.connect(lambda v: model.setP(v))
|
||||
# self._propertyLayout.addRow("旋转 P:", pRot)
|
||||
#
|
||||
# rRot = QDoubleSpinBox()
|
||||
# rRot.setRange(-180, 180)
|
||||
# rRot.setValue(model.getR())
|
||||
# rRot.valueChanged.connect(lambda v: model.setR(v))
|
||||
# self._propertyLayout.addRow("旋转 R:", rRot)
|
||||
|
||||
# 缩放属性
|
||||
# xScale = QDoubleSpinBox()
|
||||
# xScale.setRange(0.01, 100)
|
||||
# xScale.setSingleStep(0.1)
|
||||
# xScale.setValue(model.getScale().getX())
|
||||
# xScale.valueChanged.connect(lambda v: model.setScale(v, model.getScale().getY(), model.getScale().getZ()))
|
||||
# self._propertyLayout.addRow("缩放 X:", xScale)
|
||||
#
|
||||
# yScale = QDoubleSpinBox()
|
||||
# yScale.setRange(0.01, 100)
|
||||
# yScale.setSingleStep(0.1)
|
||||
# yScale.setValue(model.getScale().getY())
|
||||
# yScale.valueChanged.connect(lambda v: model.setScale(model.getScale().getX(), v, model.getScale().getZ()))
|
||||
# self._propertyLayout.addRow("缩放 Y:", yScale)
|
||||
#
|
||||
# zScale = QDoubleSpinBox()
|
||||
# zScale.setRange(0.01, 100)
|
||||
# zScale.setSingleStep(0.1)
|
||||
# zScale.setValue(model.getScale().getZ())
|
||||
# zScale.valueChanged.connect(lambda v: model.setScale(model.getScale().getX(), model.getScale().getY(), v))
|
||||
# self._propertyLayout.addRow("缩放 Z:", zScale)
|
||||
|
||||
def _updateLightPosition(self,light_object,node_path,axis,value):
|
||||
current_pos = light_object.pos
|
||||
|
||||
@ -813,6 +817,7 @@ class PropertyPanelManager:
|
||||
#漫反射贴图
|
||||
diffuse_button = QPushButton("选择漫反射贴图")
|
||||
diffuse_button.clicked.connect(lambda checked,title=unique_name:self._selectDiffuseTexture(title))
|
||||
#diffuse_button.clicked.connect(lambda: self._selectDiffuseTexture())
|
||||
self._propertyLayout.addRow("漫反射贴图:",diffuse_button)
|
||||
|
||||
#法线贴图
|
||||
@ -1147,8 +1152,10 @@ class PropertyPanelManager:
|
||||
if file_dialog.exec_():
|
||||
filename = file_dialog.selectedFiles()[0]
|
||||
if filename:
|
||||
self._applyDiffuseTexture(material_title,filename)
|
||||
print(f"已选择漫反射贴图:{filename}")
|
||||
# 使用跨平台路径标准化
|
||||
normalized_path = util.normalize_model_path(filename)
|
||||
self._applyDiffuseTexture(material_title,normalized_path)
|
||||
print(f"已选择漫反射贴图:{filename} -> 标准化路径:{normalized_path}")
|
||||
|
||||
def _selectNormalTexture(self,material):
|
||||
"""选择法线贴图"""
|
||||
@ -1159,8 +1166,10 @@ class PropertyPanelManager:
|
||||
if file_dialog.exec_():
|
||||
filename = file_dialog.selectedFiles()[0]
|
||||
if filename:
|
||||
self._applyNormalTexture(material,filename)
|
||||
print(f"已选择法线贴图:{filename}")
|
||||
# 使用跨平台路径标准化
|
||||
normalized_path = util.normalize_model_path(filename)
|
||||
self._applyNormalTexture(material,normalized_path)
|
||||
print(f"已选择法线贴图:{filename} -> 标准化路径:{normalized_path}")
|
||||
|
||||
def _selectRoughnessTexture(self,material):
|
||||
"""选择粗糙度贴图"""
|
||||
@ -1171,8 +1180,10 @@ class PropertyPanelManager:
|
||||
if file_dialog.exec_():
|
||||
filename = file_dialog.selectedFiles()[0]
|
||||
if filename:
|
||||
self._applyRoughnessTexture_FINAL(material,filename)
|
||||
print(f"已选择粗糙度贴图: {filename}")
|
||||
# 使用跨平台路径标准化
|
||||
normalized_path = util.normalize_model_path(filename)
|
||||
self._applyRoughnessTexture_FINAL(material,normalized_path)
|
||||
print(f"已选择粗糙度贴图: {filename} -> 标准化路径:{normalized_path}")
|
||||
|
||||
def _selectMetallicTexture(self,material):
|
||||
"""选择金属性贴图"""
|
||||
@ -1183,8 +1194,10 @@ class PropertyPanelManager:
|
||||
if file_dialog.exec_():
|
||||
filename = file_dialog.selectedFiles()[0]
|
||||
if filename:
|
||||
self._applyMetallicTexture_NEW(material,filename)
|
||||
print(f"已选择金属性贴图: {filename}")
|
||||
# 使用跨平台路径标准化
|
||||
normalized_path = util.normalize_model_path(filename)
|
||||
self._applyMetallicTexture_NEW(material,normalized_path)
|
||||
print(f"已选择金属性贴图: {filename} -> 标准化路径:{normalized_path}")
|
||||
|
||||
#IOR贴图
|
||||
def _selectIORTexture(self,material):
|
||||
@ -1196,8 +1209,10 @@ class PropertyPanelManager:
|
||||
if file_dialong.exec_():
|
||||
filename = file_dialong.selectedFiles()[0]
|
||||
if filename:
|
||||
self._applyIORTexture(material,filename)
|
||||
print(f"已选择IOR贴图:{filename}")
|
||||
# 使用跨平台路径标准化
|
||||
normalized_path = util.normalize_model_path(filename)
|
||||
self._applyIORTexture(material,normalized_path)
|
||||
print(f"已选择IOR贴图:{filename} -> 标准化路径:{normalized_path}")
|
||||
|
||||
def _selectParallaxTexture(self,material):
|
||||
"""选择视差贴图"""
|
||||
@ -1208,8 +1223,10 @@ class PropertyPanelManager:
|
||||
if file_dialog.exec_():
|
||||
filename = file_dialog.selectedFiles()[0]
|
||||
if filename:
|
||||
self._applyParallaxTexture(material,filename)
|
||||
print(f"已选择视差贴图:{filename}")
|
||||
# 使用跨平台路径标准化
|
||||
normalized_path = util.normalize_model_path(filename)
|
||||
self._applyParallaxTexture(material,normalized_path)
|
||||
print(f"已选择视差贴图:{filename} -> 标准化路径:{normalized_path}")
|
||||
|
||||
def _selectEmissionTexture(self,material):
|
||||
"""选择自发光贴图"""
|
||||
@ -1220,8 +1237,10 @@ class PropertyPanelManager:
|
||||
if file_dialog.exec_():
|
||||
filename = file_dialog.selectedFiles()[0]
|
||||
if filename:
|
||||
self._applyEmissionTexture(material,filename)
|
||||
print(f"已选择自发光贴图:{filename}")
|
||||
# 使用跨平台路径标准化
|
||||
normalized_path = util.normalize_model_path(filename)
|
||||
self._applyEmissionTexture(material,normalized_path)
|
||||
print(f"已选择自发光贴图:{filename} -> 标准化路径:{normalized_path}")
|
||||
|
||||
def _selectAOTexture(self,material):
|
||||
"""选择环境光遮蔽贴图"""
|
||||
@ -1232,8 +1251,10 @@ class PropertyPanelManager:
|
||||
if file_dialog.exec_():
|
||||
filename = file_dialog.selectedFiles()[0]
|
||||
if filename:
|
||||
self._applyAOTexture(material,filename)
|
||||
print(f"已选择AO贴图:{filename}")
|
||||
# 使用跨平台路径标准化
|
||||
normalized_path = util.normalize_model_path(filename)
|
||||
self._applyAOTexture(material,normalized_path)
|
||||
print(f"已选择AO贴图:{filename} -> 标准化路径:{normalized_path}")
|
||||
|
||||
def _selectAlphaTexture(self,material):
|
||||
"""选择透明度贴图"""
|
||||
@ -1244,8 +1265,10 @@ class PropertyPanelManager:
|
||||
if file_dialog.exec_():
|
||||
filename = file_dialog.selectedFiles()[0]
|
||||
if filename:
|
||||
self._applyAlphaTexture(material,filename)
|
||||
print(f"已选择透明度贴图:{filename}")
|
||||
# 使用跨平台路径标准化
|
||||
normalized_path = util.normalize_model_path(filename)
|
||||
self._applyAlphaTexture(material,normalized_path)
|
||||
print(f"已选择透明度贴图:{filename} -> 标准化路径:{normalized_path}")
|
||||
|
||||
def _selectDetailTexture(self,material):
|
||||
"""选择细节贴图"""
|
||||
@ -1256,8 +1279,10 @@ class PropertyPanelManager:
|
||||
if file_dialog.exec_():
|
||||
filename = file_dialog.selectedFiles()[0]
|
||||
if filename:
|
||||
self._applyDetailTexture(material,filename)
|
||||
print(f"已选择细节贴图:{filename}")
|
||||
# 使用跨平台路径标准化
|
||||
normalized_path = util.normalize_model_path(filename)
|
||||
self._applyDetailTexture(material,normalized_path)
|
||||
print(f"已选择细节贴图:{filename} -> 标准化路径:{normalized_path}")
|
||||
|
||||
def _selectGlossTexture(self,material):
|
||||
"""选择光泽贴图"""
|
||||
@ -1268,9 +1293,43 @@ class PropertyPanelManager:
|
||||
if file_dialog.exec_():
|
||||
filename = file_dialog.selectedFiles()[0]
|
||||
if filename:
|
||||
self._applyGlossTexture(material,filename)
|
||||
print(f"已选择光泽贴图:{filename}")
|
||||
# 使用跨平台路径标准化
|
||||
normalized_path = util.normalize_model_path(filename)
|
||||
self._applyGlossTexture(material,normalized_path)
|
||||
print(f"已选择光泽贴图:{filename} -> 标准化路径:{normalized_path}")
|
||||
|
||||
|
||||
|
||||
# def _applyDiffuseTexture(self, texture_path):
|
||||
# from panda3d.core import TextureStage
|
||||
# try:
|
||||
# from RenderPipelineFile.rpcore.loader import RPLoader
|
||||
# texture = RPLoader.load_texture(texture_path)
|
||||
# if not texture:
|
||||
# print("纹理加载失败")
|
||||
# return
|
||||
#
|
||||
# node = self.world.selected_np
|
||||
# if not node:
|
||||
# print("未选中节点")
|
||||
# return
|
||||
#
|
||||
# # 1. 直接给节点挂贴图
|
||||
# diffuse_stage = TextureStage("diffuse")
|
||||
# diffuse_stage.setSort(0)
|
||||
# node.setTexture(diffuse_stage, texture)
|
||||
#
|
||||
# # 2. 再给它刷一个 RenderPipeline 效果
|
||||
# effect_file = "effects/default.yaml"
|
||||
# self.world.render_pipeline.set_effect(
|
||||
# node,
|
||||
# effect_file,
|
||||
# {"diffuse_texture": texture},
|
||||
# 100
|
||||
# )
|
||||
# print("贴图已直接贴到节点:", node.getName())
|
||||
# except Exception as e:
|
||||
# print("贴图失败:", e)
|
||||
def _applyDiffuseTexture(self,material_title,texture_path):
|
||||
"""应用漫反射贴图"""
|
||||
try:
|
||||
@ -3959,7 +4018,9 @@ class PropertyPanelManager:
|
||||
self.speed_spinbox = QDoubleSpinBox()
|
||||
self.speed_spinbox.setRange(0.1,5.0)
|
||||
self.speed_spinbox.setSingleStep(0.1)
|
||||
self.speed_spinbox.setValue(1.0)
|
||||
saved = origin_model.getPythonTag("anim_speed")
|
||||
self.speed_spinbox.setValue(saved if saved is not None else 1.0)
|
||||
#self.speed_spinbox.setValue(1.0)
|
||||
self.speed_spinbox.valueChanged.connect(lambda v:self._setAnimationSpeed(origin_model,v))
|
||||
self._propertyLayout.addRow("播放速度:",self.speed_spinbox)
|
||||
|
||||
@ -4541,7 +4602,6 @@ except Exception as e:
|
||||
actor = self._getActor(origin_model)
|
||||
if not actor:
|
||||
return
|
||||
|
||||
# 获取原始动画名称
|
||||
current_index = self.animation_combo.currentIndex()
|
||||
|
||||
@ -4552,6 +4612,7 @@ except Exception as e:
|
||||
|
||||
if anim_name:
|
||||
actor.setPlayRate(speed, anim_name)
|
||||
origin_model.setPythonTag("anim_speed",speed)
|
||||
print(f"[动画] 速度设为: {speed} ({display_name})")
|
||||
|
||||
def _detectNonSkeletalAnimations(self, origin_model):
|
||||
|
||||
@ -407,11 +407,11 @@ class CustomTreeWidget(QTreeWidget):
|
||||
currentItem = self.currentItem()
|
||||
if currentItem and currentItem.parent():
|
||||
# 检查是否是模型节点或其子节点
|
||||
if self.world.isModelOrChild(currentItem):
|
||||
if self.world.interface_manager.isModelOrChild(currentItem):
|
||||
nodePath = currentItem.data(0, Qt.UserRole)
|
||||
if nodePath:
|
||||
print("正在删除节点...")
|
||||
self.world.deleteNode(nodePath, currentItem)
|
||||
self.world.interface_manager.deleteNode(nodePath, currentItem)
|
||||
print("删除完成")
|
||||
else:
|
||||
super().keyPressEvent(event)
|
||||
Loading…
Reference in New Issue
Block a user