forked from Rowland/EG
8.28合并
This commit is contained in:
parent
95395bdb05
commit
d980330d9d
@ -53,7 +53,7 @@ class Panda3DWorld(ShowBase):
|
||||
Panda3DWorld : A class to handle all panda3D world manipulation
|
||||
"""
|
||||
|
||||
def __init__(self, width=1380, height=729, is_fullscreen=False, size=1.0, clear_color=LVecBase4f(0, 0.5, 0, 1),
|
||||
def __init__(self, width=1380, height=750, is_fullscreen=False, size=1.0, clear_color=LVecBase4f(0, 0.5, 0, 1),
|
||||
name="qpanda3D"):
|
||||
|
||||
global _global_world_instance
|
||||
|
||||
@ -114,7 +114,7 @@ class EventHandler:
|
||||
def mousePressEventLeft(self, evt):
|
||||
"""处理鼠标左键按下事件"""
|
||||
print("\n=== 开始处理鼠标左键事件 ===")
|
||||
print(f"当前工具: {self.world.currentTool}")
|
||||
#print(f"当前工具: {self.world.currentTool}")
|
||||
|
||||
if not evt:
|
||||
print("事件为空")
|
||||
@ -126,7 +126,7 @@ class EventHandler:
|
||||
# 获取鼠标点击的位置
|
||||
x = evt.get('x', 0)
|
||||
y = evt.get('y', 0)
|
||||
print(f"鼠标点击位置: ({x}, {y})")
|
||||
#print(f"鼠标点击位置: ({x}, {y})")
|
||||
|
||||
# 获取准确的窗口尺寸
|
||||
winWidth, winHeight = self.world.getWindowSize()
|
||||
@ -134,20 +134,20 @@ class EventHandler:
|
||||
# 直接使用 x, y 创建鼠标位置
|
||||
mx = 2.0 * x / float(winWidth) - 1.0
|
||||
my = 1.0 - 2.0 * y / float(winHeight)
|
||||
print(f"转换后的坐标: ({mx}, {my})")
|
||||
#print(f"转换后的坐标: ({mx}, {my})")
|
||||
|
||||
# 创建射线
|
||||
nearPoint = Point3()
|
||||
farPoint = Point3()
|
||||
self.world.cam.node().getLens().extrude(Point2(mx, my), nearPoint, farPoint)
|
||||
print(f"相机坐标系射线起点: {nearPoint}")
|
||||
print(f"相机坐标系射线终点: {farPoint}")
|
||||
#print(f"相机坐标系射线起点: {nearPoint}")
|
||||
#print(f"相机坐标系射线终点: {farPoint}")
|
||||
|
||||
# 将相机坐标系的点转换到世界坐标系
|
||||
worldNearPoint = self.world.render.getRelativePoint(self.world.cam, nearPoint)
|
||||
worldFarPoint = self.world.render.getRelativePoint(self.world.cam, farPoint)
|
||||
print(f"世界坐标系射线起点: {worldNearPoint}")
|
||||
print(f"世界坐标系射线终点: {worldFarPoint}")
|
||||
#print(f"世界坐标系射线起点: {worldNearPoint}")
|
||||
#print(f"世界坐标系射线终点: {worldFarPoint}")
|
||||
|
||||
# 进行射线检测
|
||||
picker = CollisionTraverser()
|
||||
|
||||
@ -63,7 +63,49 @@ class SelectionSystem:
|
||||
"z": (1.0, 1.0, 0.0, 1.0) # 黄色高亮
|
||||
}
|
||||
|
||||
self._current_cursor = None
|
||||
self._default_cursor = None
|
||||
|
||||
print("✓ 选择和变换系统初始化完成")
|
||||
# ==================== 光标设置 ====================
|
||||
def _setCursor(self,cursor_type):
|
||||
try:
|
||||
from PyQt5.QtCore import Qt
|
||||
if self._current_cursor == cursor_type:
|
||||
return
|
||||
if hasattr(self.world,'main_window') and self.world.main_window:
|
||||
main_window = self.world.main_window
|
||||
else:
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
main_window = QApplication.activeWindow()
|
||||
if not main_window:
|
||||
windows = QApplication.topLevelWindows()
|
||||
for window in windows:
|
||||
if hasattr(window,'isVisible') and window.isVisible():
|
||||
main_window = window
|
||||
break
|
||||
if main_window:
|
||||
if cursor_type == "crosshair":
|
||||
main_window.setCursor(Qt.CrossCursor)
|
||||
elif cursor_type == "size_hor":
|
||||
main_window.setCursor(Qt.SizeHorCursor)
|
||||
elif cursor_type == "size_ver":
|
||||
main_window.setCursor(Qt.SizeVerCursor)
|
||||
elif cursor_type == "size_all":
|
||||
main_window.setCursor(Qt.SizeAllCursor)
|
||||
elif cursor_type == "pointing_hand":
|
||||
main_window.setCursor(Qt.PointingHandCursor)
|
||||
else:
|
||||
main_window.unsetCursor()
|
||||
self._current_cursor = cursor_type
|
||||
print(f"光标已设置:{cursor_type}")
|
||||
self._current_cursor = cursor_type
|
||||
else:
|
||||
print("警告:无法获取主窗口,光标设置失败")
|
||||
except Exception as e:
|
||||
print(f"设置光标失败{e}")
|
||||
def _resetCursor(self):
|
||||
self._setCursor("default")
|
||||
|
||||
# ==================== 选择框系统 ====================
|
||||
|
||||
@ -108,6 +150,26 @@ class SelectionSystem:
|
||||
if not self.selectionBox or not self.selectionBoxTarget:
|
||||
return
|
||||
|
||||
if self.selectionBoxTarget.isEmpty():
|
||||
return
|
||||
|
||||
minPoint = Point3()
|
||||
maxPoint = Point3()
|
||||
|
||||
try:
|
||||
has_bounds = self.selectionBoxTarget.calcTightBounds(minPoint, maxPoint, self.world.render)
|
||||
if not has_bounds:
|
||||
return
|
||||
except:
|
||||
return
|
||||
|
||||
# 检查边界框的有效性
|
||||
if (minPoint.x > maxPoint.x or minPoint.y > maxPoint.y or minPoint.z > maxPoint.z or
|
||||
abs(minPoint.x) > 1e10 or abs(minPoint.y) > 1e10 or abs(minPoint.z) > 1e10 or
|
||||
abs(maxPoint.x) > 1e10 or abs(maxPoint.y) > 1e10 or abs(maxPoint.z) > 1e10):
|
||||
print("警告: 检测到无效的边界框,跳过选择框更新")
|
||||
return
|
||||
|
||||
# 检查是否需要重新计算边界框
|
||||
if not hasattr(self, '_bounds_cache'):
|
||||
self._bounds_cache = {}
|
||||
@ -471,8 +533,8 @@ class SelectionSystem:
|
||||
self.gizmo.setShaderOff() # 禁用着色器
|
||||
self.gizmo.setFogOff() # 禁用雾效
|
||||
self.gizmo.setBin("fixed", 40) # 设置为fixed渲染层级,数值越大越优先
|
||||
self.gizmo.setDepthWrite(False) # 禁用深度写入
|
||||
self.gizmo.setDepthTest(False) # 禁用深度测试,确保始终可见
|
||||
#self.gizmo.setDepthWrite(False) # 禁用深度写入
|
||||
#self.gizmo.setDepthTest(False) # 禁用深度测试,确保始终可见
|
||||
|
||||
# 设置各轴节点的渲染属性
|
||||
for axis_node in axis_nodes:
|
||||
@ -481,8 +543,8 @@ class SelectionSystem:
|
||||
axis_node.setShaderOff()
|
||||
axis_node.setFogOff()
|
||||
axis_node.setBin("fixed", 40) # 与主节点相同优先级
|
||||
axis_node.setDepthWrite(False) # 禁用深度写入
|
||||
axis_node.setDepthTest(False) # 禁用深度测试
|
||||
#axis_node.setDepthWrite(False) # 禁用深度写入
|
||||
#axis_node.setDepthTest(False) # 禁用深度测试
|
||||
|
||||
# 设置旋转轴节点的渲染属性
|
||||
for axis_rotnode in axis_Rotnodes:
|
||||
@ -491,8 +553,8 @@ class SelectionSystem:
|
||||
axis_rotnode.setShaderOff()
|
||||
axis_rotnode.setFogOff()
|
||||
axis_rotnode.setBin("fixed", 40) # 与主节点相同优先级
|
||||
axis_rotnode.setDepthWrite(False) # 禁用深度写入
|
||||
axis_rotnode.setDepthTest(False) # 禁用深度测试
|
||||
#axis_rotnode.setDepthWrite(False) # 禁用深度写入
|
||||
#axis_rotnode.setDepthTest(False) # 禁用深度测试
|
||||
|
||||
# 收集所有handle节点
|
||||
arrow_nodes = []
|
||||
@ -530,8 +592,8 @@ class SelectionSystem:
|
||||
arrow_node.setShaderOff()
|
||||
arrow_node.setFogOff()
|
||||
arrow_node.setBin("fixed", 41) # 略高于主节点,确保最优先显示
|
||||
arrow_node.setDepthWrite(False)
|
||||
arrow_node.setDepthTest(False)
|
||||
#arrow_node.setDepthWrite(False)
|
||||
#arrow_node.setDepthTest(False)
|
||||
# 启用透明度支持
|
||||
arrow_node.setTransparency(TransparencyAttrib.MAlpha)
|
||||
|
||||
@ -541,8 +603,8 @@ class SelectionSystem:
|
||||
rot_node.setShaderOff()
|
||||
rot_node.setFogOff()
|
||||
rot_node.setBin("fixed", 41) # 略高于主节点,确保最优先显示
|
||||
rot_node.setDepthWrite(False)
|
||||
rot_node.setDepthTest(False)
|
||||
#rot_node.setDepthWrite(False)
|
||||
#rot_node.setDepthTest(False)
|
||||
# 启用透明度支持
|
||||
rot_node.setTransparency(TransparencyAttrib.MAlpha)
|
||||
|
||||
@ -640,12 +702,25 @@ class SelectionSystem:
|
||||
if current_time - self._last_gizmo_bounds_update > 0.2: # 每0.2秒计算一次边界框
|
||||
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)
|
||||
# 添加异常处理
|
||||
try:
|
||||
if self.gizmoTarget.calcTightBounds(minPoint, maxPoint, self.world.render):
|
||||
# 检查边界框的有效性
|
||||
if (abs(minPoint.x) < 1e10 and abs(minPoint.y) < 1e10 and abs(minPoint.z) < 1e10 and
|
||||
abs(maxPoint.x) < 1e10 and abs(maxPoint.y) < 1e10 and abs(maxPoint.z) < 1e10):
|
||||
# 计算中心点
|
||||
center = Point3((minPoint.x + maxPoint.x) * 0.5,
|
||||
(minPoint.y + maxPoint.y) * 0.5,
|
||||
(minPoint.z + maxPoint.z) * 0.5)
|
||||
self.gizmo.setPos(center)
|
||||
except Exception as e:
|
||||
print(f"更新Gizmo位置时出错: {e}")
|
||||
# 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)
|
||||
self._last_gizmo_bounds_update = current_time
|
||||
|
||||
is_scale_tool = self.world.tool_manager.isScaleTool() if self.world.tool_manager else False
|
||||
@ -733,6 +808,7 @@ class SelectionSystem:
|
||||
self.dragStartMousePos = None
|
||||
self.gizmoTargetStartPos = None
|
||||
self.gizmoStartPos = None
|
||||
self._resetCursor()
|
||||
|
||||
|
||||
# def setGizmoAxisColor(self, axis, color):
|
||||
@ -839,8 +915,8 @@ class SelectionSystem:
|
||||
handle_node.setFogOff() # 禁用雾效果
|
||||
|
||||
handle_node.setBin("fixed",41)
|
||||
handle_node.setDepthWrite(False)
|
||||
handle_node.setDepthTest(False)
|
||||
#handle_node.setDepthWrite(False)
|
||||
#handle_node.setDepthTest(False)
|
||||
|
||||
# 保存材质引用以便后续修改
|
||||
if axis == "x":
|
||||
@ -854,8 +930,8 @@ class SelectionSystem:
|
||||
axis_node.setShaderOff()
|
||||
axis_node.setFogOff()
|
||||
axis_node.setBin("fixed", 40)
|
||||
axis_node.setDepthWrite(False)
|
||||
axis_node.setDepthTest(False)
|
||||
#axis_node.setDepthWrite(False)
|
||||
#axis_node.setDepthTest(False)
|
||||
|
||||
except Exception as e:
|
||||
print(f"设置坐标轴颜色失败: {str(e)}")
|
||||
@ -889,15 +965,18 @@ class SelectionSystem:
|
||||
|
||||
axis_node = axis_nodes[axis]
|
||||
|
||||
if axis_node.isEmpty():
|
||||
return
|
||||
|
||||
handle_node = None
|
||||
handle_node = axis_node.find("x_handle") if axis == "x" else handle_node
|
||||
handle_node = axis_node.find("y_handle") if axis == "y" else handle_node
|
||||
handle_node = axis_node.find("z_handle") if axis == "z" else handle_node
|
||||
|
||||
#如果找不到特定名称的节点,尝试查找任何子节点
|
||||
if not handle_node:
|
||||
# 如果找不到特定名称的节点,尝试查找任何子节点
|
||||
if not handle_node or handle_node.isEmpty():
|
||||
children = axis_node.getChildren()
|
||||
if children.getNumPath()>0:
|
||||
if children.getNumPaths() > 0:
|
||||
handle_node = children[0]
|
||||
|
||||
if not handle_node:
|
||||
@ -907,15 +986,6 @@ class SelectionSystem:
|
||||
# 创建或获取材质
|
||||
mat = Material()
|
||||
|
||||
# # 设置材质属性 - 使用自发光确保在RenderPipeline下可见
|
||||
# mat.setBaseColor(Vec4(color[0], color[1], color[2], color[3]))
|
||||
# mat.setDiffuse(Vec4(0, 0, 0, 1))
|
||||
# #mat.setEmission(Vec4(color[0], color[1], color[2], 1.0)) # 自发光
|
||||
# mat.setEmission(Vec4(1,1,1,1.0)) # 自发光
|
||||
# mat.set_roughness(1)
|
||||
|
||||
# 设置材质属性 - 使用更自然的颜色,避免过亮的自发光
|
||||
# 将颜色值控制在合理范围内
|
||||
adjusted_color = Vec4(
|
||||
min(color[0], 1.0),
|
||||
min(color[1], 1.0),
|
||||
@ -924,16 +994,11 @@ class SelectionSystem:
|
||||
)
|
||||
|
||||
mat.setBaseColor(adjusted_color)
|
||||
#mat.setDiffuse(adjusted_color * 1) # 稍微降低漫反射亮度
|
||||
#mat.setAmbient(adjusted_color * 0.4) # 设置环境光反射
|
||||
# mat.setSpecular(Vec4(0.4, 0.4, 0.4, 1.0)) # 适度的镜面反射
|
||||
# mat.setShininess(1.0) # 适中的高光强度
|
||||
mat.setEmission(Vec4(1, 1, 1, 1.0)) # 自发光
|
||||
|
||||
# 应用材质
|
||||
handle_node.setMaterial(mat, 1)
|
||||
|
||||
|
||||
# 设置透明度
|
||||
if color[3] < 1.0:
|
||||
handle_node.setTransparency(TransparencyAttrib.MAlpha)
|
||||
@ -945,8 +1010,8 @@ class SelectionSystem:
|
||||
handle_node.setFogOff() # 禁用雾效果
|
||||
|
||||
handle_node.setBin("fixed",41)
|
||||
handle_node.setDepthWrite(False)
|
||||
handle_node.setDepthTest(False)
|
||||
#handle_node.setDepthWrite(False)
|
||||
#handle_node.setDepthTest(False)
|
||||
|
||||
# 保存材质引用以便后续修改
|
||||
if axis == "x":
|
||||
@ -960,8 +1025,8 @@ class SelectionSystem:
|
||||
axis_node.setShaderOff()
|
||||
axis_node.setFogOff()
|
||||
axis_node.setBin("fixed", 40)
|
||||
axis_node.setDepthWrite(False)
|
||||
axis_node.setDepthTest(False)
|
||||
#axis_node.setDepthWrite(False)
|
||||
#axis_node.setDepthTest(False)
|
||||
|
||||
except Exception as e:
|
||||
print(f"设置坐标轴颜色失败: {str(e)}")
|
||||
@ -1336,6 +1401,7 @@ class SelectionSystem:
|
||||
def updateGizmoHighlight(self, mouseX, mouseY):
|
||||
"""更新坐标轴高亮状态"""
|
||||
if not self.gizmo or self.isDraggingGizmo:
|
||||
self._resetCursor()
|
||||
return
|
||||
|
||||
# 使用碰撞检测方法
|
||||
@ -1357,15 +1423,19 @@ class SelectionSystem:
|
||||
# 高亮新的轴
|
||||
if hoveredAxis:
|
||||
self.setGizmoAxisColor(hoveredAxis, self.gizmo_highlight_colors[hoveredAxis])
|
||||
self._setCursor("pointing_hand")
|
||||
else:
|
||||
# 如果没有悬停在任何轴上,确保所有轴都恢复原始颜色
|
||||
for axis_name in ["x", "y", "z"]:
|
||||
if axis_name != self.dragGizmoAxis: # 不要改变正在拖拽的轴的颜色
|
||||
self.setGizmoAxisColor(axis_name, self.gizmo_colors[axis_name])
|
||||
self._resetCursor()
|
||||
|
||||
self.gizmoHighlightAxis = hoveredAxis
|
||||
|
||||
self._last_detected_axis = hoveredAxis
|
||||
elif hoveredAxis is None:
|
||||
self._resetCursor()
|
||||
|
||||
def _detectHoveredAxis(self, mouseX, mouseY):
|
||||
"""检测鼠标悬停的轴 - 提取为独立方法"""
|
||||
@ -1445,6 +1515,13 @@ class SelectionSystem:
|
||||
# self.dragGizmoAxis = axis
|
||||
#
|
||||
# self.gizmoHighlightAxis = self.dragGizmoAxis
|
||||
# 设置拖拽光标
|
||||
if self.dragGizmoAxis == "x":
|
||||
self._setCursor("size_all") # 水平调整光标
|
||||
elif self.dragGizmoAxis == "y":
|
||||
self._setCursor("size_all") # 垂直调整光标
|
||||
elif self.dragGizmoAxis == "z":
|
||||
self._setCursor("size_all") # 全向调整光标
|
||||
|
||||
print(
|
||||
f"开始拖拽 {self.dragGizmoAxis} 轴 - 目标起始位置: {self.gizmoTargetStartPos}, 坐标轴位置: {self.gizmoStartPos}, 鼠标: ({mouseX}, {mouseY})")
|
||||
@ -1487,6 +1564,9 @@ class SelectionSystem:
|
||||
|
||||
if is_scale_tool:
|
||||
scale_factor = 1.0 + (mouseDeltaX + mouseDeltaY) * 0.01
|
||||
|
||||
scale_factor = max(0.001, scale_factor)
|
||||
|
||||
start_scale = getattr(self,'gizmoTargetStartScale',Vec3(1,1,1))
|
||||
|
||||
if is_gui_element:
|
||||
@ -1514,6 +1594,12 @@ class SelectionSystem:
|
||||
start_scale.y * scale_factor,
|
||||
start_scale.z * scale_factor)
|
||||
|
||||
new_scale = Vec3(
|
||||
max(0.001,new_scale.x),
|
||||
max(0.001,new_scale.y),
|
||||
max(0.001,new_scale.z)
|
||||
)
|
||||
|
||||
# 应用新缩放值
|
||||
self.gizmoTarget.setScale(new_scale)
|
||||
# 安全地更新属性面板
|
||||
@ -1776,6 +1862,7 @@ class SelectionSystem:
|
||||
|
||||
# 重置高亮轴
|
||||
self.gizmoHighlightAxis = None
|
||||
self._resetCursor()
|
||||
# ==================== 选择管理 ====================
|
||||
|
||||
def updateSelection(self, nodePath):
|
||||
|
||||
@ -390,9 +390,6 @@ class TerrainManager:
|
||||
print(f"✓ 地形高度已修改: 位置({x}, {y}), 半径{radius}, 操作{operation}")
|
||||
print(f"✓ 重新设置了地形碰撞体")
|
||||
|
||||
# 更新场景树
|
||||
if hasattr(self.world, 'scene_manager') and hasattr(self.world.scene_manager, 'updateSceneTree'):
|
||||
self.world.scene_manager.updateSceneTree()
|
||||
|
||||
return modified
|
||||
|
||||
|
||||
@ -399,14 +399,14 @@ class CoreWorld(Panda3DWorld):
|
||||
|
||||
def mousePressEventRight(self, evt):
|
||||
"""处理鼠标右键按下事件"""
|
||||
print("右键按下")
|
||||
#print("右键按下")
|
||||
self.mouseRightPressed = True
|
||||
self.lastMouseX = evt['x']
|
||||
self.lastMouseY = evt['y']
|
||||
|
||||
def mouseReleaseEventRight(self, evt):
|
||||
"""处理鼠标右键释放事件"""
|
||||
print("右键释放")
|
||||
#print("右键释放")
|
||||
self.mouseRightPressed = False
|
||||
|
||||
def mouseMoveEvent(self, evt):
|
||||
|
||||
@ -24,6 +24,78 @@ except ImportError:
|
||||
WEB_ENGINE_AVAILABLE = False
|
||||
print("⚠️ QtWebEngineWidgets 不可用,Cesium 集成功能将被禁用")
|
||||
|
||||
def createGUI3DImage(self, pos=(0, 0, 0), image_path=None, size=1.0):
|
||||
from panda3d.core import CardMaker, Material, LColor,TransparencyAttrib
|
||||
|
||||
# 参数类型检查和转换
|
||||
if isinstance(size, (list, tuple)):
|
||||
if len(size) >= 2:
|
||||
x_size, y_size = float(size[0]), float(size[1])
|
||||
else:
|
||||
x_size = y_size = float(size[0]) if size else 1.0
|
||||
else:
|
||||
x_size = y_size = float(size)
|
||||
|
||||
# 创建卡片
|
||||
cm = CardMaker('gui_3d_image')
|
||||
cm.setFrame(-x_size/2, x_size/2, -y_size/2, y_size/2)
|
||||
|
||||
# 创建3D图像节点
|
||||
image_node = self.world.render.attachNewNode(cm.generate())
|
||||
image_node.setPos(*pos)
|
||||
|
||||
# 为3D图像创建独立的材质
|
||||
material = Material(f"image-material-{len(self.gui_elements)}")
|
||||
material.setBaseColor(LColor(1, 1, 1, 1))
|
||||
material.setDiffuse(LColor(1, 1, 1, 1))
|
||||
material.setAmbient(LColor(0.5, 0.5, 0.5, 1))
|
||||
material.setSpecular(LColor(0.1, 0.1, 0.1, 1.0))
|
||||
material.setShininess(10.0)
|
||||
material.setEmission(LColor(0, 0, 0, 1)) # 无自发光
|
||||
image_node.setMaterial(material, 1)
|
||||
|
||||
image_node.setTransparency(TransparencyAttrib.MAlpha)
|
||||
|
||||
# 如果提供了图像路径,则加载纹理
|
||||
if image_path:
|
||||
self.update3DImageTexture(image_node, image_path)
|
||||
|
||||
# 应用PBR效果(如果可用)
|
||||
try:
|
||||
if hasattr(self, 'render_pipeline') and self.render_pipeline:
|
||||
self.render_pipeline.set_effect(
|
||||
image_node,
|
||||
"effects/default.yaml",
|
||||
{
|
||||
"normal_mapping": True,
|
||||
"render_gbuffer": True,
|
||||
"alpha_testing": False,
|
||||
"parallax_mapping": False,
|
||||
"render_shadow": False,
|
||||
"render_envmap": True,
|
||||
"disable_children_effects": True
|
||||
},
|
||||
50
|
||||
)
|
||||
print("✓ GUI 3D图像PBR效果已应用")
|
||||
except Exception as e:
|
||||
print(f"⚠️ GUI 3D图像PBR效果应用失败: {e}")
|
||||
|
||||
# 为GUI元素添加标识(效仿3D文本方法)
|
||||
image_node.setTag("gui_type", "3d_image")
|
||||
image_node.setTag("gui_id", f"3d_image_{len(self.gui_elements)}")
|
||||
if image_path:
|
||||
image_node.setTag("gui_image_path", image_path)
|
||||
image_node.setTag("is_gui_element", "1")
|
||||
|
||||
self.gui_elements.append(image_node)
|
||||
|
||||
# 更新场景树
|
||||
if hasattr(self.world, 'updateSceneTree'):
|
||||
self.world.updateSceneTree()
|
||||
|
||||
print(f"✓ 3D图像创建完成: {image_path or '无纹理'} (世界位置: {pos})")
|
||||
return image_node
|
||||
|
||||
class GUIManager:
|
||||
"""GUI元素管理系统类"""
|
||||
@ -1887,28 +1959,9 @@ class GUIManager:
|
||||
if color.isValid():
|
||||
r, g, b = color.red() / 255.0, color.green() / 255.0, color.blue() / 255.0
|
||||
self.editGUIElement(gui_element, "color", [r, g, b, 1.0])
|
||||
|
||||
# def editGUI2DPosition(self, gui_element, axis, value):
|
||||
# """编辑2D GUI元素位置"""
|
||||
# try:
|
||||
# current_pos = gui_element.getPos()
|
||||
#
|
||||
# if axis == "x":
|
||||
# # 将逻辑坐标转换为屏幕坐标
|
||||
# new_screen_x = value * 0.1
|
||||
# gui_element.setPos(new_screen_x, current_pos.getY(), current_pos.getZ())
|
||||
# elif axis == "z":
|
||||
# # 将逻辑坐标转换为屏幕坐标
|
||||
# new_screen_z = value * 0.1
|
||||
# gui_element.setPos(current_pos.getX(), current_pos.getY(), new_screen_z)
|
||||
#
|
||||
# print(f"更新2D GUI位置: {axis}轴 = {value} (屏幕坐标: {gui_element.getPos()})")
|
||||
#
|
||||
# except Exception as e:
|
||||
# print(f"编辑2D GUI位置失败: {str(e)}")
|
||||
|
||||
def update3DImageTexture(self, model_nodepath, image_path):
|
||||
from panda3d.core import Texture
|
||||
from panda3d.core import Texture, TextureStage
|
||||
|
||||
try:
|
||||
# 加载新纹理
|
||||
@ -1918,14 +1971,21 @@ class GUIManager:
|
||||
new_texture.setMagfilter(Texture.FT_linear)
|
||||
new_texture.setMinfilter(Texture.FT_linear_mipmap_linear)
|
||||
|
||||
# 应用纹理到模型
|
||||
model_nodepath.setTexture(new_texture, 1)
|
||||
model_nodepath.clearTexture()
|
||||
|
||||
# 为3D图像创建独立的纹理阶段
|
||||
image_stage = TextureStage("3d_image_texture")
|
||||
image_stage.setSort(0) # 使用第一个纹理槽
|
||||
image_stage.setMode(TextureStage.MModulate) # 使用调制模式
|
||||
|
||||
# 应用纹理到模型,使用独立的纹理阶段
|
||||
model_nodepath.setTexture(image_stage, new_texture)
|
||||
|
||||
# 更新标签
|
||||
model_nodepath.setTag("gui_image_path", image_path)
|
||||
|
||||
# 确保材质设置正确
|
||||
if not model_nodepath.has_material():
|
||||
if not model_nodepath.hasMaterial():
|
||||
from panda3d.core import Material, LColor
|
||||
mat = Material()
|
||||
mat.setName(f"image-material-{id(model_nodepath)}")
|
||||
@ -1936,11 +1996,72 @@ class GUIManager:
|
||||
mat.setShininess(10.0)
|
||||
model_nodepath.setMaterial(mat, 1)
|
||||
|
||||
# 保护子节点的纹理(特别是3D文本)
|
||||
self._preserveChildNodeTextures(model_nodepath)
|
||||
|
||||
print(f"✅ 3D图像纹理已更新为: {image_path}")
|
||||
return True
|
||||
else:
|
||||
print(f"❌ 无法加载纹理: {image_path}")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"❌ 更新纹理时出错: {e}")
|
||||
return False
|
||||
|
||||
def _preserveChildNodeTextures(self, parent_node):
|
||||
"""保护子节点的纹理不被父节点纹理影响"""
|
||||
try:
|
||||
# 遍历所有直接子节点
|
||||
for i in range(parent_node.getNumChildren()):
|
||||
child = parent_node.getChild(i)
|
||||
|
||||
# 检查子节点是否为3D文本或其他需要特殊处理的节点
|
||||
if self._is3DTextElement(child):
|
||||
# 为3D文本创建独立的纹理阶段
|
||||
self._restore3DTextTexture(child)
|
||||
elif self._isOtherSpecialElement(child):
|
||||
# 为其他特殊元素恢复纹理
|
||||
self._restoreSpecialElementTexture(child)
|
||||
|
||||
except Exception as e:
|
||||
print(f"保护子节点纹理时出错: {e}")
|
||||
|
||||
def _is3DTextElement(self, node):
|
||||
"""检查节点是否为3D文本元素"""
|
||||
try:
|
||||
return (hasattr(node, 'getTag') and
|
||||
node.getTag("gui_type") == "3d_text")
|
||||
except:
|
||||
return False
|
||||
|
||||
def _isOtherSpecialElement(self, node):
|
||||
"""检查节点是否为其他需要特殊处理的元素"""
|
||||
try:
|
||||
return (hasattr(node, 'getTag') and
|
||||
node.getTag("gui_type") in ["3d_image", "button", "label"])
|
||||
except:
|
||||
return False
|
||||
|
||||
def _restore3DTextTexture(self, text_node):
|
||||
"""恢复3D文本的纹理设置"""
|
||||
try:
|
||||
from panda3d.core import TextureStage
|
||||
|
||||
# 如果3D文本已经有字体纹理,确保它使用正确的纹理阶段
|
||||
if hasattr(text_node, 'getTexture') and text_node.getTexture():
|
||||
# 创建专门用于文本的纹理阶段
|
||||
text_stage = TextureStage("text_texture")
|
||||
text_stage.setSort(10) # 使用较高的纹理槽索引
|
||||
text_stage.setMode(TextureStage.MModulate)
|
||||
|
||||
# 重新应用文本纹理
|
||||
current_texture = text_node.getTexture()
|
||||
text_node.setTexture(text_stage, current_texture)
|
||||
|
||||
print(f"已恢复3D文本纹理: {text_node.getName()}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"恢复3D文本纹理失败: {e}")
|
||||
|
||||
def update2DImageTexture(self, gui_element, image_path):
|
||||
"""更新2D图片纹理"""
|
||||
|
||||
@ -87,7 +87,7 @@ class SceneManager:
|
||||
print("✓ 场景管理系统初始化完成")
|
||||
|
||||
# ==================== 模型导入和处理 ====================
|
||||
|
||||
|
||||
def importModel(self, filepath, apply_unit_conversion=False, normalize_scales=True, auto_convert_to_glb=True):
|
||||
"""导入模型到场景 - 只在根节点下创建
|
||||
|
||||
@ -98,15 +98,15 @@ class SceneManager:
|
||||
auto_convert_to_glb: 是否自动将非GLB格式转换为GLB以获得更好的动画支持
|
||||
"""
|
||||
try:
|
||||
|
||||
# 预处理文件路径和转换
|
||||
filepath = util.normalize_model_path(filepath)
|
||||
original_filepath = filepath
|
||||
print(f"\n💾 开始导入模型: {os.path.basename(filepath)}")
|
||||
print(f"单位转换: {'开启' if apply_unit_conversion else '关闭'}")
|
||||
print(f"缩放标准化: {'开启' if normalize_scales else '关闭'}")
|
||||
print(f"自动转换GLB: {'开启' if auto_convert_to_glb else '关闭'}")
|
||||
|
||||
# 预处理文件路径和转换
|
||||
filepath = util.normalize_model_path(filepath)
|
||||
original_filepath = filepath
|
||||
|
||||
# 检查是否需要转换为GLB
|
||||
if auto_convert_to_glb and self._shouldConvertToGLB(filepath):
|
||||
print(f"🔄 检测到需要转换的格式,尝试转换为GLB...")
|
||||
@ -154,14 +154,27 @@ class SceneManager:
|
||||
model.setTag("converted_from", os.path.splitext(original_filepath)[1])
|
||||
model.setTag("converted_to_glb", "true")
|
||||
|
||||
# # 应用处理选项
|
||||
# if apply_unit_conversion and filepath.lower().endswith('.fbx'):
|
||||
# print("应用FBX单位转换(厘米到米)...")
|
||||
# self._applyUnitConversion(model, 0.01)
|
||||
# model.setTag("unit_conversion_applied", "true")
|
||||
#
|
||||
# if normalize_scales and filepath.lower().endswith('.fbx'):
|
||||
# print("标准化FBX模型缩放层级...")
|
||||
# self._normalizeModelScales(model)
|
||||
# model.setTag("scale_normalization_applied", "true")
|
||||
|
||||
# 应用处理选项
|
||||
if apply_unit_conversion and filepath.lower().endswith('.fbx'):
|
||||
print("应用FBX单位转换(厘米到米)...")
|
||||
# 对于GLB文件,通常不需要单位转换,因为它们已经是标准单位
|
||||
if apply_unit_conversion and filepath.lower().endswith(
|
||||
('.fbx', '.obj')) and not filepath.lower().endswith('.glb'):
|
||||
print("应用单位转换(厘米到米)...")
|
||||
self._applyUnitConversion(model, 0.01)
|
||||
model.setTag("unit_conversion_applied", "true")
|
||||
|
||||
if normalize_scales and filepath.lower().endswith('.fbx'):
|
||||
print("标准化FBX模型缩放层级...")
|
||||
if normalize_scales and filepath.lower().endswith(('.fbx', '.obj')):
|
||||
print("标准化模型缩放层级...")
|
||||
self._normalizeModelScales(model)
|
||||
model.setTag("scale_normalization_applied", "true")
|
||||
|
||||
@ -429,6 +442,8 @@ class SceneManager:
|
||||
print("模型已应用过单位转换,跳过")
|
||||
return
|
||||
|
||||
|
||||
|
||||
# 获取当前边界用于后续位置调整
|
||||
original_bounds = model.getBounds()
|
||||
|
||||
@ -734,7 +749,7 @@ class SceneManager:
|
||||
|
||||
# 根据调试设置决定是否显示碰撞体
|
||||
if hasattr(self.world, 'debug_collision') and self.world.debug_collision:
|
||||
cNodePath.show()
|
||||
cNodePath.hide()
|
||||
else:
|
||||
cNodePath.hide()
|
||||
|
||||
@ -1053,9 +1068,7 @@ class SceneManager:
|
||||
light.casts_shadows = True
|
||||
light.shadow_map_resolution = 256
|
||||
|
||||
# 设置光源的世界坐标位置
|
||||
world_pos = light_np.getPos(self.world.render)
|
||||
light.setPos(world_pos)
|
||||
light.setPos(*pos)
|
||||
|
||||
# 添加到渲染管线
|
||||
render_pipeline.add_light(light)
|
||||
@ -1152,9 +1165,7 @@ class SceneManager:
|
||||
# 创建点光源对象
|
||||
light = PointLight()
|
||||
|
||||
# 设置光源的世界坐标位置
|
||||
world_pos = light_np.getPos(self.world.render)
|
||||
light.setPos(world_pos)
|
||||
light.setPos(*pos)
|
||||
|
||||
light.energy = 5000
|
||||
light.radius = 1000
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
from PIL.ImageChops import lighter
|
||||
from PyQt5.QtWidgets import QTreeWidget, QTreeWidgetItem, QMenu, QStyle
|
||||
from PyQt5.QtCore import Qt
|
||||
from PyQt5.sip import delete
|
||||
@ -79,19 +80,113 @@ class InterfaceManager:
|
||||
deleteAction.triggered.connect(lambda:self.deleteCesiumTileset(nodePath,item))
|
||||
|
||||
else:
|
||||
# 为模型节点或其子节点添加删除选项
|
||||
parentItem = item.parent()
|
||||
if parentItem:
|
||||
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))
|
||||
#灯光节点添加特殊处理
|
||||
if self.isLightNode(nodePath):
|
||||
deleteAction = menu.addAction("删除灯光")
|
||||
deleteAction.triggered.connect(lambda:self.deleteLightNode(nodePath,item))
|
||||
else:
|
||||
deleteAction = menu.addAction("删除")
|
||||
deleteAction.triggered.connect(lambda: self.deleteNode(nodePath, item))
|
||||
|
||||
# 显示菜单
|
||||
menu.exec_(self.treeWidget.viewport().mapToGlobal(position))
|
||||
|
||||
def isLightNode(self, nodePath):
|
||||
try:
|
||||
if not nodePath or nodePath.isEmpty():
|
||||
return False
|
||||
|
||||
# 修复:统一使用 rp_light_object
|
||||
if hasattr(nodePath, 'getPythonTag'):
|
||||
light_object = nodePath.getPythonTag('rp_light_object')
|
||||
if light_object is not None:
|
||||
return True
|
||||
|
||||
if hasattr(nodePath, 'getTag'):
|
||||
light_type = nodePath.getTag('light_type')
|
||||
if light_type in ["spot_light", "point_light"]:
|
||||
return True
|
||||
|
||||
if hasattr(self.world, 'Spotlight') and nodePath in self.world.Spotlight:
|
||||
return True
|
||||
if hasattr(self.world, 'Pointlight') and nodePath in self.world.Pointlight:
|
||||
return True
|
||||
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"判断节点是否是灯光节点失败: {str(e)}")
|
||||
return False
|
||||
|
||||
def deleteLightNode(self, nodePath, item):
|
||||
"""专门处理灯光节点的删除"""
|
||||
try:
|
||||
print(f"开始删除灯光节点: {nodePath.getName()}")
|
||||
|
||||
# 从RenderPipeline中移除灯光(如果存在)
|
||||
if hasattr(nodePath, 'getPythonTag'):
|
||||
light_object = nodePath.getPythonTag('rp_light_object')
|
||||
if light_object and hasattr(self.world, 'render_pipeline'):
|
||||
print("从RenderPipeline移除灯光")
|
||||
self.world.render_pipeline.remove_light(light_object)
|
||||
nodePath.clearPythonTag('rp_light_object')
|
||||
|
||||
if hasattr(self.world,'Spotlight') and nodePath in self.world.Spotlight:
|
||||
self.world.Spotlight.remove(nodePath)
|
||||
print("从Spotlight列表中删除")
|
||||
if hasattr(self.world,'Pointlight') and nodePath in self.world.Pointlight:
|
||||
self.world.Pointlight.remove(nodePath)
|
||||
print("从Pointlight列表中移除")
|
||||
|
||||
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
|
||||
|
||||
print(f"移除节点{nodePath.getName()}")
|
||||
nodePath.removeNode()
|
||||
|
||||
parentItem = item.parent()
|
||||
if parentItem:
|
||||
parentItem.removeChild(item)
|
||||
|
||||
print(f"成功删除灯光节点{nodePath.getName()}")
|
||||
|
||||
if hasattr(self.world,'property_panel'):
|
||||
self.world.property_panel.clearPropertyPanel()
|
||||
if hasattr(self.world,'selection'):
|
||||
self.world.selection.updateSelection(None)
|
||||
|
||||
except Exception as e:
|
||||
print(f"删除灯光节点失败: {str(e)}")
|
||||
|
||||
def _recursiveRemoveLights(self, nodePath):
|
||||
"""递归删除节点及其子节点中的所有灯光"""
|
||||
if nodePath.isEmpty():
|
||||
return
|
||||
|
||||
# 先递归处理所有子节点
|
||||
for child in nodePath.getChildren():
|
||||
self._recursiveRemoveLights(child)
|
||||
|
||||
# 然后处理当前节点
|
||||
if self.isLightNode(nodePath):
|
||||
print(f"删除子灯光节点: {nodePath.getName()}")
|
||||
|
||||
# 从RenderPipeline中移除灯光
|
||||
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)
|
||||
nodePath.clearPythonTag('rp_light_object')
|
||||
|
||||
# 从灯光列表中移除
|
||||
if hasattr(self.world, 'Spotlight') and nodePath in self.world.Spotlight:
|
||||
self.world.Spotlight.remove(nodePath)
|
||||
if hasattr(self.world, 'Pointlight') and nodePath in self.world.Pointlight:
|
||||
self.world.Pointlight.remove(nodePath)
|
||||
|
||||
def deleteCesiumTileset(self, nodePath, item):
|
||||
"""删除 Cesium tileset"""
|
||||
try:
|
||||
@ -137,9 +232,14 @@ class InterfaceManager:
|
||||
def deleteNode(self, nodePath, item):
|
||||
"""删除节点"""
|
||||
try:
|
||||
item_data = item.data(0,Qt.UserRole+1)
|
||||
# 如果是灯光节点,直接调用灯光删除方法
|
||||
if self.isLightNode(nodePath):
|
||||
self.deleteLightNode(nodePath, item)
|
||||
return
|
||||
|
||||
item_data = item.data(0, Qt.UserRole + 1)
|
||||
if item_data == "terrain":
|
||||
if hasattr(self.world,'terrain_manager') and self.world.terrain_manager.terrains:
|
||||
if hasattr(self.world, 'terrain_manager') and self.world.terrain_manager.terrains:
|
||||
terrain_to_remove = None
|
||||
for terrain_info in self.world.terrain_manager.terrains:
|
||||
if terrain_info['node'] == nodePath:
|
||||
@ -153,34 +253,38 @@ class InterfaceManager:
|
||||
self.world.selection.updateSelection(None)
|
||||
return
|
||||
|
||||
# 先递归删除所有子节点中的灯光
|
||||
self._recursiveRemoveLights(nodePath)
|
||||
|
||||
# 从场景中移除
|
||||
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 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)
|
||||
# 强制删除节点及其所有子节点
|
||||
if not nodePath.isEmpty():
|
||||
# 先递归删除所有子节点
|
||||
children = list(nodePath.getChildren())
|
||||
for child in children:
|
||||
try:
|
||||
if not child.isEmpty():
|
||||
child.removeNode()
|
||||
except Exception as e:
|
||||
print(f"删除子节点失败: {str(e)}")
|
||||
|
||||
nodePath.removeNode()
|
||||
# 再删除节点本身
|
||||
nodePath.removeNode()
|
||||
|
||||
if hasattr(self.world,'selection'):
|
||||
if hasattr(self.world, 'selection'):
|
||||
self.world.selection.checkAndClearIfTargetDeleted()
|
||||
|
||||
# 如果是模型根节点,从模型列表中移除
|
||||
#if item.parent().text(0) == "模型":
|
||||
if nodePath in self.world.models:
|
||||
if hasattr(self.world, 'models') and nodePath in self.world.models:
|
||||
self.world.models.remove(nodePath)
|
||||
|
||||
# 从树形控件中移除
|
||||
@ -196,6 +300,51 @@ class InterfaceManager:
|
||||
|
||||
except Exception as e:
|
||||
print(f"删除节点失败: {str(e)}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
def _cleanupAllLightsInSubtree(self,parentNode):
|
||||
try:
|
||||
if parentNode.isEmpty():
|
||||
return
|
||||
#收集所有子节点
|
||||
all_nodes = []
|
||||
|
||||
def collect_all_nodes(node):
|
||||
if not node.isEmpty():
|
||||
all_nodes.append(node)
|
||||
for child in node.getChildren():
|
||||
collect_all_nodes(child)
|
||||
|
||||
collect_all_nodes(parentNode)
|
||||
|
||||
ligths_processed = 0
|
||||
for node in all_nodes:
|
||||
if self.isLightNode(node):
|
||||
if hasattr(node,'getPythonTag'):
|
||||
light_object = node.getPythonTag('rp_light_object')
|
||||
if light_object and hasattr(self.world,'render_pipeline'):
|
||||
try:
|
||||
self.world.render_pipeline.remove_light(light_object)
|
||||
print(f"✓ 从渲染管线移除灯光: {node.getName()}")
|
||||
except Exception as e:
|
||||
print(f"⚠ 从渲染管线移除灯光失败: {str(e)}")
|
||||
#从灯光列表中移除
|
||||
try:
|
||||
if node in self.world.Spotlight:
|
||||
self.world.Spotlight.remove(node)
|
||||
print(f"从Spotlight列表移除{node.getName()}")
|
||||
if node in self.world.Pointlight:
|
||||
self.world.Pointlight.remove(node)
|
||||
print(f"从pointlight列表移除{node.getName()}")
|
||||
except Exception as e:
|
||||
print(f"从灯列表移除失败{str(e)}")
|
||||
ligths_processed += 1
|
||||
if ligths_processed>0:
|
||||
print(f"清理{ligths_processed}个灯光节点")
|
||||
except Exception as e:
|
||||
print(f"清理节点树中的灯光失败{str(e)}")
|
||||
|
||||
|
||||
def updateSceneTree(self):
|
||||
"""更新场景树显示 - 实际实现"""
|
||||
|
||||
@ -314,6 +314,20 @@ class MainWindow(QMainWindow):
|
||||
self.createMenu = menubar.addMenu('创建')
|
||||
self.setupCreateMenuActions() # 统一创建菜单动作
|
||||
|
||||
# self.createGUIaddMenu = self.createMenu.addMenu('GUI')
|
||||
# self.createButtonAction = self.createGUIaddMenu.addAction('创建按钮')
|
||||
# self.createLabelAction = self.createGUIaddMenu.addAction('创建标签')
|
||||
# self.createEntryAction = self.createGUIaddMenu.addAction('创建输入框')
|
||||
# self.createGUIaddMenu.addSeparator()
|
||||
# self.createVirtualScreenAction = self.createGUIaddMenu.addAction('创建虚拟屏幕')
|
||||
# self.createCesiumViewAction = self.createGUIaddMenu.addAction('创建Cesium地图')
|
||||
# self.toggleCesiumViewAction = self.createGUIaddMenu.addAction('开关地图')
|
||||
# self.refreshCesiumViewAction = self.createGUIaddMenu.addAction('刷新地图')
|
||||
#
|
||||
# self.createLightaddMenu = self.createMenu.addMenu('光源')
|
||||
# self.createSpotLightAction = self.createLightaddMenu.addAction('聚光灯')
|
||||
# self.createPointLightAction = self.createLightaddMenu.addAction('点光源')
|
||||
|
||||
#添加地形菜单
|
||||
self.createTerrainMenu = self.createMenu.addMenu('地形')
|
||||
self.createFlatTerrainAction = self.createTerrainMenu.addAction('创建平面地形')
|
||||
@ -647,11 +661,6 @@ class MainWindow(QMainWindow):
|
||||
self.createHeightmapTerrainTool.setText("高度图地形")
|
||||
self.toolbar.addWidget(self.createHeightmapTerrainTool)
|
||||
|
||||
self.terrainEditTool = QToolButton()
|
||||
self.terrainEditTool.setText("地形编辑")
|
||||
self.terrainEditTool.setCheckable(True)
|
||||
self.toolbar.addWidget(self.terrainEditTool)
|
||||
|
||||
# 默认选择"选择"工具
|
||||
self.selectTool.setChecked(True)
|
||||
self.world.setCurrentTool("选择")
|
||||
@ -1429,22 +1438,6 @@ class MainWindow(QMainWindow):
|
||||
else:
|
||||
QMessageBox.warning(self, "错误", "高度图地形创建失败!")
|
||||
|
||||
def onTerrainEditMode(self):
|
||||
"""地形编辑模式"""
|
||||
# 检查当前是否已经处于地形编辑模式
|
||||
if self.world.currentTool == "地形编辑":
|
||||
# 退出地形编辑模式
|
||||
self.world.setCurrentTool(None)
|
||||
self.terrainEditTool.setChecked(False)
|
||||
self.terrainEditTool.setText("地形编辑")
|
||||
QMessageBox.information(self, "地形编辑", "已退出地形编辑模式")
|
||||
else:
|
||||
# 进入地形编辑模式
|
||||
self.world.setCurrentTool("地形编辑")
|
||||
self.terrainEditTool.setChecked(True)
|
||||
self.terrainEditTool.setText("退出地形编辑")
|
||||
QMessageBox.information(self, "地形编辑",
|
||||
"已进入地形编辑模式\n\n使用鼠标左键抬高地形\n使用鼠标右键降低地形")
|
||||
def setup_main_window(world):
|
||||
"""设置主窗口的便利函数"""
|
||||
app = QApplication.instance()
|
||||
|
||||
@ -340,9 +340,9 @@ class PropertyPanelManager:
|
||||
self.scale_y.setValue(scale.getY())
|
||||
self.scale_z.setValue(scale.getZ())
|
||||
|
||||
self.scale_x.valueChanged.connect(lambda v:terrain_node.setScaleX(v))
|
||||
self.scale_y.valueChanged.connect(lambda v:terrain_node.setScaleY(v))
|
||||
self.scale_z.valueChanged.connect(lambda v:terrain_node.setScaleZ(v))
|
||||
self.scale_x.valueChanged.connect(lambda v:self._updateXScale(terrain_node,v))
|
||||
self.scale_y.valueChanged.connect(lambda v:self._updateYScale(terrain_node,v))
|
||||
self.scale_z.valueChanged.connect(lambda v:self._updateZScale(terrain_node,v))
|
||||
|
||||
x_label2 = QLabel("X")
|
||||
y_label2 = QLabel("Y")
|
||||
@ -1153,9 +1153,6 @@ class PropertyPanelManager:
|
||||
current_scale.getZ()])):
|
||||
scale_widget.setRange(-1000, 1000)
|
||||
scale_widget.setSingleStep(0.1)
|
||||
# 如果缩放值为0,设置为一个很小的非零值
|
||||
if scale_value == 0:
|
||||
scale_value = 0.01 if scale_value >= 0 else -0.01
|
||||
scale_widget.setValue(scale_value)
|
||||
|
||||
self.scale_x.valueChanged.connect(lambda value: self._onScaleValueChanged(self.scale_x, value))
|
||||
@ -1193,13 +1190,7 @@ class PropertyPanelManager:
|
||||
self._updateModelMaterialPanel(model)
|
||||
|
||||
def _onScaleValueChanged(self, scale_widget, value):
|
||||
"""确保缩放值不为0"""
|
||||
if value == 0:
|
||||
# 设置为一个很小的非零值,保持原有符号
|
||||
if hasattr(scale_widget, 'value') and scale_widget.value() > 0:
|
||||
scale_widget.setValue(0.01)
|
||||
else:
|
||||
scale_widget.setValue(-0.01)
|
||||
pass
|
||||
|
||||
def _updateXScale(self, model, value):
|
||||
"""更新X轴缩放值"""
|
||||
@ -1558,6 +1549,7 @@ class PropertyPanelManager:
|
||||
if success:
|
||||
# 保存路径到 Tag
|
||||
gui_element.setTag("texture_path", file_path)
|
||||
gui_element.setTag("gui_image_path",file_path)
|
||||
# 更新显示
|
||||
texture_label.setText(file_path)
|
||||
# 可选:刷新场景树或其他 UI
|
||||
@ -1925,19 +1917,6 @@ class PropertyPanelManager:
|
||||
except Exception as e:
|
||||
print(f"✗ 更新GUI元素Z轴缩放失败: {e}")
|
||||
|
||||
def update3DImageTexture(self,nodepath,texture_path):
|
||||
try:
|
||||
tex = self.world.loader.loadTexture(texture_path)
|
||||
if tex:
|
||||
nodepath.setTexture(tex,1)
|
||||
return True
|
||||
else:
|
||||
print(f"[警告] 无法加载贴图: {texture_path}")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"[错误] 更新 3D 图片纹理失败: {e}")
|
||||
return False
|
||||
|
||||
def update2DImageTexture(self, gui_element, image_path):
|
||||
try:
|
||||
new_texture = self.world.loader.loadTexture(image_path)
|
||||
|
||||
@ -20,7 +20,7 @@ from PyQt5.sip import wrapinstance
|
||||
from panda3d.core import ModelRoot
|
||||
|
||||
from QPanda3D.QPanda3DWidget import QPanda3DWidget
|
||||
|
||||
from scene import util
|
||||
|
||||
class NewProjectDialog(QDialog):
|
||||
"""新建项目对话框"""
|
||||
@ -150,8 +150,8 @@ class CustomPanda3DWidget(QPanda3DWidget):
|
||||
for url in event.mimeData().urls():
|
||||
filepath = url.toLocalFile()
|
||||
if filepath.lower().endswith(('.egg', '.bam', '.obj', '.fbx', '.gltf', '.glb')):
|
||||
# 使用关键字参数确保兼容性
|
||||
self.world.importModel(filepath)
|
||||
#self.world.addAnimationPanel(None,filepath)
|
||||
event.acceptProposedAction()
|
||||
else:
|
||||
event.ignore()
|
||||
@ -936,6 +936,7 @@ class CustomAssetsTreeWidget(QTreeWidget):
|
||||
internal_paths.append(filepath)
|
||||
# 检查是否是模型文件(用于向外拖拽)
|
||||
if filepath.lower().endswith(('.egg', '.bam', '.obj', '.fbx', '.gltf', '.glb')):
|
||||
print(f"模型路ing!!!!!!!!!!!!!!!!!{QUrl.fromLocalFile(filepath)}")
|
||||
urls.append(QUrl.fromLocalFile(filepath))
|
||||
|
||||
# 设置内部拖拽数据
|
||||
@ -1910,24 +1911,30 @@ class CustomTreeWidget(QTreeWidget):
|
||||
|
||||
def should_skip(node):
|
||||
name = node.getName()
|
||||
return name in BLACK_LIST or name.startswith('__') or isinstance(node.node(),CollisionNode) or isinstance(node.node(),ModelRoot) or name==""
|
||||
return name in BLACK_LIST or name.startswith('__') or isinstance(node.node(), CollisionNode) or isinstance(
|
||||
node.node(), ModelRoot) or name == ""
|
||||
|
||||
def addNodeToTree(node,parentItem,force = False):
|
||||
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)
|
||||
return None
|
||||
nodeItem = QTreeWidgetItem(parentItem, [node.getName()])
|
||||
nodeItem.setData(0, Qt.UserRole, node)
|
||||
nodeItem.setData(0, Qt.UserRole + 1, node_type)
|
||||
|
||||
for child in node.getChildren():
|
||||
addNodeToTree(child,nodeItem,force = False)
|
||||
addNodeToTree(child, nodeItem, force=False)
|
||||
return nodeItem
|
||||
|
||||
try:
|
||||
from PyQt5.QtWidgets import QTreeWidgetItem
|
||||
from PyQt5.QtCore import Qt
|
||||
|
||||
# 初始化new_qt_item变量
|
||||
new_qt_item = None
|
||||
|
||||
if node_type == "IMPORTED_MODEL_NODE":
|
||||
node_name = node.getTag("file") if hasattr(node, 'getTag') else "model"
|
||||
addNodeToTree(node, parent_item, force=True)
|
||||
new_qt_item = addNodeToTree(node, parent_item, force=True)
|
||||
else:
|
||||
node_name = node.getName() if hasattr(node, 'getName') else "node"
|
||||
new_qt_item = QTreeWidgetItem(parent_item, [node_name])
|
||||
|
||||
Loading…
Reference in New Issue
Block a user