Compare commits

..

No commits in common. "95e0681d7ccb4859255b6c2ddcd61f86374756ea" and "ee0fe2741dc8c0410f05d940ca8264fa2aa5c109" have entirely different histories.

16 changed files with 312 additions and 505 deletions

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@ -13,7 +13,6 @@ 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:
@ -50,14 +49,14 @@ class SelectionSystem:
# 高亮相关
self.gizmoHighlightAxis = None
self.gizmo_colors = {
"x": (1*10, 0, 0, 1), # 红色
"y": (0, 1*10, 0, 1), # 绿色
"z": (0, 0, 1*10, 1) # 蓝色
"x": (1, 0, 0, 1), # 红色
"y": (0, 1, 0, 1), # 绿色
"z": (0, 0, 1, 1) # 蓝色
}
self.gizmo_highlight_colors = {
"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) # 黄色高亮
"x": (1.5, 1.5, 0, 1), # 黄色高亮
"y": (1.5, 1.5, 0, 1), # 黄色高亮
"z": (1.5, 1.5, 0, 1) # 黄色高亮
}
print("✓ 选择和变换系统初始化完成")
@ -596,7 +595,7 @@ class SelectionSystem:
print(f"❌ 最后备用方案也失败: {e}")
def updateGizmoTask(self, task):
"""坐标轴更新任务 - 包含固定大小功能"""
"""坐标轴更新任务"""
try:
if not self.gizmo or not self.gizmoTarget:
return task.done
@ -633,58 +632,12 @@ class SelectionSystem:
# 顶级模型:使用世界坐标系朝向
self.gizmo.setHpr(0, 0, 0)
# 【新功能】:动态调整坐标轴大小,保持固定的屏幕大小
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:
@ -1294,38 +1247,8 @@ 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)
# 【改进修复】:智能缩放补偿,区分继承缩放和本体缩放
# 计算父节点链的累积缩放(不包括目标节点本身)
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}")
# 使用动态比例因子
scale_factor = pixel_to_world_ratio * 0.5 # 0.5是调整因子,可以根据需要调整
# 【关键修复】:在正确的坐标系中计算移动向量
# 计算移动距离(标量)
@ -1359,8 +1282,6 @@ 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
View File

@ -391,27 +391,7 @@ class MyWorld(CoreWorld):
# 模型导入和处理方法 - 代理到scene_manager
def importModel(self, 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)
return self.scene_manager.importModel(filepath)
def importModelAsync(self, filepath):
"""异步导入模型"""

View File

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

View File

