diff --git a/.gitignore b/.gitignore
index 76bb497f..74ae8b6c 100644
Binary files a/.gitignore and b/.gitignore differ
diff --git a/.idea/.gitignore b/.idea/.gitignore
deleted file mode 100644
index 359bb530..00000000
--- a/.idea/.gitignore
+++ /dev/null
@@ -1,3 +0,0 @@
-# 默认忽略的文件
-/shelf/
-/workspace.xml
diff --git a/.idea/EG.iml b/.idea/EG.iml
index c1574376..8a3456df 100644
--- a/.idea/EG.iml
+++ b/.idea/EG.iml
@@ -4,7 +4,7 @@
-
+
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
deleted file mode 100644
index 84212658..00000000
--- a/.idea/inspectionProfiles/Project_Default.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml
deleted file mode 100644
index 105ce2da..00000000
--- a/.idea/inspectionProfiles/profiles_settings.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 51b9fc17..370e0928 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -3,5 +3,5 @@
-
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
deleted file mode 100644
index 4bfd7d3a..00000000
--- a/.idea/modules.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
deleted file mode 100644
index 35eb1ddf..00000000
--- a/.idea/vcs.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/QPanda3D/Helpers/__pycache__/Env_Grid_Maker.cpython-312.pyc b/QPanda3D/Helpers/__pycache__/Env_Grid_Maker.cpython-312.pyc
deleted file mode 100644
index 23ab32d8..00000000
Binary files a/QPanda3D/Helpers/__pycache__/Env_Grid_Maker.cpython-312.pyc and /dev/null differ
diff --git a/QPanda3D/Helpers/__pycache__/__init__.cpython-312.pyc b/QPanda3D/Helpers/__pycache__/__init__.cpython-312.pyc
deleted file mode 100644
index f0b56cb1..00000000
Binary files a/QPanda3D/Helpers/__pycache__/__init__.cpython-312.pyc and /dev/null differ
diff --git a/QPanda3D/Tools/__pycache__/__init__.cpython-312.pyc b/QPanda3D/Tools/__pycache__/__init__.cpython-312.pyc
deleted file mode 100644
index 3c5d492a..00000000
Binary files a/QPanda3D/Tools/__pycache__/__init__.cpython-312.pyc and /dev/null differ
diff --git a/QPanda3D/Tools/__pycache__/generate_qt_to_pd3d_translator.cpython-312.pyc b/QPanda3D/Tools/__pycache__/generate_qt_to_pd3d_translator.cpython-312.pyc
deleted file mode 100644
index e67c5397..00000000
Binary files a/QPanda3D/Tools/__pycache__/generate_qt_to_pd3d_translator.cpython-312.pyc and /dev/null differ
diff --git a/__pycache__/gui_edit_window.cpython-310.pyc b/__pycache__/gui_edit_window.cpython-310.pyc
deleted file mode 100644
index 4ff712e2..00000000
Binary files a/__pycache__/gui_edit_window.cpython-310.pyc and /dev/null differ
diff --git a/__pycache__/gui_editor_window.cpython-310.pyc b/__pycache__/gui_editor_window.cpython-310.pyc
deleted file mode 100644
index b3976f5b..00000000
Binary files a/__pycache__/gui_editor_window.cpython-310.pyc and /dev/null differ
diff --git a/__pycache__/gui_preview_window.cpython-310.pyc b/__pycache__/gui_preview_window.cpython-310.pyc
deleted file mode 100644
index ba2587bd..00000000
Binary files a/__pycache__/gui_preview_window.cpython-310.pyc and /dev/null differ
diff --git a/__pycache__/gui_preview_window.cpython-312.pyc b/__pycache__/gui_preview_window.cpython-312.pyc
deleted file mode 100644
index d9b3c6c2..00000000
Binary files a/__pycache__/gui_preview_window.cpython-312.pyc and /dev/null differ
diff --git a/__pycache__/main.cpython-310.pyc b/__pycache__/main.cpython-310.pyc
deleted file mode 100644
index f92ba533..00000000
Binary files a/__pycache__/main.cpython-310.pyc and /dev/null differ
diff --git a/__pycache__/main.cpython-312.pyc b/__pycache__/main.cpython-312.pyc
deleted file mode 100644
index c1b6b806..00000000
Binary files a/__pycache__/main.cpython-312.pyc and /dev/null differ
diff --git a/__pycache__/test.cpython-310.pyc b/__pycache__/test.cpython-310.pyc
deleted file mode 100644
index 39b802e9..00000000
Binary files a/__pycache__/test.cpython-310.pyc and /dev/null differ
diff --git a/__pycache__/video_integration.cpython-310.pyc b/__pycache__/video_integration.cpython-310.pyc
deleted file mode 100644
index 49794078..00000000
Binary files a/__pycache__/video_integration.cpython-310.pyc and /dev/null differ
diff --git a/core/TranslateArrowHandle.fbx b/core/TranslateArrowHandle.fbx
new file mode 100755
index 00000000..29725650
Binary files /dev/null and b/core/TranslateArrowHandle.fbx differ
diff --git a/core/__pycache__/CustomMouseController.cpython-310.pyc b/core/__pycache__/CustomMouseController.cpython-310.pyc
deleted file mode 100644
index a974aa7a..00000000
Binary files a/core/__pycache__/CustomMouseController.cpython-310.pyc and /dev/null differ
diff --git a/core/__pycache__/CustomMouseController.cpython-312.pyc b/core/__pycache__/CustomMouseController.cpython-312.pyc
deleted file mode 100644
index 3005ff4f..00000000
Binary files a/core/__pycache__/CustomMouseController.cpython-312.pyc and /dev/null differ
diff --git a/core/__pycache__/__init__.cpython-310.pyc b/core/__pycache__/__init__.cpython-310.pyc
deleted file mode 100644
index 40f6ebde..00000000
Binary files a/core/__pycache__/__init__.cpython-310.pyc and /dev/null differ
diff --git a/core/__pycache__/__init__.cpython-312.pyc b/core/__pycache__/__init__.cpython-312.pyc
deleted file mode 100644
index ea8a3f4d..00000000
Binary files a/core/__pycache__/__init__.cpython-312.pyc and /dev/null differ
diff --git a/core/__pycache__/alvr_streamer.cpython-310.pyc b/core/__pycache__/alvr_streamer.cpython-310.pyc
deleted file mode 100644
index a861298d..00000000
Binary files a/core/__pycache__/alvr_streamer.cpython-310.pyc and /dev/null differ
diff --git a/core/__pycache__/alvr_streamer.cpython-312.pyc b/core/__pycache__/alvr_streamer.cpython-312.pyc
deleted file mode 100644
index bd0b0529..00000000
Binary files a/core/__pycache__/alvr_streamer.cpython-312.pyc and /dev/null differ
diff --git a/core/__pycache__/event_handler.cpython-310.pyc b/core/__pycache__/event_handler.cpython-310.pyc
deleted file mode 100644
index 74eb37ca..00000000
Binary files a/core/__pycache__/event_handler.cpython-310.pyc and /dev/null differ
diff --git a/core/__pycache__/event_handler.cpython-312.pyc b/core/__pycache__/event_handler.cpython-312.pyc
deleted file mode 100644
index 46d916d2..00000000
Binary files a/core/__pycache__/event_handler.cpython-312.pyc and /dev/null differ
diff --git a/core/__pycache__/render_pipeline_world.cpython-310.pyc b/core/__pycache__/render_pipeline_world.cpython-310.pyc
deleted file mode 100644
index 91ec6f39..00000000
Binary files a/core/__pycache__/render_pipeline_world.cpython-310.pyc and /dev/null differ
diff --git a/core/__pycache__/script_system.cpython-310.pyc b/core/__pycache__/script_system.cpython-310.pyc
deleted file mode 100644
index b64c149c..00000000
Binary files a/core/__pycache__/script_system.cpython-310.pyc and /dev/null differ
diff --git a/core/__pycache__/script_system.cpython-312.pyc b/core/__pycache__/script_system.cpython-312.pyc
deleted file mode 100644
index 6c360ff0..00000000
Binary files a/core/__pycache__/script_system.cpython-312.pyc and /dev/null differ
diff --git a/core/__pycache__/selection.cpython-310.pyc b/core/__pycache__/selection.cpython-310.pyc
deleted file mode 100644
index 58be847c..00000000
Binary files a/core/__pycache__/selection.cpython-310.pyc and /dev/null differ
diff --git a/core/__pycache__/selection.cpython-312.pyc b/core/__pycache__/selection.cpython-312.pyc
deleted file mode 100644
index 68ef7ea4..00000000
Binary files a/core/__pycache__/selection.cpython-312.pyc and /dev/null differ
diff --git a/core/__pycache__/tool_manager.cpython-310.pyc b/core/__pycache__/tool_manager.cpython-310.pyc
deleted file mode 100644
index 6f3cf305..00000000
Binary files a/core/__pycache__/tool_manager.cpython-310.pyc and /dev/null differ
diff --git a/core/__pycache__/tool_manager.cpython-312.pyc b/core/__pycache__/tool_manager.cpython-312.pyc
deleted file mode 100644
index 0c61d5ac..00000000
Binary files a/core/__pycache__/tool_manager.cpython-312.pyc and /dev/null differ
diff --git a/core/__pycache__/vr_input_handler.cpython-310.pyc b/core/__pycache__/vr_input_handler.cpython-310.pyc
deleted file mode 100644
index 6eb270f8..00000000
Binary files a/core/__pycache__/vr_input_handler.cpython-310.pyc and /dev/null differ
diff --git a/core/__pycache__/vr_input_handler.cpython-312.pyc b/core/__pycache__/vr_input_handler.cpython-312.pyc
deleted file mode 100644
index a5cc4d93..00000000
Binary files a/core/__pycache__/vr_input_handler.cpython-312.pyc and /dev/null differ
diff --git a/core/__pycache__/vr_manager.cpython-310.pyc b/core/__pycache__/vr_manager.cpython-310.pyc
deleted file mode 100644
index 0abd52e0..00000000
Binary files a/core/__pycache__/vr_manager.cpython-310.pyc and /dev/null differ
diff --git a/core/__pycache__/vr_manager.cpython-312.pyc b/core/__pycache__/vr_manager.cpython-312.pyc
deleted file mode 100644
index 9432a144..00000000
Binary files a/core/__pycache__/vr_manager.cpython-312.pyc and /dev/null differ
diff --git a/core/__pycache__/world.cpython-310.pyc b/core/__pycache__/world.cpython-310.pyc
deleted file mode 100644
index b9644a5f..00000000
Binary files a/core/__pycache__/world.cpython-310.pyc and /dev/null differ
diff --git a/core/__pycache__/world.cpython-312.pyc b/core/__pycache__/world.cpython-312.pyc
deleted file mode 100644
index ebe521bc..00000000
Binary files a/core/__pycache__/world.cpython-312.pyc and /dev/null differ
diff --git a/core/selection.py b/core/selection.py
index d1c62a24..22ff8ef5 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("✓ 选择和变换系统初始化完成")
@@ -105,6 +105,37 @@ class SelectionSystem:
if not self.selectionBox or not self.selectionBoxTarget:
return
+ # 检查是否需要重新计算边界框
+ if not hasattr(self, '_bounds_cache'):
+ self._bounds_cache = {}
+
+ node_name = self.selectionBoxTarget.getName()
+ import time
+ current_time = time.time()
+
+ # 如果缓存存在且未过期,则使用缓存
+ if (node_name in self._bounds_cache and
+ current_time - self._bounds_cache[node_name]['time'] < 0.1):
+ minPoint, maxPoint = self._bounds_cache[node_name]['bounds']
+ else:
+ # 计算新的边界框并缓存
+ minPoint = Point3()
+ maxPoint = Point3()
+ if not self.selectionBoxTarget.calcTightBounds(minPoint, maxPoint, self.world.render):
+ return
+
+ # 缓存结果
+ self._bounds_cache[node_name] = {
+ 'bounds': (minPoint, maxPoint),
+ 'time': current_time
+ }
+
+ # 清理旧缓存
+ expired_keys = [k for k, v in self._bounds_cache.items()
+ if current_time - v['time'] > 1.0]
+ for key in expired_keys:
+ del self._bounds_cache[key]
+
# 清除现有的几何体
self.selectionBox.removeNode()
self.selectionBox = self.world.render.attachNewNode("selectionBox")
@@ -177,6 +208,18 @@ class SelectionSystem:
def updateSelectionBoxTask(self, task):
"""选择框更新任务"""
try:
+ if not hasattr(self,'_last_selection_box_update'):
+ self._last_selection_box_update = 0
+
+ import time
+ current_time = time.time()
+ if current_time - self._last_selection_box_update < 0.1:
+ return task.cont
+ self._last_selection_box_update = current_time
+
+ #检查目标节点是否已被删除
+ self.checkAndClearIfTargetDeleted()
+
if not self.selectionBox or not self.selectionBoxTarget:
return task.done # 结束任务
@@ -260,11 +303,18 @@ 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"])
+ self._updateGizmoScreenSize()
+
+ self._setupGizmoRendering()
+
+ # 现在才显示坐标轴
+ self.gizmo.show()
+
# 只启动一次更新任务
taskMgr.add(self.updateGizmoTask, "updateGizmo")
@@ -279,325 +329,117 @@ 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"])
-
- 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 _setupGizmoRendering(self):
- """设置坐标轴渲染属性"""
try:
- # 设置渲染优先级,确保在最前面显示
- self.gizmo.setBin("gui-popup", 1000)
- self.gizmo.setDepthTest(False)
- self.gizmo.setDepthWrite(False)
- self.gizmo.setLightOff()
+ axis_nodes = [self.gizmoXAxis,self.gizmoYAxis,self.gizmoZAxis]
- # 为每个轴设置独立的渲染属性
- for i, axis_node in enumerate([self.gizmoXAxis, self.gizmoYAxis, self.gizmoZAxis]):
+ for axis_node in axis_nodes:
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)
-
- # 直接设置颜色,不使用复杂的渲染状态
+ axis_node.setShaderOff()
+ axis_node.setFogOff()
+ #设置渲染层级,确保大多数对象之前渲染
+ axis_node.setBin("fixed",30)
+ axis_node.setDepthWrite(False)
+ axis_node.setDepthTest(False)
+ arrow_nodes = []
if self.gizmoXAxis:
- self.gizmoXAxis.setColor(1, 0, 0, 1)
- self.gizmoXAxis.setLightOff()
- self.gizmoXAxis.setBin("gui-popup", 20001)
- self.gizmoXAxis.setDepthTest(False)
-
+ x_arrow = self.gizmoXAxis.find("x_arrow")
+ if x_arrow:
+ arrow_nodes.append(x_arrow)
if self.gizmoYAxis:
- self.gizmoYAxis.setColor(0, 1, 0, 1)
- self.gizmoYAxis.setLightOff()
- self.gizmoYAxis.setBin("gui-popup", 20002)
- self.gizmoYAxis.setDepthTest(False)
-
+ y_arrow = self.gizmoYAxis.find("y_arrow")
+ if y_arrow:
+ arrow_nodes.append(y_arrow)
if self.gizmoZAxis:
- self.gizmoZAxis.setColor(0, 0, 1, 1)
- self.gizmoZAxis.setLightOff()
- self.gizmoZAxis.setBin("gui-popup", 20003)
- self.gizmoZAxis.setDepthTest(False)
+ z_arrow = self.gizmoZAxis.find("z_arrow")
+ if z_arrow:
+ arrow_nodes.append(z_arrow)
+ 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"❌ 最后备用方案也失败: {e}")
+ print(f"设置坐标轴渲染属性失败: {str(e)}")
def updateGizmoTask(self, task):
"""坐标轴更新任务 - 包含固定大小功能"""
try:
+ # 限制更新频率
+ if not hasattr(self, '_last_gizmo_update'):
+ self._last_gizmo_update = 0
+
+ import time
+ current_time = time.time()
+ if current_time - self._last_gizmo_update < 0.05: # 每0.05秒更新一次
+ return task.cont
+ self._last_gizmo_update = current_time
+
+ #检查目标节点是否已被删除
+ self.checkAndClearIfTargetDeleted()
+
if not self.gizmo or not self.gizmoTarget:
return task.done
@@ -613,25 +455,8 @@ class SelectionSystem:
self.gizmoTarget.setPos(light_pos)
else:
- # 更新坐标轴位置,始终在目标节点中心
- 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._updateGizmoPositionAndOrientation()
# 【新功能】:动态调整坐标轴大小,保持固定的屏幕大小
self._updateGizmoScreenSize()
@@ -642,6 +467,33 @@ class SelectionSystem:
print(f"坐标轴更新任务出错: {str(e)}")
return task.done
+ def _updateGizmoPositionAndOrientation(self):
+ """优化的Gizmo位置和朝向更新"""
+ # 只在必要时重新计算边界框
+ if not hasattr(self, '_last_gizmo_bounds_update'):
+ self._last_gizmo_bounds_update = 0
+
+ import time
+ current_time = time.time()
+ 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)
+ self._last_gizmo_bounds_update = current_time
+
+ # 更新朝向
+ 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)
+
def _updateGizmoScreenSize(self):
"""动态调整坐标轴大小,保持固定的屏幕大小"""
try:
@@ -674,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:
@@ -706,34 +558,122 @@ 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)
+
+ arrow_node.setLightOff() # 禁用光照影响
+ arrow_node.setShaderOff() # 禁用着色器
+ arrow_node.setFogOff() # 禁用雾效果
+
+ arrow_node.setBin("fixed",31)
+ #arrow_node.setDepthWrite(False)
+ #arrow_node.setDepthTest(True)
+
+ # 保存材质引用以便后续修改
+ 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.setBin("fixed", 30)
+ #axis_node.setDepthWrite(False)
+ #axis_node.setDepthTest(True)
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
@@ -971,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:
# 获取坐标轴中心的世界坐标
@@ -1015,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)
@@ -1045,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):
"""检测鼠标悬停的轴 - 提取为独立方法"""
@@ -1142,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)}")
@@ -1203,33 +1157,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)
@@ -1237,28 +1181,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
@@ -1267,29 +1228,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":
@@ -1332,7 +1318,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:
@@ -1343,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
@@ -1396,3 +1386,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/gui/__pycache__/__init__.cpython-310.pyc b/gui/__pycache__/__init__.cpython-310.pyc
deleted file mode 100644
index d7606244..00000000
Binary files a/gui/__pycache__/__init__.cpython-310.pyc and /dev/null differ
diff --git a/gui/__pycache__/__init__.cpython-312.pyc b/gui/__pycache__/__init__.cpython-312.pyc
deleted file mode 100644
index 3b169f9f..00000000
Binary files a/gui/__pycache__/__init__.cpython-312.pyc and /dev/null differ
diff --git a/gui/__pycache__/gui_manager.cpython-310.pyc b/gui/__pycache__/gui_manager.cpython-310.pyc
deleted file mode 100644
index 11afed32..00000000
Binary files a/gui/__pycache__/gui_manager.cpython-310.pyc and /dev/null differ
diff --git a/gui/__pycache__/gui_manager.cpython-312.pyc b/gui/__pycache__/gui_manager.cpython-312.pyc
deleted file mode 100644
index da872ace..00000000
Binary files a/gui/__pycache__/gui_manager.cpython-312.pyc and /dev/null differ
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/project/__pycache__/__init__.cpython-310.pyc b/project/__pycache__/__init__.cpython-310.pyc
deleted file mode 100644
index 21d010dd..00000000
Binary files a/project/__pycache__/__init__.cpython-310.pyc and /dev/null differ
diff --git a/project/__pycache__/__init__.cpython-312.pyc b/project/__pycache__/__init__.cpython-312.pyc
deleted file mode 100644
index 955a088f..00000000
Binary files a/project/__pycache__/__init__.cpython-312.pyc and /dev/null differ
diff --git a/project/__pycache__/project_manager.cpython-310.pyc b/project/__pycache__/project_manager.cpython-310.pyc
deleted file mode 100644
index 8ecaa7cf..00000000
Binary files a/project/__pycache__/project_manager.cpython-310.pyc and /dev/null differ
diff --git a/project/__pycache__/project_manager.cpython-312.pyc b/project/__pycache__/project_manager.cpython-312.pyc
deleted file mode 100644
index 2392bd85..00000000
Binary files a/project/__pycache__/project_manager.cpython-312.pyc and /dev/null differ
diff --git a/scene/__pycache__/__init__.cpython-310.pyc b/scene/__pycache__/__init__.cpython-310.pyc
deleted file mode 100644
index 7996ba5f..00000000
Binary files a/scene/__pycache__/__init__.cpython-310.pyc and /dev/null differ
diff --git a/scene/__pycache__/__init__.cpython-312.pyc b/scene/__pycache__/__init__.cpython-312.pyc
deleted file mode 100644
index 7f95a56c..00000000
Binary files a/scene/__pycache__/__init__.cpython-312.pyc and /dev/null differ
diff --git a/scene/__pycache__/scene_manager.cpython-310.pyc b/scene/__pycache__/scene_manager.cpython-310.pyc
deleted file mode 100644
index 3312c1a2..00000000
Binary files a/scene/__pycache__/scene_manager.cpython-310.pyc and /dev/null differ
diff --git a/scene/__pycache__/scene_manager.cpython-312.pyc b/scene/__pycache__/scene_manager.cpython-312.pyc
deleted file mode 100644
index 17a23d1f..00000000
Binary files a/scene/__pycache__/scene_manager.cpython-312.pyc and /dev/null differ
diff --git a/scene/__pycache__/util.cpython-310.pyc b/scene/__pycache__/util.cpython-310.pyc
deleted file mode 100644
index 3bec4950..00000000
Binary files a/scene/__pycache__/util.cpython-310.pyc and /dev/null differ
diff --git a/scene/__pycache__/util.cpython-312.pyc b/scene/__pycache__/util.cpython-312.pyc
deleted file mode 100644
index e1661675..00000000
Binary files a/scene/__pycache__/util.cpython-312.pyc and /dev/null differ
diff --git a/scene/scene_manager.py b/scene/scene_manager.py
index f814efec..75671fe2 100644
--- a/scene/scene_manager.py
+++ b/scene/scene_manager.py
@@ -631,6 +631,7 @@ class SceneManager:
# 将碰撞节点附加到模型上
cNodePath = model.attachNewNode(cNode)
+ #cNodePath.hide()
# cNodePath.show() # 取消注释可以显示碰撞体,用于调试
# ==================== 场景树管理 ====================
diff --git a/scripts/__pycache__/BouncerScript.cpython-310.pyc b/scripts/__pycache__/BouncerScript.cpython-310.pyc
deleted file mode 100644
index f09940c6..00000000
Binary files a/scripts/__pycache__/BouncerScript.cpython-310.pyc and /dev/null differ
diff --git a/scripts/__pycache__/BouncerScript.cpython-312.pyc b/scripts/__pycache__/BouncerScript.cpython-312.pyc
deleted file mode 100644
index 4ba6c380..00000000
Binary files a/scripts/__pycache__/BouncerScript.cpython-312.pyc and /dev/null differ
diff --git a/scripts/__pycache__/ColorChangerScript.cpython-310.pyc b/scripts/__pycache__/ColorChangerScript.cpython-310.pyc
deleted file mode 100644
index 6fcd59e2..00000000
Binary files a/scripts/__pycache__/ColorChangerScript.cpython-310.pyc and /dev/null differ
diff --git a/scripts/__pycache__/ColorChangerScript.cpython-312.pyc b/scripts/__pycache__/ColorChangerScript.cpython-312.pyc
deleted file mode 100644
index a4dbe18b..00000000
Binary files a/scripts/__pycache__/ColorChangerScript.cpython-312.pyc and /dev/null differ
diff --git a/scripts/__pycache__/ComboAnimatorScript.cpython-310.pyc b/scripts/__pycache__/ComboAnimatorScript.cpython-310.pyc
deleted file mode 100644
index 35358cce..00000000
Binary files a/scripts/__pycache__/ComboAnimatorScript.cpython-310.pyc and /dev/null differ
diff --git a/scripts/__pycache__/ComboAnimatorScript.cpython-312.pyc b/scripts/__pycache__/ComboAnimatorScript.cpython-312.pyc
deleted file mode 100644
index 6e06256a..00000000
Binary files a/scripts/__pycache__/ComboAnimatorScript.cpython-312.pyc and /dev/null differ
diff --git a/scripts/__pycache__/FollowerScript.cpython-310.pyc b/scripts/__pycache__/FollowerScript.cpython-310.pyc
deleted file mode 100644
index 9a70ece7..00000000
Binary files a/scripts/__pycache__/FollowerScript.cpython-310.pyc and /dev/null differ
diff --git a/scripts/__pycache__/FollowerScript.cpython-312.pyc b/scripts/__pycache__/FollowerScript.cpython-312.pyc
deleted file mode 100644
index 148e128a..00000000
Binary files a/scripts/__pycache__/FollowerScript.cpython-312.pyc and /dev/null differ
diff --git a/scripts/__pycache__/MoverScript.cpython-310.pyc b/scripts/__pycache__/MoverScript.cpython-310.pyc
deleted file mode 100644
index 6ad7ee14..00000000
Binary files a/scripts/__pycache__/MoverScript.cpython-310.pyc and /dev/null differ
diff --git a/scripts/__pycache__/MoverScript.cpython-312.pyc b/scripts/__pycache__/MoverScript.cpython-312.pyc
deleted file mode 100644
index db031c12..00000000
Binary files a/scripts/__pycache__/MoverScript.cpython-312.pyc and /dev/null differ
diff --git a/scripts/__pycache__/R_P.cpython-312.pyc b/scripts/__pycache__/R_P.cpython-312.pyc
deleted file mode 100644
index 13e428c4..00000000
Binary files a/scripts/__pycache__/R_P.cpython-312.pyc and /dev/null differ
diff --git a/scripts/__pycache__/R_R.cpython-312.pyc b/scripts/__pycache__/R_R.cpython-312.pyc
deleted file mode 100644
index cb78e9a1..00000000
Binary files a/scripts/__pycache__/R_R.cpython-312.pyc and /dev/null differ
diff --git a/scripts/__pycache__/Rotate_H_Script.cpython-312.pyc b/scripts/__pycache__/Rotate_H_Script.cpython-312.pyc
deleted file mode 100644
index 19c94938..00000000
Binary files a/scripts/__pycache__/Rotate_H_Script.cpython-312.pyc and /dev/null differ
diff --git a/scripts/__pycache__/Rotate_P_Script.cpython-312.pyc b/scripts/__pycache__/Rotate_P_Script.cpython-312.pyc
deleted file mode 100644
index 53889503..00000000
Binary files a/scripts/__pycache__/Rotate_P_Script.cpython-312.pyc and /dev/null differ
diff --git a/scripts/__pycache__/Rotate_R_Script.cpython-312.pyc b/scripts/__pycache__/Rotate_R_Script.cpython-312.pyc
deleted file mode 100644
index 433fef48..00000000
Binary files a/scripts/__pycache__/Rotate_R_Script.cpython-312.pyc and /dev/null differ
diff --git a/scripts/__pycache__/RotatorScript.cpython-310.pyc b/scripts/__pycache__/RotatorScript.cpython-310.pyc
deleted file mode 100644
index 0ca41005..00000000
Binary files a/scripts/__pycache__/RotatorScript.cpython-310.pyc and /dev/null differ
diff --git a/scripts/__pycache__/RotatorScript.cpython-312.pyc b/scripts/__pycache__/RotatorScript.cpython-312.pyc
deleted file mode 100644
index 39213b91..00000000
Binary files a/scripts/__pycache__/RotatorScript.cpython-312.pyc and /dev/null differ
diff --git a/scripts/__pycache__/ScalerScript.cpython-310.pyc b/scripts/__pycache__/ScalerScript.cpython-310.pyc
deleted file mode 100644
index bca9cb46..00000000
Binary files a/scripts/__pycache__/ScalerScript.cpython-310.pyc and /dev/null differ
diff --git a/scripts/__pycache__/ScalerScript.cpython-312.pyc b/scripts/__pycache__/ScalerScript.cpython-312.pyc
deleted file mode 100644
index b325ba70..00000000
Binary files a/scripts/__pycache__/ScalerScript.cpython-312.pyc and /dev/null differ
diff --git a/scripts/__pycache__/TestMover.cpython-310.pyc b/scripts/__pycache__/TestMover.cpython-310.pyc
deleted file mode 100644
index 18a9edff..00000000
Binary files a/scripts/__pycache__/TestMover.cpython-310.pyc and /dev/null differ
diff --git a/scripts/__pycache__/TestMover.cpython-312.pyc b/scripts/__pycache__/TestMover.cpython-312.pyc
deleted file mode 100644
index 7c3275c4..00000000
Binary files a/scripts/__pycache__/TestMover.cpython-312.pyc and /dev/null differ
diff --git a/scripts/__pycache__/TestRotator.cpython-310.pyc b/scripts/__pycache__/TestRotator.cpython-310.pyc
deleted file mode 100644
index 50a0e8ef..00000000
Binary files a/scripts/__pycache__/TestRotator.cpython-310.pyc and /dev/null differ
diff --git a/scripts/__pycache__/TestRotator.cpython-312.pyc b/scripts/__pycache__/TestRotator.cpython-312.pyc
deleted file mode 100644
index a86d722e..00000000
Binary files a/scripts/__pycache__/TestRotator.cpython-312.pyc and /dev/null differ
diff --git a/scripts/__pycache__/TestScaler.cpython-310.pyc b/scripts/__pycache__/TestScaler.cpython-310.pyc
deleted file mode 100644
index b8ff9eeb..00000000
Binary files a/scripts/__pycache__/TestScaler.cpython-310.pyc and /dev/null differ
diff --git a/scripts/__pycache__/TestScaler.cpython-312.pyc b/scripts/__pycache__/TestScaler.cpython-312.pyc
deleted file mode 100644
index c41784f9..00000000
Binary files a/scripts/__pycache__/TestScaler.cpython-312.pyc and /dev/null differ
diff --git a/scripts/__pycache__/example_script.cpython-310.pyc b/scripts/__pycache__/example_script.cpython-310.pyc
deleted file mode 100644
index a9ff8b06..00000000
Binary files a/scripts/__pycache__/example_script.cpython-310.pyc and /dev/null differ
diff --git a/scripts/__pycache__/example_script.cpython-312.pyc b/scripts/__pycache__/example_script.cpython-312.pyc
deleted file mode 100644
index d7825c25..00000000
Binary files a/scripts/__pycache__/example_script.cpython-312.pyc and /dev/null differ
diff --git a/scripts/__pycache__/test.cpython-310.pyc b/scripts/__pycache__/test.cpython-310.pyc
deleted file mode 100644
index 8e0f5e88..00000000
Binary files a/scripts/__pycache__/test.cpython-310.pyc and /dev/null differ
diff --git a/scripts/__pycache__/test_quick_script.cpython-310.pyc b/scripts/__pycache__/test_quick_script.cpython-310.pyc
deleted file mode 100644
index 4f6c08ab..00000000
Binary files a/scripts/__pycache__/test_quick_script.cpython-310.pyc and /dev/null differ
diff --git a/scripts/__pycache__/test_quick_script.cpython-312.pyc b/scripts/__pycache__/test_quick_script.cpython-312.pyc
deleted file mode 100644
index 62416a8a..00000000
Binary files a/scripts/__pycache__/test_quick_script.cpython-312.pyc and /dev/null differ
diff --git a/ui/interface_manager.py b/ui/interface_manager.py
index 3a6d22a6..b3fbd8f3 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
@@ -26,8 +27,11 @@ class InterfaceManager:
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:
@@ -36,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:
@@ -77,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))
@@ -94,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()
@@ -239,4 +267,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 5736cdb0..d2c51790 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
@@ -69,6 +70,20 @@ 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)
+ self._syncSceneVisibility()
+ def _syncSceneVisibility(self):
+ scene_root = self.world.render
+ self._syncEffectiveVisibility(scene_root)
+
+
def updatePropertyPanel(self, item):
"""更新属性面板显示"""
if not self._propertyLayout or not self._propertyLayout.parent():
@@ -91,15 +106,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)
@@ -130,6 +164,59 @@ class PropertyPanelManager:
if propertyWidget:
propertyWidget.update()
+ def _setUserVisible(self,node,visible):
+ node.setPythonTag("user_visible",visible)
+ self._syncEffectiveVisibility(node)
+
+ def _syncEffectiveVisibility(self, start_node):
+ """广度优先,确保父隐藏则子一定隐藏"""
+ # 获取起始节点的父节点
+ 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()
+ user = node.getPythonTag("user_visible")
+ if user is None:
+ user = True
+ eff = parent_eff and user
+ node.setPythonTag("effective_visible", eff)
+
+ # 特殊处理:检查是否为碰撞体节点
+
+ if node.getName().startswith("modelCollision_"):
+ node.hide()
+ else:
+ 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)
+
+ collision_nodes = model.findAllMatches("**/modelCollision_*")
+ for collision_node in collision_nodes:
+ collision_node.hide()
+
+ except Exception as e:
+ print(f"切换模型可见性失败: {str(e)}")
+
def refreshModelValues(self, nodePath):
"""实时刷新模型所有属性数值(相对/世界位置、旋转、缩放)"""
if not nodePath or self._propertyLayout is None:
@@ -208,16 +295,31 @@ 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())
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")
@@ -242,7 +344,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())
@@ -261,7 +363,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())
@@ -332,6 +434,50 @@ class PropertyPanelManager:
# 材质属性组
self._updateModelMaterialPanel(model)
+ def refreshModelValues(self,nodePath):
+ if not nodePath or self._propertyLayout is None:
+ return
+ parent = nodePath.getParent()
+ render = self.world.render
+ relPos = nodePath.getPos(parent) if parent else nodePath.getPos()
+ if hasattr(self,'pos_x') and self.pos_x:
+ self.pos_x.blockSignals(True)
+ self.pos_x.setValue(relPos.getX())
+ self.pos_x.blockSignals(False)
+ if hasattr(self,'pos_y') and self.pos_y:
+ self.pos_y.blockSignals(True)
+ self.pos_y.setValue(relPos.getY())
+ self.pos_y.blockSignals(False)
+ if hasattr(self,'pos_z') and self.pos_z:
+ self.pos_z.blockSignals(True)
+ self.pos_z.setValue(relPos.getZ())
+ self.pos_z.blockSignals(False)
+
+ worldPos = nodePath.getPos(render)
+ for axis,attr in zip(('x','y','z'),('world_pos_x','world_pos_y','world_pos_z')):
+ spin = getattr(self,attr,None)
+ if spin:
+ spin.blockSignals(True)
+ spin.setValue(getattr(worldPos,axis))
+ spin.blockSignals(False)
+
+ hpr = nodePath.getHpr()
+ for idx,(attr,val) in enumerate(zip(('rot_h','rot_p','rot_r'),hpr)):
+ spin = getattr(self,attr,None)
+ if spin:
+ spin.blockSignals(True)
+ spin.setValue(val)
+ spin.blockSignals(False)
+
+ scale = nodePath.getScale()
+ for axis,attr in zip(('x','y','z'),('scale_x','scale_y','scale_z')):
+ spin = getattr(self,attr,None)
+ if spin:
+ spin.blockSignals(True)
+ spin.setValue(getattr(scale,axis))
+ spin.blockSignals(False)
+
+
def updateGUIPropertyPanel(self, gui_element):
"""更新GUI元素属性面板"""
@@ -3892,7 +4038,6 @@ class PropertyPanelManager:
if preset_name in presets:
azimuth, altitude = presets[preset_name]
- # 更新滑块和数值框
# 更新滑块和数值框
self.azimuthSpinBox.blockSignals(True)
self.altitudeSpinBox.blockSignals(True)
@@ -4147,14 +4292,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("此模型无动画")
@@ -4826,459 +4963,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()
- tab_widget.setMinimumWidth(200) # 设置最小宽度
-
- for anim_type, anim_data in animations.items():
- tab = QWidget()
- tab_layout = QGridLayout(tab) # 改为QGridLayout保持一致
- self._buildAnimationTypeUI(tab_layout, origin_model, anim_type, anim_data)
- tab_widget.addTab(tab, self._getAnimTypeDisplayName(anim_type))
-
- layout.addWidget(QLabel("动画类型:"), 1, 0)
- layout.addWidget(tab_widget, 1, 1, 1, 3)
- else:
- # 只有一种类型,直接显示
- anim_type, anim_data = next(iter(animations.items()))
- self._buildAnimationTypeUI(layout, 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
-
- current_row = layout.rowCount()
-
- if anim_type == 'transform':
- # 变换动画控制
- self.ns_transform_combo = QComboBox()
- self.ns_transform_combo.addItems(anim_data['names'])
- self.ns_transform_combo.setMinimumWidth(80)
- layout.addWidget(QLabel("变换动画:"), current_row, 0)
- layout.addWidget(self.ns_transform_combo, current_row, 1, 1, 3)
- current_row += 1
-
- elif anim_type == 'texture':
- # 纹理动画控制
- self.ns_texture_combo = QComboBox()
- self.ns_texture_combo.addItems(anim_data['stages'])
- self.ns_texture_combo.setMinimumWidth(80)
- layout.addWidget(QLabel("纹理动画:"), current_row, 0)
- layout.addWidget(self.ns_texture_combo, current_row, 1, 1, 3)
- current_row += 1
-
- elif anim_type == 'material':
- # 材质动画控制
- self.ns_material_combo = QComboBox()
- self.ns_material_combo.addItems(anim_data['properties'])
- self.ns_material_combo.setMinimumWidth(80)
- layout.addWidget(QLabel("材质动画:"), current_row, 0)
- layout.addWidget(self.ns_material_combo, current_row, 1, 1, 3)
- current_row += 1
-
- elif anim_type == 'lerp':
- # Lerp动画控制
- self.ns_lerp_combo = QComboBox()
- self.ns_lerp_combo.addItems(anim_data['intervals'])
- self.ns_lerp_combo.setMinimumWidth(80)
- layout.addWidget(QLabel("Lerp动画:"), current_row, 0)
- layout.addWidget(self.ns_lerp_combo, current_row, 1, 1, 3)
- current_row += 1
-
- # 通用控制按钮
- btn_box = QWidget()
- btn_lay = QHBoxLayout(btn_box)
- btn_lay.setContentsMargins(0, 0, 0, 0)
- for txt, cmd in (("播放", "play"), ("暂停", "pause"), ("停止", "stop"), ("循环", "loop")):
- btn = QPushButton(txt)
- btn.setMinimumWidth(35) # 设置按钮最小宽度
- btn.setMaximumWidth(50) # 限制按钮最大宽度
- btn.clicked.connect(lambda _, c=cmd, t=anim_type: self._controlNonSkeletalAnimation(origin_model, t, c))
- btn_lay.addWidget(btn)
- layout.addWidget(QLabel("控制:"), current_row, 0)
- layout.addWidget(btn_box, current_row, 1, 1, 3)
- current_row += 1
-
- # 播放速度
- speed_spinbox = QDoubleSpinBox()
- speed_spinbox.setRange(0.1, 5.0)
- speed_spinbox.setSingleStep(0.1)
- speed_spinbox.setValue(1.0)
- speed_spinbox.setMinimumWidth(60)
- speed_spinbox.setMaximumWidth(80)
- speed_spinbox.valueChanged.connect(
- lambda v, t=anim_type: self._setNonSkeletalAnimationSpeed(origin_model, t, v))
- layout.addWidget(QLabel("播放速度:"), current_row, 0)
- layout.addWidget(speed_spinbox, current_row, 1, 1, 3)
-
- 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)
@@ -5313,20 +4998,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)
diff --git a/ui/widgets.py b/ui/widgets.py
index 00822588..52f97d1d 100644
--- a/ui/widgets.py
+++ b/ui/widgets.py
@@ -347,6 +347,24 @@ class CustomTreeWidget(QTreeWidget):
if not dragged_node or not target_node:
event.ignore()
return
+
+ # # 检查是否是有效的父子关系
+ # if self.isValidParentChild(dragged_item, target_item):
+ # # 保存当前的世界坐标
+ # world_pos = dragged_node.getPos(self.world.render)
+ #
+ # # 更新场景图中的父子关系
+ # dragged_node.wrtReparentTo(target_node)
+ #
+ # # 接受拖放事件,更新树形控件
+ # super().dropEvent(event)
+ #
+ # #self.world.property_panel.updateNodeVisibilityAfterDrag(dragged_item)
+ # # 更新属性面板
+ # self.world.updatePropertyPanel(dragged_item)
+ # self.world.property_panel._syncEffectiveVisibility(dragged_node)
+
+
print(f"dragged_node: {dragged_node}, target_node: {target_node}")
@@ -394,7 +412,7 @@ class CustomTreeWidget(QTreeWidget):
# 事后验证:确保节点仍在"场景"根节点下
self._ensureUnderSceneRoot(dragged_item)
-
+ self.world.property_panel._syncEffectiveVisibility(dragged_node)
event.accept()
# try: