From ad59e573be1a5e5d3a231338b7204a4be2114e88 Mon Sep 17 00:00:00 2001 From: Hector <2055590199@qq.com> Date: Mon, 18 Aug 2025 11:49:39 +0800 Subject: [PATCH 1/5] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=B8=96=E7=95=8C?= =?UTF-8?q?=E4=BD=8D=E7=BD=AE=E6=9B=B4=E6=96=B0=EF=BC=8C=E8=8A=82=E7=82=B9?= =?UTF-8?q?=E6=8B=96=E5=8A=A8=E9=80=BB=E8=BE=91=E6=94=B9=E8=BF=9B=EF=BC=8C?= =?UTF-8?q?=E6=89=80=E6=9C=89=E8=8A=82=E7=82=B9=E4=B8=8D=E5=8F=97=E7=88=B6?= =?UTF-8?q?=E7=BA=A7=E7=BC=A9=E6=94=BE=E5=BD=B1=E5=93=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- RenderPipelineFile/config/daytime.yaml | 2 +- core/selection.py | 118 ++++++++++++++++--------- ui/property_panel.py | 21 ++++- 3 files changed, 94 insertions(+), 47 deletions(-) diff --git a/RenderPipelineFile/config/daytime.yaml b/RenderPipelineFile/config/daytime.yaml index abf9d58d..0a5dfddc 100644 --- a/RenderPipelineFile/config/daytime.yaml +++ b/RenderPipelineFile/config/daytime.yaml @@ -17,7 +17,7 @@ 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.4777777778]]] + sun_azimuth: [[[0.5000000000,0.1888888889]]] sun_altitude: [[[0.5000000000,1.0000000000]]] extinction: [[[0.4913294798,0.6378830084]]] volumetrics: diff --git a/core/selection.py b/core/selection.py index 6fdc36fb..4c55c3e4 100644 --- a/core/selection.py +++ b/core/selection.py @@ -1282,33 +1282,23 @@ class SelectionSystem: # 确定轴向量的变换上下文 if parent_node and parent_node != self.world.render: - # 子节点:使用父节点的局部坐标系 - # print(f"子节点拖拽 - 父节点: {parent_node.getName()}, 父节点旋转: {parent_node.getHpr()}") - # transform_context = parent_node transform_mat = parent_node.getMat(self.world.render) world_axis_vector = transform_mat.xformVec(local_axis_vector) else: world_axis_vector = local_axis_vector - # 顶级模型:使用世界坐标系 - # print(f"顶级模型拖拽 - 使用世界坐标系") - # transform_context = self.world.render - axis_end = gizmo_world_pos + world_axis_vector + + #axis_end = gizmo_world_pos + world_axis_vector # 投影到屏幕空间 def worldToScreen(worldPos): try: - # 先转换为相机坐标系 camPos = self.world.cam.getRelativePoint(self.world.render, worldPos) - - # 检查是否在相机前方 if camPos.getY() <= 0: return None screenPos = Point2() if self.world.cam.node().getLens().project(camPos, screenPos): - # 获取准确的窗口尺寸 winWidth, winHeight = self.world.getWindowSize() - winX = (screenPos.x + 1) * 0.5 * winWidth winY = (1 - screenPos.y) * 0.5 * winHeight return (winX, winY) @@ -1316,28 +1306,45 @@ class SelectionSystem: except Exception as e: print(f"世界坐标转屏幕坐标失败: {e}") return None + axis_start_screen = worldToScreen(gizmo_world_pos) + axis_end_world = gizmo_world_pos + world_axis_vector + axis_end_screen = worldToScreen(axis_end_world) + #gizmo_screen = worldToScreen(gizmo_world_pos) + #axis_screen = worldToScreen(axis_end) - gizmo_screen = worldToScreen(gizmo_world_pos) - axis_screen = worldToScreen(axis_end) + # if not gizmo_screen: + # print("拖拽更新失败: 坐标轴中心不在屏幕内") + # return + # if not axis_screen: + # print("拖拽更新失败: 坐标轴端点不在屏幕内") + # return + # + # # 计算轴在屏幕空间的方向向量 + # screen_axis_dir = ( + # axis_screen[0] - gizmo_screen[0], + # axis_screen[1] - gizmo_screen[1] + # ) - if not gizmo_screen: - print("拖拽更新失败: 坐标轴中心不在屏幕内") - return - if not axis_screen: - print("拖拽更新失败: 坐标轴端点不在屏幕内") + if not axis_start_screen or not axis_end_screen: + print("拖拽更新失败: 无法获取轴线屏幕坐标") return - # 计算轴在屏幕空间的方向向量 screen_axis_dir = ( - axis_screen[0] - gizmo_screen[0], - axis_screen[1] - gizmo_screen[1] + axis_end_screen[0] - axis_start_screen[0], + axis_end_screen[1] - axis_start_screen[1] ) + # 归一化屏幕轴方向 import math length = math.sqrt(screen_axis_dir[0]**2 + screen_axis_dir[1]**2) if length > 0: - screen_axis_dir = (screen_axis_dir[0] / length, screen_axis_dir[1] / length) + #screen_axis_dir = (screen_axis_dir[0] / length, screen_axis_dir[1] / length) + screen_axis_dir = ( + screen_axis_dir[0] / length, + screen_axis_dir[1] / length + ) + else: print("拖拽更新失败: 屏幕轴方向长度为0") return @@ -1346,29 +1353,54 @@ class SelectionSystem: projected_distance = (mouseDeltaX * screen_axis_dir[0] + mouseDeltaY * screen_axis_dir[1]) - scale_adjustment = 1.0 - if parent_node and parent_node!= self.world.render: - current_node = parent_node - total_scale = 1.0 - while current_node and current_node != self.world.render: - node_scale = current_node.getScale() - avg_scale = (node_scale.x+node_scale.y + node_scale.z)/3.0 - total_scale *= avg_scale - current_node = current_node.getParent() - if total_scale>0: - scale_adjustment = 1.0 / total_scale - # parent_scale = parent_node.getScale() - # avg_scale = (parent_scale.x+parent_scale.y+parent_scale.z)/3.0 - # if avg_scale>0: - # scale_adjustment = 1.0 / avg_scale + cam_pos = self.world.cam.getPos(self.world.render) + distance_to_object = (cam_pos - gizmo_world_pos).length() + lens = self.world.cam.node().getLens() + fov = lens.getFov()[0] + winWidth,winHeight = self.world.getWindowSize() - fixed_pixel_to_world_ratio = 0.01 # 1像素 = 0.01世界单位 - scale_factor = fixed_pixel_to_world_ratio * scale_adjustment + pixels_to_world_units = (2*distance_to_object*math.tan(math.radians(fov/2)))/winWidth + + movement_distance = projected_distance * pixels_to_world_units + + total_scale_factor = 1.0 + current_node = self.gizmoTarget.getParent() + + while current_node and current_node != self.world.render: + node_scale = current_node.getScale() + avg_scale = (node_scale.x+node_scale.y + node_scale.z) / 3.0 + total_scale_factor *= avg_scale + current_node = current_node.getParent() + + if total_scale_factor > 0: + movement_distance = movement_distance / total_scale_factor - movement_distance = projected_distance * scale_factor - # 获取当前位置并只修改选中轴的坐标 currentPos = self.gizmoTargetStartPos + + # scale_adjustment = 1.0 + # if parent_node and parent_node!= self.world.render: + # current_node = parent_node + # total_scale = 1.0 + # while current_node and current_node != self.world.render: + # node_scale = current_node.getScale() + # avg_scale = (node_scale.x+node_scale.y + node_scale.z)/3.0 + # total_scale *= avg_scale + # current_node = current_node.getParent() + # if total_scale>0: + # scale_adjustment = 1.0 / total_scale + # # parent_scale = parent_node.getScale() + # # avg_scale = (parent_scale.x+parent_scale.y+parent_scale.z)/3.0 + # # if avg_scale>0: + # # scale_adjustment = 1.0 / avg_scale + # + # + # fixed_pixel_to_world_ratio = 0.01 # 1像素 = 0.01世界单位 + # scale_factor = fixed_pixel_to_world_ratio * scale_adjustment + # + # movement_distance = projected_distance * scale_factor + # # 获取当前位置并只修改选中轴的坐标 + # currentPos = self.gizmoTargetStartPos # 根据拖拽的轴,只修改对应的坐标分量 if self.dragGizmoAxis == "x": @@ -1411,7 +1443,7 @@ class SelectionSystem: import time current_time = time.time() if current_time - self._last_drag_debug_time > 0.1: # 每0.1秒最多输出一次 - print(f"拖拽更新成功 - 轴:{self.dragGizmoAxis}, 比例:{scale_factor:.6f}, 投影:{projected_distance:.2f}") + #print(f"拖拽更新成功 - 轴:{self.dragGizmoAxis}, 比例:{scale_factor:.6f}, 投影:{projected_distance:.2f}") self._last_drag_debug_time = current_time except Exception as e: diff --git a/ui/property_panel.py b/ui/property_panel.py index 1684aa7c..05cae777 100644 --- a/ui/property_panel.py +++ b/ui/property_panel.py @@ -192,9 +192,24 @@ class PropertyPanelManager: self.pos_z.setValue(relativePos.getZ()) # 连接位置变化事件 - self.pos_x.valueChanged.connect(lambda v: model.setX(parent, v) if parent else model.setX(v)) - self.pos_y.valueChanged.connect(lambda v: model.setY(parent, v) if parent else model.setY(v)) - self.pos_z.valueChanged.connect(lambda v: model.setZ(parent, v) if parent else model.setZ(v)) + # self.pos_x.valueChanged.connect(lambda v: model.setX(parent, v) if parent else model.setX(v)) + # self.pos_y.valueChanged.connect(lambda v: model.setY(parent, v) if parent else model.setY(v)) + # self.pos_z.valueChanged.connect(lambda v: model.setZ(parent, v) if parent else model.setZ(v)) + + def updateXPosition(value): + model.setX(value) + self.refreshModelValues(model) + self.pos_x.valueChanged.connect(updateXPosition) + + def updateYPosition(value): + model.setY(value) + self.refreshModelValues(model) + self.pos_y.valueChanged.connect(updateYPosition) + + def updateZPosition(value): + model.setZ(value) + self.refreshModelValues(model) + self.pos_z.valueChanged.connect(updateZPosition) # 创建并设置 X, Y, Z 标签居中 x_label1 = QLabel("X") -- 2.45.2 From f81c4fd4cc1df07c853f2b3a4caf68011c9a5a07 Mon Sep 17 00:00:00 2001 From: Hector <2055590199@qq.com> Date: Mon, 18 Aug 2025 16:52:49 +0800 Subject: [PATCH 2/5] =?UTF-8?q?=E8=8A=82=E7=82=B9=E6=98=BE=E7=A4=BA?= =?UTF-8?q?=E9=9A=90=E8=97=8F=E9=80=BB=E8=BE=91=E8=A1=A5=E5=85=A8=EF=BC=8C?= =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=97=A0=E7=94=A8=E9=9D=9E=E9=AA=A8=E9=AA=BC?= =?UTF-8?q?=E5=8A=A8=E7=94=BB=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scene/scene_manager.py | 2 +- ui/interface_manager.py | 34 ++- ui/property_panel.py | 510 +++++----------------------------------- 3 files changed, 91 insertions(+), 455 deletions(-) diff --git a/scene/scene_manager.py b/scene/scene_manager.py index f814efec..a19eabee 100644 --- a/scene/scene_manager.py +++ b/scene/scene_manager.py @@ -630,7 +630,7 @@ class SceneManager: cNode.addSolid(cSphere) # 将碰撞节点附加到模型上 - cNodePath = model.attachNewNode(cNode) + #cNodePath = model.attachNewNode(cNode) # cNodePath.show() # 取消注释可以显示碰撞体,用于调试 # ==================== 场景树管理 ==================== diff --git a/ui/interface_manager.py b/ui/interface_manager.py index d45c3458..d49cf9b3 100644 --- a/ui/interface_manager.py +++ b/ui/interface_manager.py @@ -22,6 +22,7 @@ class InterfaceManager: # 更新场景树 self.world.scene_manager.updateSceneTree() + #self.syncVisibilityDownward() def onTreeItemClicked(self, item, column): """处理树形控件项目点击事件""" @@ -239,4 +240,35 @@ class InterfaceManager: item.setExpanded(True) for i in range(item.childCount()): - self._restore_expanded(item.child(i),path) \ No newline at end of file + self._restore_expanded(item.child(i),path) + + + def syncVisibilityDownward(self): + from collections import deque + + if not self.world.models: + return + q = deque() + + for root in self.world.models: + q.append(root) + + while q: + node = q.popleft() + + visible = node.getPythonTag("visible") + if visible is None: + visible = True + if not visible: + stack = [node] + while stack: + cur = stack.pop() + cur.hide() + + for child in cur.getChildren(): + stack.append(child) + continue + node.show() + + for child in node.getChildren(): + q.append(child) diff --git a/ui/property_panel.py b/ui/property_panel.py index 05cae777..52f87291 100644 --- a/ui/property_panel.py +++ b/ui/property_panel.py @@ -1,3 +1,4 @@ +from collections import deque from traceback import print_exc from types import new_class from typing import Hashable @@ -68,15 +69,34 @@ class PropertyPanelManager: self._propertyLayout.addWidget(tipLabel) return + model = item.data(0, Qt.UserRole) + + user_visible = True + if model: + user_visible = model.getPythonTag("user_visible") + if user_visible is None: + user_visible = True + model.setPythonTag("user_visible", True) + self.name_group = QGroupBox("物体名称") name_layout = QHBoxLayout() self.active_check = QCheckBox() - self.active_check.setChecked(True) # 默认激活 + # 根据模型的实际可见性状态设置复选框 + self.active_check.setChecked(user_visible) self.name_input = QLineEdit(itemText) name_layout.addWidget(self.active_check) name_layout.addWidget(self.name_input) self.name_group.setLayout(name_layout) self._propertyLayout.addWidget(self.name_group) + + if model: + try: + self.active_check.stateChanged.disconnect() + except TypeError: + pass + self.active_check.stateChanged.connect( + lambda state,m = model:self._setUserVisible(m,state == Qt.Checked) + ) # nameLabel = QLabel("名称:") # nameEdit = QLineEdit(itemText) # self._propertyLayout.addRow(nameLabel, nameEdit) @@ -107,6 +127,39 @@ class PropertyPanelManager: if propertyWidget: propertyWidget.update() + def _setUserVisible(self,node,visible): + node.setPythonTag("user_visible",visible) + self._syncEffectiveVisibility(node) + + def _syncEffectiveVisibility(self, start_node): + """广度优先,确保父隐藏则子一定隐藏""" + q = deque([(start_node, True)]) # (node, parent_effective_visible) + + while q: + node, parent_eff = q.popleft() + user = node.getPythonTag("user_visible") + if user is None: + user = True + eff = parent_eff and user + node.setPythonTag("effective_visible", eff) + + if eff: + node.show() + else: + node.hide() + + for child in node.getChildren(): + q.append((child, eff)) + + def _toggleModelVisibility(self, model, state): + """切换模型可见性状态""" + try: + # 用我们自己维护的可见性接口,而不是直接 show/hide + visible = (state == Qt.Checked) + self._setUserVisible(model, visible) + except Exception as e: + print(f"切换模型可见性失败: {str(e)}") + def refreshModelValues(self, nodePath): """实时刷新模型所有属性数值(相对/世界位置、旋转、缩放)""" if not nodePath or self._propertyLayout is None: @@ -185,7 +238,7 @@ class PropertyPanelManager: # 设置位置控件属性 for pos_widget in [self.pos_x, self.pos_y, self.pos_z]: - pos_widget.setRange(-1000, 1000) + pos_widget.setRange(-1000000.0, 1000000.0) self.pos_x.setValue(relativePos.getX()) self.pos_y.setValue(relativePos.getY()) @@ -234,7 +287,7 @@ class PropertyPanelManager: # 设置世界位置控件属性 for world_pos_widget in [self.world_pos_x, self.world_pos_y, self.world_pos_z]: - world_pos_widget.setRange(-1000, 1000) + world_pos_widget.setRange(-1000000.0, 1000000.0) world_pos_widget.setReadOnly(True) self.world_pos_x.setValue(worldPos.getX()) @@ -253,7 +306,7 @@ class PropertyPanelManager: # 设置旋转控件属性 for rot_widget in [self.rot_h, self.rot_p, self.rot_r]: - rot_widget.setRange(-180, 180) + rot_widget.setRange(-360, 360) self.rot_h.setValue(model.getH()) self.rot_p.setValue(model.getP()) @@ -4182,14 +4235,6 @@ class PropertyPanelManager: # 忽略 Actor 加载错误,很多模型都不是角色动画 print(f"[信息] 该模型不包含骨骼动画: {actor_error}") - # 只有在没有骨骼动画时才检测非骨骼动画 - if not has_skeletal_anim: - non_skeletal_anims = self._detectNonSkeletalAnimations(origin_model) - if non_skeletal_anims and self._validateNonSkeletalAnimations(origin_model, non_skeletal_anims): - self._buildNonSkeletalUI(origin_model, non_skeletal_anims, animation_layout) - has_animation = True - print(f"[信息] 检测到非骨骼动画: {list(non_skeletal_anims.keys())}") - # 如果都没有动画 if not has_animation: no_anim_label = QLabel("此模型无动画") @@ -4861,434 +4906,7 @@ except Exception as e: origin_model.setPythonTag("anim_speed",speed) print(f"[动画] 速度设为: {speed} ({display_name})") - def _detectNonSkeletalAnimations(self, origin_model): - """检测模型中的非骨骼动画""" - animations = {} - try: - print(f"[调试] 开始检测非骨骼动画: {origin_model}") - - # 1. 精确检测 AnimBundle (非骨骼动画) - bundle_nodes = origin_model.findAllMatches("**/+AnimBundleNode") - print(f"[调试] 找到 AnimBundleNode 数量: {bundle_nodes.getNumPaths()}") - - if not bundle_nodes.isEmpty(): - for i, bundle_node in enumerate(bundle_nodes): - try: - bundle = bundle_node.node().getBundle() - print(f"[调试] AnimBundle #{i}: {bundle}") - - if bundle and hasattr(bundle, 'getNumFrames'): - num_frames = bundle.getNumFrames() - print(f"[调试] Bundle 帧数: {num_frames}") - - # 只有真正有多帧的才认为是动画 - if num_frames > 1: - anim_names = [] - - # 尝试获取动画名称 - try: - if hasattr(bundle, 'getName') and bundle.getName(): - anim_names.append(bundle.getName()) - else: - anim_names.append(f"Animation_{i}") - print(f"[调试] 检测到有效帧动画: {anim_names}, {num_frames} 帧") - except Exception as name_error: - anim_names.append(f"Animation_{i}") - print(f"[调试] 使用默认动画名: Animation_{i}") - - animations['transform'] = { - 'bundle': bundle, - 'names': anim_names, - 'node': bundle_node, - 'frames': num_frames - } - else: - print(f"[调试] Bundle 只有 {num_frames} 帧,不认为是动画") - - except Exception as bundle_error: - print(f"[调试] 处理 bundle 失败: {bundle_error}") - - # 2. 仅对 GLB 文件进行特殊检测 - filepath = origin_model.getTag("model_path") - if filepath and filepath.lower().endswith('.glb') and not animations: - print(f"[调试] GLB 文件特殊检测: {filepath}") - - # 检查是否有任何动画相关的节点名称 - all_nodes = origin_model.findAllMatches("**") - anim_indicators = [] - - for node in all_nodes: - node_name = node.getName().lower() - # 查找典型的动画节点命名模式 - if any(keyword in node_name for keyword in ['anim', 'key', 'frame', 'action', 'timeline']): - anim_indicators.append(node.getName()) - print(f"[调试] 发现可能的动画节点: {node.getName()}") - - # 只有在明确找到动画指示器时才创建动画条目 - if anim_indicators: - animations['glb_keyframe'] = { - 'bundle': None, - 'names': ['GLB_Animation'], - 'node': origin_model, - 'type': 'glb_manual', - 'indicators': anim_indicators - } - print(f"[调试] 创建 GLB 动画条目,基于指示器: {anim_indicators}") - - except Exception as e: - print(f"检测非骨骼动画失败: {e}") - - print(f"[调试] 最终检测结果: {animations}") - return animations if animations else None - - def _validateNonSkeletalAnimations(self, origin_model, animations): - """验证检测到的非骨骼动画是否真的可以播放""" - try: - print(f"[验证] 开始验证非骨骼动画: {list(animations.keys())}") - - for anim_type, anim_data in animations.items(): - if anim_type == 'transform': - # 验证变换动画 - bundle = anim_data.get('bundle') - if bundle: - # 检查是否真的有可播放的动画数据 - if hasattr(bundle, 'getNumFrames'): - frames = bundle.getNumFrames() - if frames <= 1: - print(f"[验证] 变换动画帧数不足: {frames}") - return False - - # 检查是否有有效的动画通道 - try: - if hasattr(bundle, 'getNumChannels'): - channels = bundle.getNumChannels() - if channels == 0: - print(f"[验证] 无有效动画通道") - return False - except: - pass - - elif anim_type == 'glb_keyframe': - # 验证 GLB 动画指示器 - indicators = anim_data.get('indicators', []) - if not indicators: - print(f"[验证] GLB 动画无有效指示器") - return False - - print(f"[验证] 动画验证通过") - return True - - except Exception as e: - print(f"[验证] 动画验证失败: {e}") - return False - - def _buildNonSkeletalUI(self, origin_model, animations, layout): - """构建非骨骼动画控制UI""" - from PyQt5.QtWidgets import QLabel, QComboBox, QHBoxLayout, QWidget, QPushButton, QDoubleSpinBox, QTabWidget - - # 动画信息 - info_text = f"非骨骼动画数量: {len(animations)}" - info_label = QLabel(info_text) - info_label.setStyleSheet("color:#888;font-size:10px;") - layout.addWidget(QLabel("信息:"), 0, 0) - layout.addWidget(info_label, 0, 1, 1, 3) - - # 如果有多种类型的动画,使用标签页 - if len(animations) > 1: - tab_widget = QTabWidget() - - for anim_type, anim_data in animations.items(): - tab = QWidget() - tab_layout = QVBoxLayout(tab) - self._buildAnimationTypeUI(tab_layout, origin_model, anim_type, anim_data) - tab_widget.addTab(tab, self._getAnimTypeDisplayName(anim_type)) - - self._propertyLayout.addRow("动画类型:", tab_widget) - else: - # 只有一种类型,直接显示 - anim_type, anim_data = next(iter(animations.items())) - self._buildAnimationTypeUI(self._propertyLayout, origin_model, anim_type, anim_data) - - # 存储动画信息供控制使用 - if not hasattr(self, '_non_skeletal_cache'): - self._non_skeletal_cache = {} - self._non_skeletal_cache[origin_model] = animations - - def _buildAnimationTypeUI(self, layout, origin_model, anim_type, anim_data): - """为特定动画类型构建UI""" - from PyQt5.QtWidgets import QLabel, QComboBox, QHBoxLayout, QWidget, QPushButton, QDoubleSpinBox - - if anim_type == 'transform': - # 变换动画控制 - self.ns_transform_combo = QComboBox() - self.ns_transform_combo.addItems(anim_data['names']) - layout.addRow("变换动画:", self.ns_transform_combo) - - elif anim_type == 'texture': - # 纹理动画控制 - self.ns_texture_combo = QComboBox() - self.ns_texture_combo.addItems(anim_data['stages']) - layout.addRow("纹理动画:", self.ns_texture_combo) - - elif anim_type == 'material': - # 材质动画控制 - self.ns_material_combo = QComboBox() - self.ns_material_combo.addItems(anim_data['properties']) - layout.addRow("材质动画:", self.ns_material_combo) - - elif anim_type == 'lerp': - # Lerp动画控制 - self.ns_lerp_combo = QComboBox() - self.ns_lerp_combo.addItems(anim_data['intervals']) - layout.addRow("Lerp动画:", self.ns_lerp_combo) - - # 通用控制按钮 - btn_box = QWidget() - btn_lay = QHBoxLayout(btn_box) - for txt, cmd in (("播放", "play"), ("暂停", "pause"), ("停止", "stop"), ("循环", "loop")): - btn = QPushButton(txt) - btn.clicked.connect(lambda _, c=cmd, t=anim_type: self._controlNonSkeletalAnimation(origin_model, t, c)) - btn_lay.addWidget(btn) - layout.addRow("控制:", btn_box) - - # 播放速度 - speed_spinbox = QDoubleSpinBox() - speed_spinbox.setRange(0.1, 5.0) - speed_spinbox.setSingleStep(0.1) - speed_spinbox.setValue(1.0) - speed_spinbox.valueChanged.connect(lambda v, t=anim_type: self._setNonSkeletalAnimationSpeed(origin_model, t, v)) - layout.addRow("播放速度:", speed_spinbox) - - def _getAnimTypeDisplayName(self, anim_type): - """获取动画类型的显示名称""" - names = { - 'transform': '变换动画', - 'glb_keyframe': 'GLB关键帧动画', - 'texture': '纹理动画', - 'material': '材质动画', - 'lerp': 'Lerp动画' - } - return names.get(anim_type, anim_type) - - def _controlNonSkeletalAnimation(self, origin_model, anim_type, command): - """控制非骨骼动画播放""" - try: - if not hasattr(self, '_non_skeletal_cache') or origin_model not in self._non_skeletal_cache: - return - - animations = self._non_skeletal_cache[origin_model] - if anim_type not in animations: - return - - anim_data = animations[anim_type] - - if anim_type == 'transform': - self._controlTransformAnimation(origin_model, anim_data, command) - elif anim_type == 'glb_keyframe': - self._controlGLBKeyframeAnimation(origin_model, anim_data, command) - elif anim_type == 'texture': - self._controlTextureAnimation(origin_model, anim_data, command) - elif anim_type == 'material': - self._controlMaterialAnimation(origin_model, anim_data, command) - elif anim_type == 'lerp': - self._controlLerpAnimation(origin_model, anim_data, command) - - print(f"[非骨骼动画] {anim_type} {command}") - - except Exception as e: - print(f"控制非骨骼动画失败: {e}") - - def _controlTransformAnimation(self, origin_model, anim_data, command): - """控制变换动画""" - try: - bundle = anim_data['bundle'] - bundle_node = anim_data['node'] - - print(f"[调试] 控制变换动画: {command}, Bundle: {bundle}") - - if command == 'play': - # 方法1: 通过 AnimBundle 直接播放 - if hasattr(bundle, 'play'): - bundle.play() - print(f"[动画] 通过 bundle.play() 播放") - # 方法2: 通过 AnimControl 播放 - elif hasattr(bundle_node.node(), 'getAnimControl'): - controls = bundle_node.node().getAnimControls() - if controls: - controls[0].play() - print(f"[动画] 通过 AnimControl 播放") - # 方法3: 通过启用动画节点 - else: - bundle_node.node().setPlayRate(1.0) - print(f"[动画] 设置播放速率为 1.0") - - elif command == 'pause': - if hasattr(bundle, 'pause'): - bundle.pause() - elif hasattr(bundle_node.node(), 'getAnimControl'): - controls = bundle_node.node().getAnimControls() - if controls: - controls[0].pause() - else: - bundle_node.node().setPlayRate(0.0) - - elif command == 'stop': - if hasattr(bundle, 'stop'): - bundle.stop() - elif hasattr(bundle_node.node(), 'getAnimControl'): - controls = bundle_node.node().getAnimControls() - if controls: - controls[0].stop() - else: - bundle_node.node().setPlayRate(0.0) - # 重置到第一帧 - if hasattr(bundle, 'setFrame'): - bundle.setFrame(0) - - elif command == 'loop': - if hasattr(bundle, 'loop'): - bundle.loop() - elif hasattr(bundle_node.node(), 'getAnimControl'): - controls = bundle_node.node().getAnimControls() - if controls: - controls[0].loop(True) - controls[0].play() - else: - bundle_node.node().setPlayRate(1.0) - - except Exception as e: - print(f"[错误] 控制变换动画失败: {e}") - import traceback - traceback.print_exc() - - def _controlGLBKeyframeAnimation(self, origin_model, anim_data, command): - """控制 GLB 关键帧动画""" - try: - print(f"[调试] 控制 GLB 动画: {command}") - - # 尝试通过 AnimControlCollection 控制 - from panda3d.core import AnimControlCollection - - # 方法1: 查找 AnimControlCollection - anim_collection = AnimControlCollection() - origin_model.getAnimControls(anim_collection) - - if anim_collection.getNumAnims() > 0: - for i in range(anim_collection.getNumAnims()): - anim_control = anim_collection.getAnim(i) - print(f"[调试] 找到动画控制: {anim_control.getName()}") - - if command == 'play': - anim_control.setPlayRate(1.0) - print(f"[GLB动画] 播放: {anim_control.getName()}") - elif command == 'pause': - anim_control.setPlayRate(0.0) - print(f"[GLB动画] 暂停: {anim_control.getName()}") - elif command == 'stop': - anim_control.setPlayRate(0.0) - anim_control.setFrame(0) - print(f"[GLB动画] 停止: {anim_control.getName()}") - elif command == 'loop': - anim_control.setPlayRate(1.0) - anim_control.loop(True) - print(f"[GLB动画] 循环: {anim_control.getName()}") - return - - # 方法2: 通过自动播放任务 - if command == 'play': - origin_model.setPlayRate(1.0) - print(f"[GLB动画] 设置播放速率为 1.0") - elif command == 'pause': - origin_model.setPlayRate(0.0) - print(f"[GLB动画] 暂停播放") - elif command == 'stop': - origin_model.setPlayRate(0.0) - print(f"[GLB动画] 停止播放") - elif command == 'loop': - origin_model.setPlayRate(1.0) - print(f"[GLB动画] 循环播放") - - except Exception as e: - print(f"[错误] 控制 GLB 动画失败: {e}") - - def _controlTextureAnimation(self, origin_model, anim_data, command): - """控制纹理动画""" - # 纹理动画通常通过 LerpInterval 实现 - print(f"[纹理动画] {command} - 功能待实现") - - def _controlMaterialAnimation(self, origin_model, anim_data, command): - """控制材质动画""" - # 材质动画通常通过修改材质属性实现 - print(f"[材质动画] {command} - 功能待实现") - - def _controlLerpAnimation(self, origin_model, anim_data, command): - """控制Lerp动画""" - # 通过 LerpInterval 控制 - print(f"[Lerp动画] {command} - 功能待实现") - - def _setNonSkeletalAnimationSpeed(self, origin_model, anim_type, speed): - """设置非骨骼动画播放速度""" - try: - if not hasattr(self, '_non_skeletal_cache') or origin_model not in self._non_skeletal_cache: - return - - animations = self._non_skeletal_cache[origin_model] - if anim_type not in animations: - return - - anim_data = animations[anim_type] - - if anim_type == 'transform': - bundle = anim_data['bundle'] - if hasattr(bundle, 'setPlayRate'): - bundle.setPlayRate(speed) - - print(f"[非骨骼动画] {anim_type} 速度设为: {speed}") - - except Exception as e: - print(f"设置非骨骼动画速度失败: {e}") - - def _detectPlayer(self,origin_model): - filepath = origin_model.getTag("model_path") - if filepath: - try: - actor = Actor(filepath) - if actor.getAnimNames(): - actor.cleanup();actor.removeNode() - return ("actor",None) - except: - pass - bundle_np = origin_model.find("**/+AnimBundleNode") - if not bundle_np.isEmpty(): - ctrl = bundle_np.node().getBundle().bind(origin_model.node(),PartGroup.PART_Whole) - return ("bundle",ctrl) - return None - - def _buildBundleUI(self,origin_model,ctrl): - from PyQt5.QtWidgets import QLabel,QPushButton,QHBoxLayout,QWidget,QSlider - - title = QLabel("骨骼动画控制") - title.setStyleSheet("color:#FF8C00;font-weight:bold;font-size:14px;margin-top:10px;") - self._propertyLayout.addRow(title) - - btn_box = QWidget() - lay = QHBoxLayout(btn_box) - for txt , fn in (("播放",ctrl.play), - ("暂停",ctrl.stop), - ("重置",lambda:ctrl.pose(0))): - btn = QPushButton(txt) - btn.clicked.connect(fn) - lay.addWidget(btn) - self._propertyLayout.addRow("控制:",btn_box) - - slider = QSlider() - slider.setOrientation(1) - slider.setRange(0,int(ctrl.getNumFrames())) - slider.valueChanged.connect(ctrl.pose) - self._propertyLayout.addRow("帧:",slider) - - self._actor_cache[origin_model] = ("bundle",ctrl) def _dispatchAnimCommand(self,origin_model,cmd): cache = self._actor_cache.get(origin_model) @@ -5323,20 +4941,6 @@ except Exception as e: elif isinstance(cmd,tuple) and cmd[0] == "speed": actor.setPlayRate(cmd[1], anim_name) - elif kind == "bundle": - ctrl = player - if cmd == "play": - ctrl.play() - elif cmd == "pause": - ctrl.stop() - elif cmd == "stop": - ctrl.stop() - ctrl.pose(0) - elif cmd == "loop": - ctrl.loop(True) - elif isinstance(cmd,tuple) and cmd[0] =="speed": - ctrl.setPlayRate(cmd[1]) - def removeActorForModel(self, model): """删除 model 对应的 Actor(如果存在)""" actor = self._actor_cache.pop(model, None) -- 2.45.2 From 138820ed488ccae7d9948e029d614a22702ae1c0 Mon Sep 17 00:00:00 2001 From: Hector <2055590199@qq.com> Date: Tue, 19 Aug 2025 10:23:38 +0800 Subject: [PATCH 3/5] =?UTF-8?q?1.=E6=94=B9=E8=BF=9B=E5=B1=9E=E6=80=A7?= =?UTF-8?q?=E9=9D=A2=E6=9D=BF=E6=98=BE=E7=A4=BA=E9=80=BB=E8=BE=91=202.?= =?UTF-8?q?=E5=9D=90=E6=A0=87=E8=BD=B4=E4=B8=8E=E8=8A=82=E7=82=B9=E5=AD=98?= =?UTF-8?q?=E5=9C=A8=E5=85=B3=E7=B3=BB=E6=94=B9=E8=BF=9B=203.=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E6=9B=B4=E5=A4=9A=E7=A7=8D=E7=B1=BB=E8=8A=82=E7=82=B9?= =?UTF-8?q?=E5=88=A0=E9=99=A4=E9=80=BB=E8=BE=91=204.=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E6=A8=A1=E5=9E=8B=E5=88=87=E6=8D=A2=E5=8F=AF=E8=A7=81=E6=80=A7?= =?UTF-8?q?=E7=9A=84=E7=A2=B0=E6=92=9E=E4=BD=93=E5=BC=82=E5=B8=B8=E6=98=BE?= =?UTF-8?q?=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- RenderPipelineFile/config/daytime.yaml | 2 +- core/selection.py | 13 +++++++++ scene/scene_manager.py | 3 ++- ui/interface_manager.py | 37 ++++++++++++++++++++++---- ui/property_panel.py | 16 ++++++++--- 5 files changed, 61 insertions(+), 10 deletions(-) diff --git a/RenderPipelineFile/config/daytime.yaml b/RenderPipelineFile/config/daytime.yaml index 0a5dfddc..e67993cc 100644 --- a/RenderPipelineFile/config/daytime.yaml +++ b/RenderPipelineFile/config/daytime.yaml @@ -17,7 +17,7 @@ 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.1888888889]]] + sun_azimuth: [[[0.5000000000,0.4416666667]]] sun_altitude: [[[0.5000000000,1.0000000000]]] extinction: [[[0.4913294798,0.6378830084]]] volumetrics: diff --git a/core/selection.py b/core/selection.py index 4c55c3e4..5d28830b 100644 --- a/core/selection.py +++ b/core/selection.py @@ -217,6 +217,9 @@ class SelectionSystem: return task.cont self._last_selection_box_update = current_time + #检查目标节点是否已被删除 + self.checkAndClearIfTargetDeleted() + if not self.selectionBox or not self.selectionBoxTarget: return task.done # 结束任务 @@ -648,6 +651,9 @@ class SelectionSystem: return task.cont self._last_gizmo_update = current_time + #检查目标节点是否已被删除 + self.checkAndClearIfTargetDeleted() + if not self.gizmo or not self.gizmoTarget: return task.done @@ -1507,3 +1513,10 @@ class SelectionSystem: def hasSelection(self): """检查是否有选中的节点""" return self.selectedNode is not None + + def checkAndClearIfTargetDeleted(self): + if self.gizmoTarget and self.gizmoTarget.isEmpty(): + self.clearGizmo() + + if self.selectionBoxTarget and self.selectionBoxTarget.isEmpty(): + self.clearSelectionBox() diff --git a/scene/scene_manager.py b/scene/scene_manager.py index a19eabee..75671fe2 100644 --- a/scene/scene_manager.py +++ b/scene/scene_manager.py @@ -630,7 +630,8 @@ class SceneManager: cNode.addSolid(cSphere) # 将碰撞节点附加到模型上 - #cNodePath = model.attachNewNode(cNode) + cNodePath = model.attachNewNode(cNode) + #cNodePath.hide() # cNodePath.show() # 取消注释可以显示碰撞体,用于调试 # ==================== 场景树管理 ==================== diff --git a/ui/interface_manager.py b/ui/interface_manager.py index d49cf9b3..2fc489db 100644 --- a/ui/interface_manager.py +++ b/ui/interface_manager.py @@ -1,5 +1,6 @@ from PyQt5.QtWidgets import QTreeWidget, QTreeWidgetItem, QMenu from PyQt5.QtCore import Qt +from PyQt5.sip import delete from panda3d.core import GeomNode, ModelRoot @@ -22,13 +23,15 @@ class InterfaceManager: # 更新场景树 self.world.scene_manager.updateSceneTree() - #self.syncVisibilityDownward() def onTreeItemClicked(self, item, column): """处理树形控件项目点击事件""" if not item: + self.world.selection.updateSelection(None) return + self.world.property_panel.updatePropertyPanel(item) + # 获取节点对象 nodePath = item.data(0, Qt.UserRole) if nodePath: @@ -37,7 +40,7 @@ class InterfaceManager: self.world.selection.updateSelection(nodePath) # 更新属性面板 - self.world.property_panel.updatePropertyPanel(item) + #self.world.property_panel.updatePropertyPanel(item) print(f"树形控件点击: {item.text(0)}") else: @@ -78,6 +81,9 @@ class InterfaceManager: if self.isModelOrChild(item): deleteAction = menu.addAction("删除") deleteAction.triggered.connect(lambda: self.deleteNode(nodePath, item)) + else: + deleteAction = menu.addAction("删除") + deleteAction.triggered.connect(lambda: self.deleteNode(nodePath, item)) # 显示菜单 menu.exec_(self.treeWidget.viewport().mapToGlobal(position)) @@ -95,12 +101,33 @@ class InterfaceManager: try: # 从场景中移除 self.world.property_panel.removeActorForModel(nodePath) + + if hasattr(nodePath,'getPythonTag'): + light_object = nodePath.getPythonTag('rp_light_object') + if light_object and hasattr(self.world,'render_pipeline'): + self.world.render_pipeline.remove_light(light_object) + + if hasattr(self.world,'selection'): + if self.world.selection.selectedNode == nodePath: + self.world.selection.clearSelectionBox() + self.world.selection.clearGizmo() + self.world.selection.selectedNode = None + self.world.selection.selectedObject = None + + if nodePath in self.world.Spotlight: + self.world.Spotlight.remove(nodePath) + if nodePath in self.world.Pointlight: + self.world.Pointlight.remove(nodePath) + nodePath.removeNode() + if hasattr(self.world,'selection'): + self.world.selection.checkAndClearIfTargetDeleted() + # 如果是模型根节点,从模型列表中移除 - if item.parent().text(0) == "模型": - if nodePath in self.world.models: - self.world.models.remove(nodePath) + #if item.parent().text(0) == "模型": + if nodePath in self.world.models: + self.world.models.remove(nodePath) # 从树形控件中移除 parentItem = item.parent() diff --git a/ui/property_panel.py b/ui/property_panel.py index 52f87291..8b6382a7 100644 --- a/ui/property_panel.py +++ b/ui/property_panel.py @@ -143,10 +143,15 @@ class PropertyPanelManager: eff = parent_eff and user node.setPythonTag("effective_visible", eff) - if eff: - node.show() - else: + #特殊处理:检查是否为碰撞体节点 + + if node.getName().startswith("modelCollision_"): node.hide() + else: + if eff: + node.show() + else: + node.hide() for child in node.getChildren(): q.append((child, eff)) @@ -157,6 +162,11 @@ class PropertyPanelManager: # 用我们自己维护的可见性接口,而不是直接 show/hide visible = (state == Qt.Checked) self._setUserVisible(model, visible) + + collision_nodes = model.findAllMatches("**/modelCollision_*") + for collision_node in collision_nodes: + collision_node.hide() + except Exception as e: print(f"切换模型可见性失败: {str(e)}") -- 2.45.2 From dc150f793bf13f565fc686135af8a01e88f1f854 Mon Sep 17 00:00:00 2001 From: Hector <2055590199@qq.com> Date: Wed, 20 Aug 2025 16:36:19 +0800 Subject: [PATCH 4/5] =?UTF-8?q?1.=E5=9D=90=E6=A0=87=E8=BD=B4=E9=A2=9C?= =?UTF-8?q?=E8=89=B2=E6=AD=A3=E5=B8=B8=E4=BD=86=E9=9C=80=E7=BB=A7=E7=BB=AD?= =?UTF-8?q?=E6=9B=B4=E6=94=B9sort=202.=E6=8B=96=E6=8B=BD=E7=BB=93=E6=9D=9F?= =?UTF-8?q?=E5=90=8E=E6=A8=A1=E5=9E=8B=E5=8F=AF=E8=A7=81=E6=80=A7=E6=9B=B4?= =?UTF-8?q?=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/TranslateArrowHandle.fbx | Bin 0 -> 49692 bytes core/selection.py | 501 ++++++++++++---------------------- ui/widgets.py | 1 + 3 files changed, 177 insertions(+), 325 deletions(-) create mode 100755 core/TranslateArrowHandle.fbx diff --git a/core/TranslateArrowHandle.fbx b/core/TranslateArrowHandle.fbx new file mode 100755 index 0000000000000000000000000000000000000000..297256501b3da663344a4ccd8a988a2e5e30319d GIT binary patch literal 49692 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 HhUEVbfwn*L literal 0 HcmV?d00001 diff --git a/core/selection.py b/core/selection.py index 5d28830b..ecbbe5c4 100644 --- a/core/selection.py +++ b/core/selection.py @@ -11,7 +11,7 @@ from PIL.ImageChops import lighter from panda3d.core import (Vec3, Point3, Point2, LineSegs, ColorAttrib, RenderState, DepthTestAttrib, CollisionTraverser, CollisionHandlerQueue, CollisionNode, CollisionRay, GeomNode, BitMask32, Material, LColor, DepthWriteAttrib, - TransparencyAttrib) + TransparencyAttrib, Vec4) from direct.task.TaskManagerGlobal import taskMgr import math @@ -50,14 +50,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, 0), # 红色 + "y": (0, 1, 0, 0), # 绿色 + "z": (0, 0, 1, 0) # 蓝色 } 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, 1, 0, 0), # 黄色高亮 + "y": (1, 1, 0, 0), # 黄色高亮 + "z": (1, 1, 0, 0) # 黄色高亮 } print("✓ 选择和变换系统初始化完成") @@ -303,7 +303,7 @@ class SelectionSystem: # 只调用一次几何体创建 self.createGizmoGeometry() - # 只调用一次颜色设置 + #只调用一次颜色设置 self.setGizmoAxisColor("x", self.gizmo_colors["x"]) self.setGizmoAxisColor("y", self.gizmo_colors["y"]) self.setGizmoAxisColor("z", self.gizmo_colors["z"]) @@ -322,321 +322,79 @@ class SelectionSystem: if not self.gizmo: return - # 创建X轴(红色) - x_lines = LineSegs("x_axis") - x_lines.setThickness(6.0) - x_lines.moveTo(0, 0, 0) - x_lines.drawTo(self.axis_length, 0, 0) - # 创建X轴箭头 - x_lines.moveTo(self.axis_length - 0.5, -0.2, 0) - x_lines.drawTo(self.axis_length, 0, 0) - x_lines.drawTo(self.axis_length - 0.5, 0.2, 0) - x_geom = x_lines.create() - self.gizmoXAxis = self.gizmo.attachNewNode(x_geom) - self.gizmoXAxis.setName("gizmo_x_axis") - #self.gizmoXAxis.setLightOff() - # 创建Y轴(绿色) - y_lines = LineSegs("y_axis") - y_lines.setThickness(6.0) - y_lines.moveTo(0, 0, 0) - y_lines.drawTo(0, self.axis_length, 0) - # 创建Y轴箭头 - y_lines.moveTo(-0.2, self.axis_length - 0.5, 0) - y_lines.drawTo(0, self.axis_length, 0) - y_lines.drawTo(0.2, self.axis_length - 0.5, 0) - y_geom = y_lines.create() - self.gizmoYAxis = self.gizmo.attachNewNode(y_geom) - self.gizmoYAxis.setName("gizmo_y_axis") - #self.gizmoYAxis.setLightOff() + model_paths = [ + "core/TranslateArrowHandle.fbx", + "EG/core/TranslateArrowHandle.fbx", + ] + arrow_model = None + for path in model_paths: + try: + arrow_model = self.world.loader.loadModel(path) + if arrow_model: + print(f"成功加载模型: {path}") + break + except: + continue + self.gizmoXAxis = self.gizmo.attachNewNode("gizmo_x_axis") + x_arrow = arrow_model.copyTo(self.gizmoXAxis) + x_arrow.setName("x_arrow") + x_arrow.setHpr(0,-90,0) + x_arrow.setScale(0.1,0.05,0.05) + x_arrow.setPos(0,0,0) + + self.gizmoYAxis = self.gizmo.attachNewNode("gizmo_y_axis") + y_arrow = arrow_model.copyTo(self.gizmoYAxis) + y_arrow.setName("y_arrow") + y_arrow.setHpr(90,0,0) + y_arrow.setScale(0.1,0.05,0.05) + y_arrow.setPos(0,0,0) # 创建Z轴(蓝色) - z_lines = LineSegs("z_axis") - z_lines.setThickness(6.0) - z_lines.moveTo(0, 0, 0) - z_lines.drawTo(0, 0, self.axis_length) - # 创建Z轴箭头 - z_lines.moveTo(-0.2, 0, self.axis_length - 0.5) - z_lines.drawTo(0, 0, self.axis_length) - z_lines.drawTo(0.2, 0, self.axis_length - 0.5) - z_geom = z_lines.create() - self.gizmoZAxis = self.gizmo.attachNewNode(z_geom) - self.gizmoZAxis.setName("gizmo_z_axis") - #self.gizmoZAxis.setLightOff() + self.gizmoZAxis = self.gizmo.attachNewNode("gizmo_z_axis") + z_arrow = arrow_model.copyTo(self.gizmoZAxis) + z_arrow.setName("z_arrow") + # 旋转箭头使其指向Z轴正方向 + z_arrow.setHpr(0, 0, -90) # 根据需要调整旋转 + z_arrow.setScale(0.1,0.05,0.05) + z_arrow.setPos(0, 0, 0) - # 确保坐标轴不被光照影响 - #self.gizmo.setLightOff() - - # 使用最强的渲染设置,确保坐标轴绝对不会被遮挡 - self.gizmo.setBin("gui-popup", 0) # 使用最高的GUI渲染层 - self.gizmo.setDepthTest(False) # 完全禁用深度测试 - self.gizmo.setDepthWrite(False) # 禁用深度写入 - self.gizmo.setTwoSided(True) # 双面渲染 - - # 创建强制前景渲染状态 - from panda3d.core import RenderModeAttrib, TransparencyAttrib - foreground_state = RenderState.make( - DepthTestAttrib.make(DepthTestAttrib.MNone), # 完全不进行深度测试 - TransparencyAttrib.make(TransparencyAttrib.MAlpha) # 启用透明度混合 - ) - #self.gizmo.setState(foreground_state) - - # 对每个坐标轴设置独立的最高渲染优先级 - self.gizmoXAxis.setBin("gui-popup", 10) - self.gizmoXAxis.setDepthTest(False) - self.gizmoXAxis.setDepthWrite(False) - self.gizmoXAxis.setLightOff() - self.gizmoXAxis.setState(foreground_state) - - self.gizmoYAxis.setBin("gui-popup", 20) - self.gizmoYAxis.setDepthTest(False) - self.gizmoYAxis.setDepthWrite(False) - self.gizmoYAxis.setLightOff() - self.gizmoYAxis.setState(foreground_state) - - self.gizmoZAxis.setBin("gui-popup", 30) - self.gizmoZAxis.setDepthTest(False) - self.gizmoZAxis.setDepthWrite(False) - self.gizmoZAxis.setLightOff() - self.gizmoZAxis.setState(foreground_state) - - - - # 初始化高亮状态 - self.gizmoHighlightAxis = None - - # 立即设置初始颜色,确保创建时就有正确的颜色 - self.setGizmoAxisColor("z", self.gizmo_colors["z"]) + # 设置初始颜色 self.setGizmoAxisColor("x", self.gizmo_colors["x"]) self.setGizmoAxisColor("y", self.gizmo_colors["y"]) + self.setGizmoAxisColor("z", self.gizmo_colors["z"]) + + # 使用最强的渲染设置,确保坐标轴绝对不会被遮挡 + # self.gizmo.setBin("default", 0) # 使用最高的GUI渲染层 + # self.gizmo.setDepthTest(True) # 完全禁用深度测试 + # self.gizmo.setDepthWrite(True) # 禁用深度写入 + # self.gizmo.setTwoSided(True) # 双面渲染 - print(f"✓ 坐标轴几何体创建完成,长度={self.axis_length}") - - # 为 RenderPipeline 环境设置正确的渲染状态 - self._setupRenderPipelineCompatibleGizmo() - - self._setupEmissiveMaterials() - self._setupGizmoRendering() except Exception as e: print(f"创建坐标轴几何体失败: {str(e)}") - def _setupEmissiveMaterials(self): - try: - from panda3d.core import Material,Vec4 - materials ={ - "x":(Vec4(1,0,0,1),Vec4(2.0,0,0,1)), - "y":(Vec4(0,1,0,1),Vec4(0,2.0,0,1)), - "z":(Vec4(0,0,1,1),Vec4(0,0,2.0,1)) - } - axis_nodes ={ - "x":self.gizmoXAxis, - "y":self.gizmoYAxis, - "z":self.gizmoZAxis - } - for axis,(base_color,emission_color) in materials.items(): - if axis_nodes[axis]: - material=Material(f"gizmo_{axis}_material") - material.setBaseColor(base_color) - material.setEmission(emission_color) - material.setRoughness(1.0) - material.setMetallic(0.0) - axis_nodes[axis].setMaterial(material) - except Exception as e: - print(f"自发光材质设置失败: {str(e)}") + def _applyAxisMaterial(self, node, color): + from panda3d.core import Material, Vec4, ColorWriteAttrib, DepthWriteAttrib + # 构造一个“自发光”的材质,让颜色不依赖灯光/法线 + m = Material() + col = Vec4(*color) + m.setBaseColor(col) # 记录用 + m.setDiffuse(col) # 记录用(RP标准材质可能不读取) + m.setEmission(Vec4(col.x, col.y, col.z, 1.0)) # 关键:用发光通道显示颜色 + node.setMaterial(m, 1) - def _setupGizmoRendering(self): - """设置坐标轴渲染属性""" - try: - # 设置渲染优先级,确保在最前面显示 - self.gizmo.setBin("gui-popup", 1000) - self.gizmo.setDepthTest(False) - self.gizmo.setDepthWrite(False) - self.gizmo.setLightOff() + # 走能直接写到最终画面的阶段(两种方式二选一): + # 方式1:完全绕过管线(最稳) + node.setTag("pipeline-disable", "1") + # 方式2:留在管线内但走最终阶段(若不想禁用管线,可改用下面这一行,并去掉上一行) + # node.setTag("pipeline-stage", "final") - # 为每个轴设置独立的渲染属性 - for i, axis_node in enumerate([self.gizmoXAxis, self.gizmoYAxis, self.gizmoZAxis]): - if axis_node: - axis_node.setBin("gui-popup", 1001 + i) - axis_node.setDepthTest(False) - axis_node.setDepthWrite(False) - axis_node.setLightOff() - - except Exception as e: - print(f"设置坐标轴渲染失败!!!!!!: {str(e)}") - - def _setupRenderPipelineCompatibleGizmo(self): - """为 RenderPipeline 环境设置兼容的坐标轴渲染 - 激进修复版本""" - try: - from panda3d.core import (ShaderAttrib, MaterialAttrib, RenderModeAttrib, - AntialiasAttrib, TransparencyAttrib, CullFaceAttrib, - RescaleNormalAttrib, TextureAttrib) - - # 第一步:完全隔离坐标轴,避免被任何系统处理 - self._isolateGizmoFromRenderPipeline() - - # 第二步:使用最简单的渲染方式 - self._setupMinimalGizmoRendering() - - # 第三步:强制设置每个轴的独立渲染 - self._forceAxisIndependentRendering() - - - except Exception as e: - # 使用最后的备用方案 - self._setupUltimateGizmoFallback() - - def _isolateGizmoFromRenderPipeline(self): - """完全隔离坐标轴,避免被 RenderPipeline 处理""" - try: - # 设置所有可能的隔离标签 - isolation_tags = [ - ("no_shadow", "1"), - ("no_lighting", "1"), - ("no_material", "1"), - ("no_shader", "1"), - ("no_fog", "1"), - ("no_texture", "1"), - ("gui_element", "1"), - ("bypass_rp", "1"), - ("fixed_pipeline", "1"), - ("ignore_all", "1") - ] - - for tag, value in isolation_tags: - self.gizmo.setTag(tag, value) - - # 完全禁用所有高级功能,使用最高优先级 - self.gizmo.setShaderOff(10000) - self.gizmo.setLightOff(10000) - self.gizmo.setFogOff(10000) - self.gizmo.setMaterialOff(10000) - self.gizmo.setTextureOff(10000) - - # 禁用所有可能的渲染特性 - from panda3d.core import RenderModeAttrib, CullFaceAttrib - # self.gizmo.setRenderModeWireframe() - # self.gizmo.setTwoSided(True) - self.gizmo.setRenderMode(RenderModeAttrib.MFilled) - self.gizmo.setTwoSided(True) - - self.gizmo.setColorScale(2.0,2.0,2.0,1.0) - for axis_node in [self.gizmoXAxis,self.gizmoYAxis,self.gizmoZAxis]: - if axis_node: - axis_node.setTag("emissive","1") - axis_node.setTag("unlit","1") - axis_node.setColorScale(2.0,2.0,2.0,1.0) - - except Exception as e: - print(f" ❌ 隔离失败: {e}") - - def _setupMinimalGizmoRendering(self): - """设置最简单的渲染方式""" - try: - from panda3d.core import (ShaderAttrib, MaterialAttrib, TextureAttrib, - CullFaceAttrib, RescaleNormalAttrib) - - # 使用最高优先级的 GUI 渲染 bin - self.gizmo.setBin("gui-popup", 10000) - self.gizmo.setDepthTest(False) - self.gizmo.setDepthWrite(False) - - # 创建最简单的渲染状态 - minimal_state = RenderState.make( - ShaderAttrib.makeOff(10000), # 完全禁用着色器 - MaterialAttrib.makeOff(), # 禁用材质 - TextureAttrib.makeOff(), # 禁用纹理 - CullFaceAttrib.make(CullFaceAttrib.MCullNone), # 禁用面剔除 - RescaleNormalAttrib.makeOff() # 禁用法线重缩放 - ) - self.gizmo.setState(minimal_state, 10000) - - except Exception as e: - print(f" ❌ 最简渲染设置失败: {e}") - - def _forceAxisIndependentRendering(self): - """强制设置每个轴的独立渲染""" - try: - # 轴配置 - axis_configs = [ - (self.gizmoXAxis, "X轴", (1.0, 0.0, 0.0, 1.0), 0), - (self.gizmoYAxis, "Y轴", (0.0, 1.0, 0.0, 1.0), 0), - (self.gizmoZAxis, "Z轴", (0.0, 0.0, 1.0, 1.0), 0) - ] - - for axis_node, name, color, priority in axis_configs: - if axis_node: - # 每个轴都完全独立设置 - self._setupSingleAxisRendering(axis_node, name, color, 0) - - - except Exception as e: - print(f" ❌ 独立轴渲染设置失败: {e}") - - def _setupSingleAxisRendering(self, axis_node, name, color, priority): - """为单个轴设置完全独立的渲染""" - try: - from panda3d.core import (LVecBase4f, RenderState, ColorAttrib, - TransparencyAttrib, LColor, AntialiasAttrib, - RenderModeAttrib, CullFaceAttrib, AuxBitplaneAttrib, - LightRampAttrib) - - # 转换颜色为LColor并增加亮度 - base_color = LColor(*color) - emissive_color = LColor(base_color[0], base_color[1], base_color[2], 1.0) - - # 完全禁用所有高级渲染功能 - axis_node.clearShader() - axis_node.clearTexture() - axis_node.clearMaterial() - axis_node.setLightOff() - axis_node.setFogOff() - axis_node.setAttrib(RenderModeAttrib.make(RenderModeAttrib.MWireframe, 6.0)) - axis_node.setAttrib(AntialiasAttrib.make(AntialiasAttrib.MLine)) - axis_node.setBin("gui-popup", 0) - axis_node.setDepthTest(False) - axis_node.setDepthWrite(False) - axis_node.setTwoSided(True) - - # 强制设置自发光颜色 - axis_node.setColor(*color) - axis_node.setColorScale(1.0, 1.0, 1.0, 1.0) # 增加整体亮度 - - except Exception as e: - print(" ❌ {} 轴渲染设置失败: {}".format(name, str(e))) - raise e - - def _setupUltimateGizmoFallback(self): - """最后的备用方案 - 最简单的渲染""" - try: - # 最简单的设置 - self.gizmo.setLightOff() - self.gizmo.setFogOff() - self.gizmo.setBin("gui-popup", 20000) - self.gizmo.setDepthTest(False) - self.gizmo.setDepthWrite(False) - - # 直接设置颜色,不使用复杂的渲染状态 - if self.gizmoXAxis: - self.gizmoXAxis.setColor(1, 0, 0, 1) - self.gizmoXAxis.setLightOff() - self.gizmoXAxis.setBin("gui-popup", 20001) - self.gizmoXAxis.setDepthTest(False) - - if self.gizmoYAxis: - self.gizmoYAxis.setColor(0, 1, 0, 1) - self.gizmoYAxis.setLightOff() - self.gizmoYAxis.setBin("gui-popup", 20002) - self.gizmoYAxis.setDepthTest(False) - - if self.gizmoZAxis: - self.gizmoZAxis.setColor(0, 0, 1, 1) - self.gizmoZAxis.setLightOff() - self.gizmoZAxis.setBin("gui-popup", 20003) - self.gizmoZAxis.setDepthTest(False) - - except Exception as e: - print(f"❌ 最后备用方案也失败: {e}") + # 可见性状态:允许颜色写入,不写深度,双面可见,禁用雾 + node.setAttrib(ColorWriteAttrib.make(ColorWriteAttrib.C_all)) + node.setAttrib(DepthWriteAttrib.make(False)) + node.setTwoSided(True) + node.setFogOff() def updateGizmoTask(self, task): """坐标轴更新任务 - 包含固定大小功能""" @@ -791,34 +549,127 @@ class SelectionSystem: self.gizmoStartPos = None - def setGizmoAxisColor(self, axis, color): - """设置坐标轴颜色 - RenderPipeline 兼容版本""" - try: - from panda3d.core import AntialiasAttrib,TransparencyAttrib + # def setGizmoAxisColor(self, axis, color): + # """设置坐标轴颜色 - RenderPipeline 兼容版本""" + # try: + # from panda3d.core import AntialiasAttrib,TransparencyAttrib + # + # axis_nodes = { + # "x": self.gizmoXAxis, + # "y": self.gizmoYAxis, + # "z": self.gizmoZAxis + # } + # + # if axis in axis_nodes and axis_nodes[axis]: + # axis_node = axis_nodes[axis] + # + # axis_node.setColor(color[0]*20.0,color[1]*20.0,color[2]*20.0,color[3]) + # axis_node.setColorScale(color[0]*10.0,color[1]*10.0,color[2]*10.0,color[3]) + # axis_node.setShaderOff(10000) + # axis_node.setLightOff() + # axis_node.setMaterialOff() + # axis_node.setTextureOff() + # axis_node.setFogOff() + # + # except Exception as e: + # print(f"设置坐标轴颜色失败: {str(e)}") + # # 回退到简单的颜色设置 + # try: + # if axis in axis_nodes and axis_nodes[axis]: + # axis_nodes[axis].setColor(*color) + # except: + # pass + def setGizmoAxisColor(self, axis, color): + """使用材质设置坐标轴颜色 - RenderPipeline兼容版本""" + try: + from panda3d.core import Material, Vec4,ColorWriteAttrib,DepthWriteAttrib,DepthTestAttrib,TransparencyAttrib + + # 获取对应的轴节点 axis_nodes = { "x": self.gizmoXAxis, "y": self.gizmoYAxis, "z": self.gizmoZAxis } - if axis in axis_nodes and axis_nodes[axis]: - axis_node = axis_nodes[axis] + if axis not in axis_nodes or not axis_nodes[axis]: + return - axis_node.setColor(color[0]*20.0,color[1]*20.0,color[2]*20.0,color[3]) - axis_node.setColorScale(color[0]*20.0,color[1]*20.0,color[2]*20.0,color[3]) - axis_node.setShaderOff(10000) - axis_node.setLightOff(10000) - axis_node.setMaterialOff(10000) - axis_node.setTextureOff(1000) - axis_node.setFogOff(10000) + axis_node = axis_nodes[axis] + + # 查找箭头模型节点 + arrow_node = None + if axis == "x": + arrow_node = axis_node.find("x_arrow") + elif axis == "y": + arrow_node = axis_node.find("y_arrow") + elif axis == "z": + arrow_node = axis_node.find("z_arrow") + + if not arrow_node: + print(f"未找到{axis}轴的箭头模型") + return + + # 创建或获取材质 + mat = Material() + + # 设置材质属性 - 使用自发光确保在RenderPipeline下可见 + mat.setBaseColor(Vec4(color[0], color[1], color[2], color[3])) + mat.setDiffuse(Vec4(0, 0, 0, 1)) + #mat.setEmission(Vec4(color[0], color[1], color[2], 1.0)) # 自发光 + mat.setEmission(Vec4(1,1,1,1.0)) # 自发光 + mat.set_roughness(1) + + # 应用材质 + arrow_node.setMaterial(mat, 1) + + + # 设置透明度 + if color[3] < 1.0: + arrow_node.setTransparency(TransparencyAttrib.MAlpha) + else: + arrow_node.setTransparency(TransparencyAttrib.MNone) + + # 创建自定义渲染状态 - 确保始终在最前方 + # state = RenderState.make( + # ColorWriteAttrib.make(ColorWriteAttrib.CAll), # 允许颜色写入 + # DepthTestAttrib.make(DepthTestAttrib.MAlways), # 始终通过深度测试 + # DepthWriteAttrib.make(DepthWriteAttrib.MOff) # 不写入深度缓冲区 + # ) + # # 应用渲染状态 + # arrow_node.set_state(state) + + arrow_node.setLightOff() # 禁用光照影响 + arrow_node.setShaderOff() # 禁用着色器 + arrow_node.setFogOff() # 禁用雾效果 + arrow_node.setBin("fixed", 10) # 使用固定渲染顺序,确保在最前 + + # 保存材质引用以便后续修改 + if axis == "x": + self.xMat = mat + elif axis == "y": + self.yMat = mat + elif axis == "z": + self.zMat = mat + + axis_node.setLightOff() + axis_node.setShaderOff() + axis_node.setFogOff() + #axis_node.set_state(state) + axis_node.setBin("fixed", 9) # 确保轴节点在箭头模型之前渲染 except Exception as e: print(f"设置坐标轴颜色失败: {str(e)}") - # 回退到简单的颜色设置 + # 回退到简单颜色设置 try: + axis_nodes = { + "x": self.gizmoXAxis, + "y": self.gizmoYAxis, + "z": self.gizmoZAxis + } + if axis in axis_nodes and axis_nodes[axis]: - axis_nodes[axis].setColor(*color) + axis_nodes[axis].setColor(color[0], color[1], color[2], color[3]) except: pass diff --git a/ui/widgets.py b/ui/widgets.py index 9a1c5dc7..dcd1cc76 100644 --- a/ui/widgets.py +++ b/ui/widgets.py @@ -363,6 +363,7 @@ class CustomTreeWidget(QTreeWidget): # 更新属性面板 self.world.updatePropertyPanel(dragged_item) + self.world.property_panel._syncEffectiveVisibility(dragged_node) else: event.ignore() -- 2.45.2 From e4084c5bf64323350bc5e018926ca6db3557a6ca Mon Sep 17 00:00:00 2001 From: Hector <2055590199@qq.com> Date: Thu, 21 Aug 2025 10:44:59 +0800 Subject: [PATCH 5/5] =?UTF-8?q?1.=E5=9D=90=E6=A0=87=E8=BD=B4=E6=B7=B1?= =?UTF-8?q?=E5=BA=A6=E5=BE=85=E6=9B=B4=E6=94=B9=202.=E6=8B=96=E5=8A=A8?= =?UTF-8?q?=E5=90=8E=E8=8A=82=E7=82=B9=E5=8F=AF=E8=A7=81=E6=80=A7=E9=80=BB?= =?UTF-8?q?=E8=BE=91=E6=94=B9=E8=BF=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- RenderPipelineFile/config/daytime.yaml | 2 +- core/selection.py | 346 +++++++++++++------------ main.py | 6 +- ui/property_panel.py | 24 +- ui/widgets.py | 9 +- 5 files changed, 217 insertions(+), 170 deletions(-) diff --git a/RenderPipelineFile/config/daytime.yaml b/RenderPipelineFile/config/daytime.yaml index e67993cc..199088f8 100644 --- a/RenderPipelineFile/config/daytime.yaml +++ b/RenderPipelineFile/config/daytime.yaml @@ -17,7 +17,7 @@ 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.4416666667]]] + sun_azimuth: [[[0.5000000000,0.0000000000]]] sun_altitude: [[[0.5000000000,1.0000000000]]] extinction: [[[0.4913294798,0.6378830084]]] volumetrics: diff --git a/core/selection.py b/core/selection.py index ecbbe5c4..22ff8ef5 100644 --- a/core/selection.py +++ b/core/selection.py @@ -308,6 +308,13 @@ class SelectionSystem: self.setGizmoAxisColor("y", self.gizmo_colors["y"]) self.setGizmoAxisColor("z", self.gizmo_colors["z"]) + self._updateGizmoScreenSize() + + self._setupGizmoRendering() + + # 现在才显示坐标轴 + self.gizmo.show() + # 只启动一次更新任务 taskMgr.add(self.updateGizmoTask, "updateGizmo") @@ -363,38 +370,59 @@ class SelectionSystem: self.setGizmoAxisColor("y", self.gizmo_colors["y"]) self.setGizmoAxisColor("z", self.gizmo_colors["z"]) - # 使用最强的渲染设置,确保坐标轴绝对不会被遮挡 - # self.gizmo.setBin("default", 0) # 使用最高的GUI渲染层 - # self.gizmo.setDepthTest(True) # 完全禁用深度测试 - # self.gizmo.setDepthWrite(True) # 禁用深度写入 - # self.gizmo.setTwoSided(True) # 双面渲染 - - + #设置渲染属性,解决模型遮挡和阴影问题 + self._setupGizmoRendering() except Exception as e: print(f"创建坐标轴几何体失败: {str(e)}") - def _applyAxisMaterial(self, node, color): - from panda3d.core import Material, Vec4, ColorWriteAttrib, DepthWriteAttrib - # 构造一个“自发光”的材质,让颜色不依赖灯光/法线 - m = Material() - col = Vec4(*color) - m.setBaseColor(col) # 记录用 - m.setDiffuse(col) # 记录用(RP标准材质可能不读取) - m.setEmission(Vec4(col.x, col.y, col.z, 1.0)) # 关键:用发光通道显示颜色 - node.setMaterial(m, 1) + def _setupGizmoRendering(self): + try: + axis_nodes = [self.gizmoXAxis,self.gizmoYAxis,self.gizmoZAxis] - # 走能直接写到最终画面的阶段(两种方式二选一): - # 方式1:完全绕过管线(最稳) - node.setTag("pipeline-disable", "1") - # 方式2:留在管线内但走最终阶段(若不想禁用管线,可改用下面这一行,并去掉上一行) - # node.setTag("pipeline-stage", "final") + for axis_node in axis_nodes: + if axis_node: + #禁用光照和阴影 + axis_node.setLightOff() + axis_node.setShaderOff() + axis_node.setFogOff() + #设置渲染层级,确保大多数对象之前渲染 + axis_node.setBin("fixed",30) + axis_node.setDepthWrite(False) + axis_node.setDepthTest(False) + arrow_nodes = [] + if self.gizmoXAxis: + x_arrow = self.gizmoXAxis.find("x_arrow") + if x_arrow: + arrow_nodes.append(x_arrow) + if self.gizmoYAxis: + y_arrow = self.gizmoYAxis.find("y_arrow") + if y_arrow: + arrow_nodes.append(y_arrow) + if self.gizmoZAxis: + z_arrow = self.gizmoZAxis.find("z_arrow") + if z_arrow: + arrow_nodes.append(z_arrow) - # 可见性状态:允许颜色写入,不写深度,双面可见,禁用雾 - node.setAttrib(ColorWriteAttrib.make(ColorWriteAttrib.C_all)) - node.setAttrib(DepthWriteAttrib.make(False)) - node.setTwoSided(True) - node.setFogOff() + for arrow_node in arrow_nodes: + if arrow_node: + arrow_node.setLightOff() + arrow_node.setShaderOff() + arrow_node.setFogOff() + arrow_node.setBin("fixed",31) + arrow_node.setDepthWrite(False) + arrow_node.setDepthTest(False) + #启用透明度S + arrow_node.setTransparency(TransparencyAttrib.MAlpha) + if self.gizmo: + self.gizmo.setLightOff() + self.gizmo.setShaderOff() + self.gizmo.setFogOff() + self.gizmo.setBin("fixed",29) + self.gizmo.setDepthWrite(False) + self.gizmo.setDepthTest(False) + except Exception as e: + print(f"设置坐标轴渲染属性失败: {str(e)}") def updateGizmoTask(self, task): """坐标轴更新任务 - 包含固定大小功能""" @@ -429,25 +457,6 @@ class SelectionSystem: else: # 只在必要时更新位置和朝向 self._updateGizmoPositionAndOrientation() - # # 更新坐标轴位置,始终在目标节点中心 - # minPoint = Point3() - # maxPoint = Point3() - # if self.gizmoTarget.calcTightBounds(minPoint, maxPoint, self.world.render): - # # 计算中心点 - # center = Point3((minPoint.x + maxPoint.x) * 0.5, - # (minPoint.y + maxPoint.y) * 0.5, - # (minPoint.z + maxPoint.z) * 0.5) - # self.gizmo.setPos(center) - # - # # 【关键修复】:更新坐标轴朝向以跟踪父节点的变化 - # parent_node = self.gizmoTarget.getParent() - # if parent_node and parent_node != self.world.render: - # # 子节点:坐标轴朝向跟随父节点 - # parent_hpr = parent_node.getHpr() - # self.gizmo.setHpr(parent_hpr) - # else: - # # 顶级模型:使用世界坐标系朝向 - # self.gizmo.setHpr(0, 0, 0) # 【新功能】:动态调整坐标轴大小,保持固定的屏幕大小 self._updateGizmoScreenSize() @@ -517,8 +526,8 @@ class SelectionSystem: self.gizmo.setScale(scale_factor) # 限制缩放范围,避免过大或过小 - min_scale = 0.1 - max_scale = 10.0 + min_scale = 0.08 + max_scale = 100.0 final_scale = max(min_scale, min(max_scale, scale_factor)) if final_scale != scale_factor: @@ -630,19 +639,13 @@ class SelectionSystem: else: arrow_node.setTransparency(TransparencyAttrib.MNone) - # 创建自定义渲染状态 - 确保始终在最前方 - # state = RenderState.make( - # ColorWriteAttrib.make(ColorWriteAttrib.CAll), # 允许颜色写入 - # DepthTestAttrib.make(DepthTestAttrib.MAlways), # 始终通过深度测试 - # DepthWriteAttrib.make(DepthWriteAttrib.MOff) # 不写入深度缓冲区 - # ) - # # 应用渲染状态 - # arrow_node.set_state(state) - arrow_node.setLightOff() # 禁用光照影响 arrow_node.setShaderOff() # 禁用着色器 arrow_node.setFogOff() # 禁用雾效果 - arrow_node.setBin("fixed", 10) # 使用固定渲染顺序,确保在最前 + + arrow_node.setBin("fixed",31) + #arrow_node.setDepthWrite(False) + #arrow_node.setDepthTest(True) # 保存材质引用以便后续修改 if axis == "x": @@ -655,8 +658,9 @@ class SelectionSystem: axis_node.setLightOff() axis_node.setShaderOff() axis_node.setFogOff() - #axis_node.set_state(state) - axis_node.setBin("fixed", 9) # 确保轴节点在箭头模型之前渲染 + axis_node.setBin("fixed", 30) + #axis_node.setDepthWrite(False) + #axis_node.setDepthTest(True) except Exception as e: print(f"设置坐标轴颜色失败: {str(e)}") @@ -907,38 +911,10 @@ class SelectionSystem: # ==================== 高亮和交互 ==================== - def updateGizmoHighlight(self, mouseX, mouseY): - """更新坐标轴高亮状态""" - if not self.gizmo or self.isDraggingGizmo: - return - - import time - current_time = time.time() - if not hasattr(self,'_last_highlight_time'): - self._last_highlight_time = 0 - - if current_time - self._last_highlight_time<0.05: - return - self._last_highlight_time = current_time - - hoveredAxis = self._detectHoveredAxis(mouseX, mouseY) - - if not hasattr(self,'_hover_stability_counter'): - self._hover_stability_counter = {} - self._last_detected_axis = None - - if hoveredAxis !=self._last_detected_axis: - self._hover_stability_counter[hoveredAxis]=1 - self._last_detected_axis = hoveredAxis - else: - self._hover_stability_counter[hoveredAxis] = self._hover_stability_counter.get(hoveredAxis,0)+1 - - if self._hover_stability_counter.get(hoveredAxis,0)>=2: - if hoveredAxis != self.gizmoHighlightAxis: - self._updateAxisHighlight(hoveredAxis) - - # 检测鼠标悬停的轴(使用相同的检测逻辑但不输出调试信息) - hoveredAxis = None + def detectGizmoAxisAtMouse(self, mouseX, mouseY): + """统一的坐标轴检测方法 - 同时用于高亮和点击检测""" + if not self.gizmo or not self.gizmoTarget: + return None try: # 获取坐标轴中心的世界坐标 @@ -951,29 +927,29 @@ class SelectionSystem: # 将3D坐标投影到屏幕坐标 def worldToScreen(worldPos): - try: - # 转换为相机坐标系 - camPos = self.world.cam.getRelativePoint(self.world.render, worldPos) + try: + # 转换为相机坐标系 + camPos = self.world.cam.getRelativePoint(self.world.render, worldPos) - # 检查点是否在相机前方 - if camPos.getY() <= 0: - return None + # 检查点是否在相机前方 + if camPos.getY() <= 0: + return None - # 使用相机lens进行投影 - screenPos = Point2() - lens = self.world.cam.node().getLens() + # 使用相机lens进行投影 + screenPos = Point2() + lens = self.world.cam.node().getLens() - if lens.project(camPos, screenPos): - # 获取准确的窗口尺寸 - winWidth, winHeight = self.world.getWindowSize() + if lens.project(camPos, screenPos): + # 获取准确的窗口尺寸 + winWidth, winHeight = self.world.getWindowSize() - # 转换为像素坐标 - winX = (screenPos.x + 1) * 0.5 * winWidth - winY = (1 - screenPos.y) * 0.5 * winHeight - return (winX, winY) - return None - except: - return None + # 转换为像素坐标 + winX = (screenPos.x + 1) * 0.5 * winWidth + winY = (1 - screenPos.y) * 0.5 * winHeight + return (winX, winY) + return None + except: + return None # 获取各坐标轴的屏幕投影 gizmo_screen = worldToScreen(gizmo_world_pos) @@ -981,70 +957,90 @@ class SelectionSystem: y_screen = worldToScreen(y_end) z_screen = worldToScreen(z_end) - # 只要坐标轴中心在屏幕内,就进行检测 - if gizmo_screen: - click_threshold = 25 + # 如果坐标轴中心不在屏幕内,返回None + if not gizmo_screen: + return None - def isNearLine(mousePos, start, end, threshold): - import math - A = mousePos[1] - start[1] - B = start[0] - mousePos[0] - C = (end[1] - start[1]) * mousePos[0] + (start[0] - end[0]) * mousePos[1] + end[0] * start[1] - start[0] * end[1] + # 设置检测阈值 + click_threshold = 35 # 统一使用25像素的检测阈值 - length = math.sqrt((end[0] - start[0])**2 + (end[1] - start[1])**2) - if length == 0: - return False + # 更准确的点到线段距离计算方法 + def distanceToLineSegment(mousePos, start, end): + import math + mx, my = mousePos + x1, y1 = start + x2, y2 = end - distance = abs(C) / length - t = ((mousePos[0] - start[0]) * (end[0] - start[0]) + - (mousePos[1] - start[1]) * (end[1] - start[1])) / (length * length) + # 线段向量 + dx = x2 - x1 + dy = y2 - y1 - return distance < threshold and 0 <= t <= 1 + # 线段长度平方 + length_sq = dx * dx + dy * dy - mouse_pos = (mouseX, mouseY) + if length_sq == 0: + # 线段退化为点 + return math.sqrt((mx - x1) ** 2 + (my - y1) ** 2) - # 分别检测每个轴,为在屏幕外的轴端点提供替代方案 - # 按优先级检测轴(Z > X > Y) + # 投影参数 + t = max(0, min(1, ((mx - x1) * dx + (my - y1) * dy) / length_sq)) - # 对于轴端点在屏幕外的情况,使用较短的轴段进行检测 - def getAxisScreenPoint(axis_name, axis_screen_end): - if axis_screen_end: - return axis_screen_end - # 如果端点在屏幕外,使用轴长度的一半作为检测点 - if axis_name == "x": - half_end = gizmo_world_pos + Vec3(self.axis_length * 0.5, 0, 0) - elif axis_name == "y": - half_end = gizmo_world_pos + Vec3(0, self.axis_length * 0.5, 0) - elif axis_name == "z": - half_end = gizmo_world_pos + Vec3(0, 0, self.axis_length * 0.5) - return worldToScreen(half_end) + # 投影点坐标 + proj_x = x1 + t * dx + proj_y = y1 + t * dy - # 获取有效的检测点(优先使用完整轴,备用使用半轴) - z_detect_point = getAxisScreenPoint("z", z_screen) - x_detect_point = getAxisScreenPoint("x", x_screen) - y_detect_point = getAxisScreenPoint("y", y_screen) + # 返回点到投影点的距离 + return math.sqrt((mx - proj_x) ** 2 + (my - proj_y) ** 2) - if z_detect_point and isNearLine(mouse_pos, gizmo_screen, z_detect_point, click_threshold): - hoveredAxis = "z" - elif x_detect_point and isNearLine(mouse_pos, gizmo_screen, x_detect_point, click_threshold): - hoveredAxis = "x" - elif y_detect_point and isNearLine(mouse_pos, gizmo_screen, y_detect_point, click_threshold): - hoveredAxis = "y" + mouse_pos = (mouseX, mouseY) + + # 检测各个轴 - 按优先级检测(Z > X > Y) + axes_to_check = [ + ("z", z_screen), + ("x", x_screen), + ("y", y_screen) + ] + + for axis_name, axis_end in axes_to_check: + if axis_end: + distance = distanceToLineSegment(mouse_pos, gizmo_screen, axis_end) + if distance < click_threshold: + return axis_name + + # 如果没有检测到,返回None + return None except Exception as e: - pass # 静默处理错误,避免频繁输出 + # 静默处理错误,避免频繁输出 + return None - # 如果高亮状态发生变化 - if hoveredAxis != self.gizmoHighlightAxis: - # 恢复之前高亮的轴 - if self.gizmoHighlightAxis: - self.setGizmoAxisColor(self.gizmoHighlightAxis, self.gizmo_colors[self.gizmoHighlightAxis]) + def updateGizmoHighlight(self, mouseX, mouseY): + """更新坐标轴高亮状态""" + if not self.gizmo or self.isDraggingGizmo: + return - # 高亮新的轴 - if hoveredAxis: - self.setGizmoAxisColor(hoveredAxis, self.gizmo_highlight_colors[hoveredAxis]) + # 使用统一的检测方法 + hoveredAxis = self.detectGizmoAxisAtMouse(mouseX, mouseY) - self.gizmoHighlightAxis = hoveredAxis + # 简化稳定性检测逻辑 + if not hasattr(self, '_last_detected_axis'): + self._last_detected_axis = None + + # 如果检测结果发生变化,立即更新高亮 + if hoveredAxis != self._last_detected_axis: + # 更新轴的高亮状态 + if hoveredAxis != self.gizmoHighlightAxis: + # 恢复之前高亮的轴 + if self.gizmoHighlightAxis: + self.setGizmoAxisColor(self.gizmoHighlightAxis, self.gizmo_colors[self.gizmoHighlightAxis]) + + # 高亮新的轴 + if hoveredAxis: + self.setGizmoAxisColor(hoveredAxis, self.gizmo_highlight_colors[hoveredAxis]) + + self.gizmoHighlightAxis = hoveredAxis + + self._last_detected_axis = hoveredAxis def _detectHoveredAxis(self, mouseX, mouseY): """检测鼠标悬停的轴 - 提取为独立方法""" @@ -1078,14 +1074,36 @@ class SelectionSystem: return self.isDraggingGizmo = True - self.dragGizmoAxis = axis + # 使用当前高亮的轴,如果有的话 + if self.gizmoHighlightAxis: + self.dragGizmoAxis = self.gizmoHighlightAxis + else: + self.dragGizmoAxis = axis self.dragStartMousePos = (mouseX, mouseY) # 保存开始拖拽时目标节点的位置和坐标轴的位置 self.gizmoTargetStartPos = self.gizmoTarget.getPos() self.gizmoStartPos = self.gizmo.getPos(self.world.render) # 坐标轴的世界位置 - print(f"开始拖拽 {axis} 轴 - 目标起始位置: {self.gizmoTargetStartPos}, 坐标轴位置: {self.gizmoStartPos}, 鼠标: ({mouseX}, {mouseY})") + # 确保正在拖动的轴保持高亮状态 + if self.dragGizmoAxis and self.dragGizmoAxis in self.gizmo_colors: + # 先将所有轴恢复为正常颜色 + for axis_name in self.gizmo_colors.keys(): + if axis_name != self.dragGizmoAxis: + self.setGizmoAxisColor(axis_name, self.gizmo_colors[axis_name]) + + # 然后将当前拖动的轴设置为高亮颜色 + self.setGizmoAxisColor(self.dragGizmoAxis, self.gizmo_highlight_colors[self.dragGizmoAxis]) + self.gizmoHighlightAxis = self.dragGizmoAxis + elif axis and axis in self.gizmo_colors: + for axis_name in self.gizmo_colors.keys(): + self.setGizmoAxisColor(axis_name, self.gizmo_colors[axis_name]) + + self.setGizmoAxisColor(axis, self.gizmo_highlight_colors[axis]) + self.gizmoHighlightAxis = axis + + print( + f"开始拖拽 {self.dragGizmoAxis} 轴 - 目标起始位置: {self.gizmoTargetStartPos}, 坐标轴位置: {self.gizmoStartPos}, 鼠标: ({mouseX}, {mouseY})") except Exception as e: print(f"开始坐标轴拖拽失败: {str(e)}") @@ -1311,6 +1329,10 @@ class SelectionSystem: def stopGizmoDrag(self): """停止坐标轴拖拽""" print(f"停止坐标轴拖拽 - 轴: {self.dragGizmoAxis}") + if self.dragGizmoAxis and self.dragGizmoAxis in self.gizmo_colors: + self.setGizmoAxisColor(self.dragGizmoAxis, self.gizmo_colors[self.dragGizmoAxis]) + # 不要将 gizmoHighlightAxis 设置为 None,保持当前高亮轴的状态 + # self.gizmoHighlightAxis = None self.isDraggingGizmo = False self.dragGizmoAxis = None diff --git a/main.py b/main.py index 3329c223..065ef800 100644 --- a/main.py +++ b/main.py @@ -369,9 +369,6 @@ class MyWorld(CoreWorld): def updatePropertyPanel(self, item): """更新属性面板显示 - 代理到property_panel""" return self.property_panel.updatePropertyPanel(item) - - # def addAnimationPanel(self,originmodel,filepath): - # return self.property_panel._addAnimationPanel(originmodel,filepath) def updateGUIPropertyPanel(self, gui_element): """更新GUI元素属性面板 - 代理到property_panel""" @@ -380,6 +377,9 @@ class MyWorld(CoreWorld): def removeActorForModel(self,model): return self.property_panel.removeActorForModel( model) + def updateNodeVisibilityAfterDrag(self,item): + return self.property_panel.updateNodeVisibilityAfterDrag(item) + # ==================== 工具管理代理 ==================== def setCurrentTool(self, tool): diff --git a/ui/property_panel.py b/ui/property_panel.py index 8b6382a7..eb13630f 100644 --- a/ui/property_panel.py +++ b/ui/property_panel.py @@ -51,6 +51,16 @@ class PropertyPanelManager: if item.widget(): item.widget().deleteLater() + def updateNodeVisibilityAfterDrag(self, item): + """拖拽结束后更新节点的可见性状态""" + node = item.data(0, Qt.UserRole) + if not node: + return + + # 当节点被拖拽后,需要根据新父节点的状态来更新可见性 + self._syncEffectiveVisibility(node) + + def updatePropertyPanel(self, item): """更新属性面板显示""" if not self._propertyLayout or not self._propertyLayout.parent(): @@ -133,7 +143,17 @@ class PropertyPanelManager: def _syncEffectiveVisibility(self, start_node): """广度优先,确保父隐藏则子一定隐藏""" - q = deque([(start_node, True)]) # (node, parent_effective_visible) + # 获取起始节点的父节点 + parent_node = start_node.getParent() + + # 确定父节点的有效可见性 + parent_effective_visible = True + if parent_node: + parent_effective_visible = parent_node.getPythonTag("effective_visible") + if parent_effective_visible is None: + parent_effective_visible = True + + q = deque([(start_node, parent_effective_visible)]) # (node, parent_effective_visible) while q: node, parent_eff = q.popleft() @@ -143,7 +163,7 @@ class PropertyPanelManager: eff = parent_eff and user node.setPythonTag("effective_visible", eff) - #特殊处理:检查是否为碰撞体节点 + # 特殊处理:检查是否为碰撞体节点 if node.getName().startswith("modelCollision_"): node.hide() diff --git a/ui/widgets.py b/ui/widgets.py index dcd1cc76..625c4b66 100644 --- a/ui/widgets.py +++ b/ui/widgets.py @@ -339,6 +339,7 @@ class CustomTreeWidget(QTreeWidget): # 获取节点引用 dragged_node = dragged_item.data(0, Qt.UserRole) + amtarget_node = target_item.data(0, Qt.UserRole) # 如果目标是模型根节点,使用 render 作为新父节点 if target_item.text(0) == "模型": @@ -360,13 +361,17 @@ class CustomTreeWidget(QTreeWidget): # 接受拖放事件,更新树形控件 super().dropEvent(event) - + + #self.world.property_panel.updateNodeVisibilityAfterDrag(dragged_item) # 更新属性面板 self.world.updatePropertyPanel(dragged_item) self.world.property_panel._syncEffectiveVisibility(dragged_node) + + + else: event.ignore() - + def isValidParentChild(self, dragged_item, target_item): """检查是否是有效的父子关系""" # 不能拖放到自己上 -- 2.45.2