@ -10,7 +10,6 @@ class InterfaceManager:
"""初始化界面管理器"""
self.world = world
self.treeWidget = None
self._expanded_paths = set()
def setTreeWidget(self, treeWidget):
"""设置树形控件引用并更新场景树"""
@ -32,7 +31,6 @@ class InterfaceManager:
nodePath = item.data(0, Qt.UserRole)
if nodePath:
# 更新选择状态
self.world.selected_np = nodePath
self.world.selection.updateSelection(nodePath)
# 更新属性面板
@ -119,8 +117,6 @@ class InterfaceManager:
"""更新场景树显示 - 实际实现"""
if not self.treeWidget:
return
self._expanded_paths.clear()
self._collect_expanded()
print("\n=== 更新场景树 ===")
self.treeWidget.clear()
@ -139,66 +135,59 @@ class InterfaceManager:
# 添加GUI元素节点组
guiItem = QTreeWidgetItem(sceneRoot, ['GUI元素'])
print(f"GUI元素数量: {len(self.world.gui_elements)}")
lightItem = QTreeWidgetItem(sceneRoot,['灯光'])
BLACK_LIST = {'','**','temp','collision'}
from panda3d.core import CollisionNode
def should_skip(node):
name = node.getName()
return name in BLACK_LIST or name.startswith('__') or isinstance(node.node(),CollisionNode) or isinstance(node.node(),ModelRoot) or name==""
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)
for child in node.getChildren():
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()}")
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:
addNodeToTree(model, modelsItem,force=True)
print(f"\n处理根模型: {model.getName()}")
addNodeToTree(model, modelsItem)
# 添加所有GUI元素
for gui in self.world.gui_elements:
gui_type = gui.getTag("gui_type") or "unknown"
gui_text = gui.getTag("gui_text") or "GUI元素"
item = QTreeWidgetItem(guiItem, [f"{gui_type}: {gui_text}"])
item.setData(0, Qt.UserRole, 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元素"
for light in self.world.Spotlight + self.world.Pointlight:
addNodeToTree(light, lightItem, force=True)
display_name = f"{gui_type}: {gui_text}"
guiElementItem = QTreeWidgetItem(guiItem, [display_name])
guiElementItem.setData(0, Qt.UserRole, gui_element)
print(f"添加GUI元素: {display_name}")
for light_element in self.world.Spotlight:
addNodeToTree(light_element,lightItem)
for light_element in self.world.Pointlight:
addNodeToTree(light_element,lightItem)
# 添加地板节点
if hasattr(self.world, 'ground') and self.world.ground:
if hasattr(self.world, 'ground'):
groundItem = QTreeWidgetItem(sceneRoot, ['地板'])
groundItem.setData(0, Qt.UserRole, self.world.ground)
print("添加地板节点")
# 展开所有节点
#self.treeWidget.expandAll()
self._restore_expanded()
self.treeWidget.expandAll()
print("=== 场景树更新完成 ===\n")
def findTreeItem(self, node, parentItem):
@ -214,29 +203,3 @@ class InterfaceManager:
if found:
return found
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)

View File

