From 3261163ec8a3443402781098261fc67e5e4db82f Mon Sep 17 00:00:00 2001 From: Hector <2055590199@qq.com> Date: Thu, 14 Aug 2025 10:06:26 +0800 Subject: [PATCH] =?UTF-8?q?=E8=B7=A8=E5=B9=B3=E5=8F=B0=E8=B7=AF=E5=BE=84?= =?UTF-8?q?=E6=A0=87=E5=87=86=E5=8C=96=EF=BC=8C=E5=9D=90=E6=A0=87=E7=B3=BB?= =?UTF-8?q?=E9=97=AE=E9=A2=98=E4=BF=AE=E6=94=B9=EF=BC=8C=E7=BC=A9=E6=94=BE?= =?UTF-8?q?=E5=90=8E=E5=AD=90=E8=8A=82=E7=82=B9=E7=A7=BB=E5=8A=A8=E9=97=AE?= =?UTF-8?q?=E9=A2=98=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- RenderPipelineFile/config/daytime.yaml | 4 +- core/selection.py | 99 ++++++- main.py | 22 +- scene/scene_manager.py | 118 ++++---- ui/interface_manager.py | 159 +++++++---- ui/property_panel.py | 381 ++++++++++++++----------- ui/widgets.py | 4 +- 7 files changed, 495 insertions(+), 292 deletions(-) diff --git a/RenderPipelineFile/config/daytime.yaml b/RenderPipelineFile/config/daytime.yaml index 633469d4..140ea957 100644 --- a/RenderPipelineFile/config/daytime.yaml +++ b/RenderPipelineFile/config/daytime.yaml @@ -17,8 +17,8 @@ control_points: scattering: sun_intensity: [[[0.0000000000,0.0000000000],[0.0041666667,0.0000000000],[0.0083333333,0.0000000000],[0.0125000000,0.0000000000],[0.0166666667,0.0000000000],[0.0208333333,0.0000000000],[0.0250000000,0.0000000000],[0.0291666667,0.0000000000],[0.0333333333,0.0000000000],[0.0375000000,0.0000000000],[0.0416666667,0.0000000000],[0.0458333333,0.0000000000],[0.0500000000,0.0000000000],[0.0541666667,0.0000000000],[0.0583333333,0.0000000000],[0.0625000000,0.0000000000],[0.0666666667,0.0000000000],[0.0708333333,0.0000000000],[0.0750000000,0.0000000000],[0.0791666667,0.0000000000],[0.0833333333,0.0000000000],[0.0875000000,0.0000000000],[0.0916666667,0.0000000000],[0.0958333333,0.0000000000],[0.1000000000,0.0000000000],[0.1041666667,0.0000000000],[0.1083333333,0.0000000000],[0.1125000000,0.0000000000],[0.1166666667,0.0000000000],[0.1208333333,0.0000000000],[0.1250000000,0.0000000000],[0.1291666667,0.0000000000],[0.1333333333,0.0000000000],[0.1375000000,0.0000000000],[0.1416666667,0.0000000000],[0.1458333333,0.0000000000],[0.1500000000,0.0000000000],[0.1541666667,0.0000000000],[0.1583333333,0.0000028805],[0.1625000000,0.0003577724],[0.1666666667,0.0013331400],[0.1708333333,0.0029671803],[0.1750000000,0.0052963381],[0.1791666667,0.0083550556],[0.1833333333,0.0121755589],[0.1875000000,0.0167876159],[0.1916666667,0.0222183530],[0.1958333333,0.0284919947],[0.2000000000,0.0356297193],[0.2041666667,0.0436494349],[0.2083333333,0.0525656099],[0.2125000000,0.0623891610],[0.2166666667,0.0731272461],[0.2208333333,0.0847831708],[0.2250000000,0.0973563167],[0.2291666667,0.1108419698],[0.2333333333,0.1252313631],[0.2375000000,0.1405115250],[0.2416666667,0.1566653434],[0.2458333333,0.1736715009],[0.2500000000,0.1915046014],[0.2541666667,0.2101350464],[0.2583333333,0.2295292930],[0.2625000000,0.2496498145],[0.2666666667,0.2704552670],[0.2708333333,0.2919006662],[0.2750000000,0.3139375192],[0.2791666667,0.3365139497],[0.2833333333,0.3595750662],[0.2875000000,0.3830630359],[0.2916666667,0.4069173972],[0.2958333333,0.4310753462],[0.3000000000,0.4554720417],[0.3041666667,0.4800408236],[0.3083333333,0.5047136020],[0.3125000000,0.5294212108],[0.3166666667,0.5540936424],[0.3208333333,0.5786605298],[0.3250000000,0.6030514553],[0.3291666667,0.6271963182],[0.3333333333,0.6510256858],[0.3375000000,0.6744711982],[0.3416666667,0.6974659988],[0.3458333333,0.7199450163],[0.3500000000,0.7418453485],[0.3541666667,0.7631067095],[0.3583333333,0.7836717291],[0.3625000000,0.8034862953],[0.3666666667,0.8224999302],[0.3708333333,0.8406661079],[0.3750000000,0.8579425235],[0.3791666667,0.8742914270],[0.3833333333,0.8896799131],[0.3875000000,0.9040801386],[0.3916666667,0.9174695289],[0.3958333333,0.9298310650],[0.4000000000,0.9411533765],[0.4041666667,0.9514309312],[0.4083333333,0.9606641691],[0.4125000000,0.9688595571],[0.4166666667,0.9760296330],[0.4208333333,0.9821930708],[0.4250000000,0.9873746114],[0.4291666667,0.9916050060],[0.4333333333,0.9949209310],[0.4375000000,0.9973647924],[0.4416666667,0.9989845508],[0.4458333333,0.9998334497],[0.4500000000,0.9999696949],[0.4541666667,0.9994560801],[0.4583333333,0.9983595429],[0.4625000000,0.9967506613],[0.4666666667,0.9947030614],[0.4708333333,0.9922927758],[0.4750000000,0.9895975125],[0.4791666667,0.9866958610],[0.4833333333,0.9836664262],[0.4875000000,0.9805868867],[0.4916666667,0.9775330316],[0.4958333333,0.9745777179],[0.5000000000,0.9717898417],[0.5041666667,0.9692332877],[0.5083333333,0.9669658924],[0.5125000000,0.9650384806],[0.5089595376,0.9690650222],[0.5208333333,0.9623666659],[0.5250000000,0.9616814371],[0.5291666667,0.9614534423],[0.5333333333,0.9616877089],[0.5375000000,0.9623790807],[0.5416666667,0.9635123329],[0.5458333333,0.9650624244],[0.5500000000,0.9669949804],[0.5541666667,0.9692669864],[0.5583333333,0.9718275065],[0.5625000000,0.9746185969],[0.5666666667,0.9775762863],[0.5708333333,0.9806315864],[0.5750000000,0.9837115661],[0.5791666667,0.9867403433],[0.5833333333,0.9896401655],[0.5875000000,0.9923323562],[0.5916666667,0.9947382579],[0.5958333333,0.9967800977],[0.6000000000,0.9983817820],[0.6041666667,0.9994696263],[0.6083333333,0.9999730028],[0.6125000000,0.9998249266],[0.6166666667,0.9989625601],[0.6208333333,0.9973276624],[0.6250000000,0.9948669567],[0.6291666667,0.9915324664],[0.6333333333,0.9872817545],[0.6375000000,0.9820781426],[0.6416666667,0.9758908775],[0.6458333333,0.9686952146],[0.6500000000,0.9604725211],[0.6541666667,0.9512102537],[0.6583333333,0.9409019858],[0.6625000000,0.9295473441],[0.6666666667,0.9171518878],[0.6708333333,0.9037270619],[0.6750000000,0.8892899902],[0.6791666667,0.8738633008],[0.6833333333,0.8574749656],[0.6875000000,0.8401579787],[0.6916666667,0.8219502453],[0.6958333333,0.8028941798],[0.7000000000,0.7830364456],[0.7041666667,0.7624277344],[0.7083333333,0.7411222520],[0.7125000000,0.7191776044],[0.7166666667,0.6966542563],[0.7208333333,0.6736152714],[0.7250000000,0.6501259629],[0.7291666667,0.6262533880],[0.7333333333,0.6020661121],[0.7375000000,0.5776338043],[0.7416666667,0.5530267796],[0.7458333333,0.5283156992],[0.7500000000,0.5035711751],[0.7541666667,0.4788634341],[0.7583333333,0.4542618347],[0.7625000000,0.4298347613],[0.7666666667,0.4056490351],[0.7708333333,0.3817697830],[0.7750000000,0.3582600107],[0.7791666667,0.3351803495],[0.7833333333,0.3125888445],[0.7875000000,0.2905406366],[0.7916666667,0.2690876955],[0.7958333333,0.2482787388],[0.8000000000,0.2281588906],[0.8041666667,0.2087696425],[0.8083333333,0.1901486315],[0.8125000000,0.1723295359],[0.8166666667,0.1553419918],[0.8208333333,0.1392115328],[0.8250000000,0.1239595144],[0.8291666667,0.1096030703],[0.8333333333,0.0961551918],[0.8375000000,0.0836246599],[0.8416666667,0.0720161369],[0.8458333333,0.0613302273],[0.8500000000,0.0515635598],[0.8541666667,0.0427088803],[0.8583333333,0.0347551990],[0.8625000000,0.0276878920],[0.8666666667,0.0214889271],[0.8708333333,0.0161369711],[0.8750000000,0.0116076130],[0.8791666667,0.0078735477],[0.8833333333,0.0049047927],[0.8875000000,0.0026688977],[0.8916666667,0.0011311782],[0.8958333333,0.0002549473],[0.9000000000,0.0000000000],[0.9041666667,0.0000000000],[0.9083333333,0.0000000000],[0.9125000000,0.0000000000],[0.9166666667,0.0000000000],[0.9208333333,0.0000000000],[0.9250000000,0.0000000000],[0.9291666667,0.0000000000],[0.9333333333,0.0000000000],[0.9375000000,0.0000000000],[0.9416666667,0.0000000000],[0.9458333333,0.0000000000],[0.9500000000,0.0000000000],[0.9541666667,0.0000000000],[0.9583333333,0.0000000000],[0.9625000000,0.0000000000],[0.9666666667,0.0000000000],[0.9708333333,0.0000000000],[0.9750000000,0.0000000000],[0.9791666667,0.0000000000],[0.9833333333,0.0000000000],[0.9875000000,0.0000000000],[0.9916666667,0.0000000000],[0.9958333333,0.0000000000]]] sun_color: [[[0.5010435645,0.5818710306],[0.0433100000,0.8999700000],[0.8635787716,0.9130000000],[0.1785000000,0.8973600000],[0.8099800000,0.8651100000],[0.2360800000,0.7712700000],[0.6583432177,0.8485126184],[0.1266806142,0.9648102053],[0.9558541267,0.9090909091],[0.5568400771,0.7353760446]],[[0.5001318426,0.5160300000],[0.0572700000,0.6541600000],[0.2395000000,0.5976800000],[0.8104600000,0.6009000000],[0.6967400000,0.5483900000]],[[0.0862400000,0.4257800000],[0.4955600000,0.4033000000],[0.8234200000,0.4340200000]]] - sun_azimuth: [[[0.5000000000,0.4055555556]]] - sun_altitude: [[[0.5000000000,0.7111111111]]] + sun_azimuth: [[[0.5000000000,0.6666666667]]] + sun_altitude: [[[0.5000000000,1.0000000000]]] extinction: [[[0.4913294798,0.6378830084]]] volumetrics: fog_ramp_size: [[[0.5510597303,0.7409470752]]] diff --git a/core/selection.py b/core/selection.py index dcf43051..28709a1c 100644 --- a/core/selection.py +++ b/core/selection.py @@ -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 diff --git a/main.py b/main.py index 1a092946..3329c223 100644 --- a/main.py +++ b/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): """异步导入模型""" diff --git a/scene/scene_manager.py b/scene/scene_manager.py index c15ca830..f814efec 100644 --- a/scene/scene_manager.py +++ b/scene/scene_manager.py @@ -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: diff --git a/ui/interface_manager.py b/ui/interface_manager.py index bb4386cd..eb4fc379 100644 --- a/ui/interface_manager.py +++ b/ui/interface_manager.py @@ -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 \ No newline at end of file + 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) \ No newline at end of file diff --git a/ui/property_panel.py b/ui/property_panel.py index 166b70c2..a12c14ff 100644 --- a/ui/property_panel.py +++ b/ui/property_panel.py @@ -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): diff --git a/ui/widgets.py b/ui/widgets.py index 61251287..db2b75ac 100644 --- a/ui/widgets.py +++ b/ui/widgets.py @@ -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) \ No newline at end of file