@ -9,7 +9,6 @@ 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:
@ -93,63 +92,6 @@ 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):
"""更新模型属性面板"""
# 获取父节点
@ -158,103 +100,84 @@ class PropertyPanelManager:
# 位置属性(相对于父节点)
relativePos = model.getPos(parent) if parent else model.getPos()
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)
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._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)
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._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)
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()
worldXPos.setRange(-1000, 1000)
worldXPos.setValue(worldPos.getX())
worldXPos.setReadOnly(True)
self._propertyLayout.addRow("世界位置 X:", worldXPos)
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)
worldYPos = QDoubleSpinBox()
worldYPos.setRange(-1000, 1000)
worldYPos.setValue(worldPos.getY())
worldYPos.setReadOnly(True)
self._propertyLayout.addRow("世界位置 Y:", worldYPos)
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)
worldZPos = QDoubleSpinBox()
worldZPos.setRange(-1000, 1000)
worldZPos.setValue(worldPos.getZ())
worldZPos.setReadOnly(True)
self._propertyLayout.addRow("世界位置 Z:", worldZPos)
# 旋转属性
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)
hRot = QDoubleSpinBox()
hRot.setRange(-180, 180)
hRot.setValue(model.getH())
hRot.valueChanged.connect(lambda v: model.setH(v))
self._propertyLayout.addRow("旋转 H:", hRot)
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)
pRot = QDoubleSpinBox()
pRot.setRange(-180, 180)
pRot.setValue(model.getP())
pRot.valueChanged.connect(lambda v: model.setP(v))
self._propertyLayout.addRow("旋转 P:", pRot)
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)
rRot = QDoubleSpinBox()
rRot.setRange(-180, 180)
rRot.setValue(model.getR())
rRot.valueChanged.connect(lambda v: model.setR(v))
self._propertyLayout.addRow("旋转 R:", rRot)
# 缩放属性
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)
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.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)
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.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)
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._addAnimationPanel(model)
self._addSunAzimuthPanel()
@ -511,6 +434,36 @@ 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()
@ -531,6 +484,49 @@ 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
@ -817,7 +813,6 @@ 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)
#法线贴图
@ -1152,10 +1147,8 @@ class PropertyPanelManager:
if file_dialog.exec_():
filename = file_dialog.selectedFiles()[0]
if filename:
# 使用跨平台路径标准化
normalized_path = util.normalize_model_path(filename)
self._applyDiffuseTexture(material_title,normalized_path)
print(f"已选择漫反射贴图:{filename} -> 标准化路径:{normalized_path}")
self._applyDiffuseTexture(material_title,filename)
print(f"已选择漫反射贴图:{filename}")
def _selectNormalTexture(self,material):
"""选择法线贴图"""
@ -1166,10 +1159,8 @@ class PropertyPanelManager:
if file_dialog.exec_():
filename = file_dialog.selectedFiles()[0]
if filename:
# 使用跨平台路径标准化
normalized_path = util.normalize_model_path(filename)
self._applyNormalTexture(material,normalized_path)
print(f"已选择法线贴图:{filename} -> 标准化路径:{normalized_path}")
self._applyNormalTexture(material,filename)
print(f"已选择法线贴图:{filename}")
def _selectRoughnessTexture(self,material):
"""选择粗糙度贴图"""
@ -1180,10 +1171,8 @@ class PropertyPanelManager:
if file_dialog.exec_():
filename = file_dialog.selectedFiles()[0]
if filename:
# 使用跨平台路径标准化
normalized_path = util.normalize_model_path(filename)
self._applyRoughnessTexture_FINAL(material,normalized_path)
print(f"已选择粗糙度贴图: {filename} -> 标准化路径:{normalized_path}")
self._applyRoughnessTexture_FINAL(material,filename)
print(f"已选择粗糙度贴图: {filename}")
def _selectMetallicTexture(self,material):
"""选择金属性贴图"""
@ -1194,10 +1183,8 @@ class PropertyPanelManager:
if file_dialog.exec_():
filename = file_dialog.selectedFiles()[0]
if filename:
# 使用跨平台路径标准化
normalized_path = util.normalize_model_path(filename)
self._applyMetallicTexture_NEW(material,normalized_path)
print(f"已选择金属性贴图: {filename} -> 标准化路径:{normalized_path}")
self._applyMetallicTexture_NEW(material,filename)
print(f"已选择金属性贴图: {filename}")
#IOR贴图
def _selectIORTexture(self,material):
@ -1209,10 +1196,8 @@ class PropertyPanelManager:
if file_dialong.exec_():
filename = file_dialong.selectedFiles()[0]
if filename:
# 使用跨平台路径标准化
normalized_path = util.normalize_model_path(filename)
self._applyIORTexture(material,normalized_path)
print(f"已选择IOR贴图:{filename} -> 标准化路径:{normalized_path}")
self._applyIORTexture(material,filename)
print(f"已选择IOR贴图:{filename}")
def _selectParallaxTexture(self,material):
"""选择视差贴图"""
@ -1223,10 +1208,8 @@ class PropertyPanelManager:
if file_dialog.exec_():
filename = file_dialog.selectedFiles()[0]
if filename:
# 使用跨平台路径标准化
normalized_path = util.normalize_model_path(filename)
self._applyParallaxTexture(material,normalized_path)
print(f"已选择视差贴图:{filename} -> 标准化路径:{normalized_path}")
self._applyParallaxTexture(material,filename)
print(f"已选择视差贴图:{filename}")
def _selectEmissionTexture(self,material):
"""选择自发光贴图"""
@ -1237,10 +1220,8 @@ class PropertyPanelManager:
if file_dialog.exec_():
filename = file_dialog.selectedFiles()[0]
if filename:
# 使用跨平台路径标准化
normalized_path = util.normalize_model_path(filename)
self._applyEmissionTexture(material,normalized_path)
print(f"已选择自发光贴图:{filename} -> 标准化路径:{normalized_path}")
self._applyEmissionTexture(material,filename)
print(f"已选择自发光贴图:{filename}")
def _selectAOTexture(self,material):
"""选择环境光遮蔽贴图"""
@ -1251,10 +1232,8 @@ class PropertyPanelManager:
if file_dialog.exec_():
filename = file_dialog.selectedFiles()[0]
if filename:
# 使用跨平台路径标准化
normalized_path = util.normalize_model_path(filename)
self._applyAOTexture(material,normalized_path)
print(f"已选择AO贴图:{filename} -> 标准化路径:{normalized_path}")
self._applyAOTexture(material,filename)
print(f"已选择AO贴图:{filename}")
def _selectAlphaTexture(self,material):
"""选择透明度贴图"""
@ -1265,10 +1244,8 @@ class PropertyPanelManager:
if file_dialog.exec_():
filename = file_dialog.selectedFiles()[0]
if filename:
# 使用跨平台路径标准化
normalized_path = util.normalize_model_path(filename)
self._applyAlphaTexture(material,normalized_path)
print(f"已选择透明度贴图:{filename} -> 标准化路径:{normalized_path}")
self._applyAlphaTexture(material,filename)
print(f"已选择透明度贴图:{filename}")
def _selectDetailTexture(self,material):
"""选择细节贴图"""
@ -1279,10 +1256,8 @@ class PropertyPanelManager:
if file_dialog.exec_():
filename = file_dialog.selectedFiles()[0]
if filename:
# 使用跨平台路径标准化
normalized_path = util.normalize_model_path(filename)
self._applyDetailTexture(material,normalized_path)
print(f"已选择细节贴图:{filename} -> 标准化路径:{normalized_path}")
self._applyDetailTexture(material,filename)
print(f"已选择细节贴图:{filename}")
def _selectGlossTexture(self,material):
"""选择光泽贴图"""
@ -1293,43 +1268,9 @@ class PropertyPanelManager:
if file_dialog.exec_():
filename = file_dialog.selectedFiles()[0]
if filename:
# 使用跨平台路径标准化
normalized_path = util.normalize_model_path(filename)
self._applyGlossTexture(material,normalized_path)
print(f"已选择光泽贴图:{filename} -> 标准化路径:{normalized_path}")
self._applyGlossTexture(material,filename)
print(f"已选择光泽贴图:{filename}")
# 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:
@ -3170,6 +3111,8 @@ class PropertyPanelManager:
print(f"未知的材质预设: {preset_name}")
return
preset = presets[preset_name]
if "shading_model" in preset:
@ -3182,6 +3125,8 @@ class PropertyPanelManager:
print(f"材质emission值: {material.emission}")
print(f"基础颜色alpha: {preset['base_color'].w}")
material.set_base_color(preset["base_color"])
material.set_roughness(preset["roughness"])
material.set_metallic(preset["metallic"])
@ -3197,7 +3142,7 @@ class PropertyPanelManager:
if preset["shading_model"]==3:
self._apply_transparent_effect()
material._applied_preset = preset_name
#material._applied_preset = preset_name
self._refreshMaterialUI()
print(f"已应用材质预设: {preset_name}")
@ -4014,9 +3959,7 @@ class PropertyPanelManager:
self.speed_spinbox = QDoubleSpinBox()
self.speed_spinbox.setRange(0.1,5.0)
self.speed_spinbox.setSingleStep(0.1)
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.setValue(1.0)
self.speed_spinbox.valueChanged.connect(lambda v:self._setAnimationSpeed(origin_model,v))
self._propertyLayout.addRow("播放速度:",self.speed_spinbox)
@ -4598,6 +4541,7 @@ except Exception as e:
actor = self._getActor(origin_model)
if not actor:
return
# 获取原始动画名称
current_index = self.animation_combo.currentIndex()
@ -4608,7 +4552,6 @@ 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):

View File

@ -407,11 +407,11 @@ class CustomTreeWidget(QTreeWidget):
currentItem = self.currentItem()
if currentItem and currentItem.parent():
# 检查是否是模型节点或其子节点
if self.world.interface_manager.isModelOrChild(currentItem):
if self.world.isModelOrChild(currentItem):
nodePath = currentItem.data(0, Qt.UserRole)
if nodePath:
print("正在删除节点...")
self.world.interface_manager.deleteNode(nodePath, currentItem)
self.world.deleteNode(nodePath, currentItem)
print("删除完成")
else:
super().keyPressEvent(event)