灯光移动问题修复,模型动画可以正常播放
This commit is contained in:
parent
69d83c6ab5
commit
405e8a9ad3
@ -373,6 +373,7 @@ class TransformGizmo(DirectObject):
|
|||||||
old_scale = action.get("old_scale")
|
old_scale = action.get("old_scale")
|
||||||
if old_scale is not None:
|
if old_scale is not None:
|
||||||
node.setScale(old_scale)
|
node.setScale(old_scale)
|
||||||
|
self._sync_light_position_if_needed(node)
|
||||||
self._redo_history.append(action)
|
self._redo_history.append(action)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@ -402,6 +403,7 @@ class TransformGizmo(DirectObject):
|
|||||||
if new_scale is not None:
|
if new_scale is not None:
|
||||||
node.setScale(new_scale)
|
node.setScale(new_scale)
|
||||||
|
|
||||||
|
self._sync_light_position_if_needed(node)
|
||||||
self._history.append(action)
|
self._history.append(action)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@ -464,6 +466,28 @@ class TransformGizmo(DirectObject):
|
|||||||
# New user action invalidates redo chain.
|
# New user action invalidates redo chain.
|
||||||
self._redo_history.clear()
|
self._redo_history.clear()
|
||||||
|
|
||||||
|
def _sync_light_position_if_needed(self, node: Optional[NodePath]) -> None:
|
||||||
|
"""When target node wraps an RP light, keep RP light position in sync."""
|
||||||
|
try:
|
||||||
|
if node is None or node.isEmpty() or (not node.hasPythonTag("rp_light_object")):
|
||||||
|
return
|
||||||
|
light_obj = node.getPythonTag("rp_light_object")
|
||||||
|
if not light_obj:
|
||||||
|
return
|
||||||
|
world_pos = node.getPos(self.world.render)
|
||||||
|
try:
|
||||||
|
light_obj.setPos(world_pos)
|
||||||
|
except Exception:
|
||||||
|
try:
|
||||||
|
light_obj.setPos(world_pos.x, world_pos.y, world_pos.z)
|
||||||
|
except Exception:
|
||||||
|
try:
|
||||||
|
light_obj.pos = world_pos
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
# ------------------------------------------------------------------ #
|
# ------------------------------------------------------------------ #
|
||||||
# Input helpers (hotkeys / mouse states)
|
# Input helpers (hotkeys / mouse states)
|
||||||
# ------------------------------------------------------------------ #
|
# ------------------------------------------------------------------ #
|
||||||
|
|||||||
@ -616,24 +616,40 @@ class CompositeCommand(Command):
|
|||||||
for command in self.commands:
|
for command in self.commands:
|
||||||
command.redo()
|
command.redo()
|
||||||
|
|
||||||
class MoveLightCommand(Command):
|
class MoveLightCommand(Command):
|
||||||
def __init__(self, node, old_pos, new_pos, light_object=None):
|
def __init__(self, node, old_pos, new_pos, light_object=None):
|
||||||
self.node = node
|
self.node = node
|
||||||
self.old_pos = Point3(old_pos)
|
self.old_pos = Point3(old_pos)
|
||||||
self.new_pos = Point3(new_pos)
|
self.new_pos = Point3(new_pos)
|
||||||
self.light_object = light_object
|
self.light_object = light_object
|
||||||
|
|
||||||
def execute(self): # 将原来的 do() 改为 execute()
|
def _apply_light_position(self, pos):
|
||||||
if self.light_object:
|
if not self.light_object:
|
||||||
self.light_object.pos = self.new_pos
|
return
|
||||||
if self.node:
|
try:
|
||||||
self.node.setPos(self.new_pos)
|
self.light_object.setPos(pos)
|
||||||
|
return
|
||||||
def undo(self):
|
except Exception:
|
||||||
if self.light_object:
|
pass
|
||||||
self.light_object.pos = self.old_pos
|
try:
|
||||||
if self.node:
|
self.light_object.setPos(pos.x, pos.y, pos.z)
|
||||||
self.node.setPos(self.old_pos)
|
return
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
self.light_object.pos = Point3(pos)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def execute(self): # 将原来的 do() 改为 execute()
|
||||||
|
self._apply_light_position(self.new_pos)
|
||||||
|
if self.node:
|
||||||
|
self.node.setPos(self.new_pos)
|
||||||
|
|
||||||
|
def undo(self):
|
||||||
|
self._apply_light_position(self.old_pos)
|
||||||
|
if self.node:
|
||||||
|
self.node.setPos(self.old_pos)
|
||||||
|
|
||||||
def redo(self):
|
def redo(self):
|
||||||
self.execute() # 调用 execute() 而不是 do()
|
self.execute() # 调用 execute() 而不是 do()
|
||||||
|
|||||||
@ -122,6 +122,46 @@ class SelectionSystem:
|
|||||||
tg = getattr(self.world, "newTransform", None)
|
tg = getattr(self.world, "newTransform", None)
|
||||||
return tg is not None
|
return tg is not None
|
||||||
|
|
||||||
|
def _sync_rp_light_position(self, light_node, light_object=None):
|
||||||
|
"""同步灯光包装节点与 RenderPipeline 灯光对象位置。"""
|
||||||
|
try:
|
||||||
|
if not light_node or light_node.isEmpty():
|
||||||
|
return False
|
||||||
|
if light_object is None:
|
||||||
|
light_object = light_node.getPythonTag("rp_light_object")
|
||||||
|
if not light_object and light_node.hasTag("light_type"):
|
||||||
|
# 兼容旧数据:节点存在 light_type 但未绑定 rp_light_object 时尝试重建绑定
|
||||||
|
scene_manager = getattr(self.world, "scene_manager", None)
|
||||||
|
if scene_manager:
|
||||||
|
try:
|
||||||
|
light_type = light_node.getTag("light_type")
|
||||||
|
if light_type == "spot_light" and hasattr(scene_manager, "_recreateSpotLight"):
|
||||||
|
scene_manager._recreateSpotLight(light_node)
|
||||||
|
elif light_type == "point_light" and hasattr(scene_manager, "_recreatePointLight"):
|
||||||
|
scene_manager._recreatePointLight(light_node)
|
||||||
|
light_object = light_node.getPythonTag("rp_light_object")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
if not light_object:
|
||||||
|
return False
|
||||||
|
|
||||||
|
world_pos = light_node.getPos(self.world.render)
|
||||||
|
|
||||||
|
# 优先使用 RP Light 的 setPos 接口
|
||||||
|
try:
|
||||||
|
light_object.setPos(world_pos)
|
||||||
|
except Exception:
|
||||||
|
try:
|
||||||
|
light_object.setPos(world_pos.x, world_pos.y, world_pos.z)
|
||||||
|
except Exception:
|
||||||
|
try:
|
||||||
|
light_object.pos = Point3(world_pos)
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
def sync_transform_gizmo_mode(self):
|
def sync_transform_gizmo_mode(self):
|
||||||
"""Sync TransformGizmo mode with current tool."""
|
"""Sync TransformGizmo mode with current tool."""
|
||||||
if not self._has_new_transform_gizmo():
|
if not self._has_new_transform_gizmo():
|
||||||
@ -738,9 +778,9 @@ class SelectionSystem:
|
|||||||
|
|
||||||
light_object = self.gizmoTarget.getPythonTag("rp_light_object")
|
light_object = self.gizmoTarget.getPythonTag("rp_light_object")
|
||||||
if light_object:
|
if light_object:
|
||||||
light_pos = light_object.pos
|
# 以节点位置为真值并回写 RP 灯光,避免“手柄能动但灯光不动”
|
||||||
self.gizmo.setPos(light_object.pos)
|
self._sync_rp_light_position(self.gizmoTarget, light_object)
|
||||||
self.gizmoTarget.setPos(light_pos)
|
self.gizmo.setPos(self.gizmoTarget.getPos(self.world.render))
|
||||||
else:
|
else:
|
||||||
# 只在必要时更新位置和朝向
|
# 只在必要时更新位置和朝向
|
||||||
self._updateGizmoPositionAndOrientation()
|
self._updateGizmoPositionAndOrientation()
|
||||||
@ -1530,7 +1570,8 @@ class SelectionSystem:
|
|||||||
|
|
||||||
light_object = self.gizmoTarget.getPythonTag("rp_light_object")
|
light_object = self.gizmoTarget.getPythonTag("rp_light_object")
|
||||||
if light_object:
|
if light_object:
|
||||||
self.gizmoTargetStartPos = Point3(light_object.pos)
|
# 起始位置统一使用节点世界坐标,避免依赖 light_object.pos 的陈旧值
|
||||||
|
self.gizmoTargetStartPos = Point3(self.gizmoTarget.getPos(self.world.render))
|
||||||
else:
|
else:
|
||||||
self.gizmoTargetStartPos = self.gizmoTarget.getPos()
|
self.gizmoTargetStartPos = self.gizmoTarget.getPos()
|
||||||
|
|
||||||
@ -1818,8 +1859,8 @@ class SelectionSystem:
|
|||||||
# 应用新位置到目标节点
|
# 应用新位置到目标节点
|
||||||
light_object = self.gizmoTarget.getPythonTag("rp_light_object")
|
light_object = self.gizmoTarget.getPythonTag("rp_light_object")
|
||||||
if light_object:
|
if light_object:
|
||||||
light_object.pos = newPos
|
|
||||||
self.gizmoTarget.setPos(newPos)
|
self.gizmoTarget.setPos(newPos)
|
||||||
|
self._sync_rp_light_position(self.gizmoTarget, light_object)
|
||||||
print(f"🔄 光源拖拽移动: {currentPos} -> {newPos}")
|
print(f"🔄 光源拖拽移动: {currentPos} -> {newPos}")
|
||||||
else:
|
else:
|
||||||
self.gizmoTarget.setPos(newPos)
|
self.gizmoTarget.setPos(newPos)
|
||||||
|
|||||||
71
main.py
71
main.py
@ -154,6 +154,7 @@ class MyWorld(CoreWorld):
|
|||||||
|
|
||||||
# 新的坐标系
|
# 新的坐标系
|
||||||
self.newTransform = TransformGizmo(self)
|
self.newTransform = TransformGizmo(self)
|
||||||
|
self._setup_transform_gizmo_light_sync()
|
||||||
|
|
||||||
# 初始化视频管理
|
# 初始化视频管理
|
||||||
if VideoManager is not None:
|
if VideoManager is not None:
|
||||||
@ -816,6 +817,76 @@ class MyWorld(CoreWorld):
|
|||||||
# 顶部工具栏
|
# 顶部工具栏
|
||||||
if self.showToolbar:
|
if self.showToolbar:
|
||||||
self._draw_toolbar()
|
self._draw_toolbar()
|
||||||
|
|
||||||
|
def _sync_rp_light_from_node(self, node):
|
||||||
|
"""将灯光包装节点的位置同步到 RenderPipeline 灯光对象。"""
|
||||||
|
try:
|
||||||
|
if not node or node.isEmpty() or (not node.hasPythonTag("rp_light_object")):
|
||||||
|
# 兼容旧场景:仅有 light_type 标签时尝试补绑 rp_light_object
|
||||||
|
if not node or node.isEmpty() or (not node.hasTag("light_type")):
|
||||||
|
return False
|
||||||
|
scene_manager = getattr(self, "scene_manager", None)
|
||||||
|
if scene_manager:
|
||||||
|
try:
|
||||||
|
light_type = node.getTag("light_type")
|
||||||
|
if light_type == "spot_light" and hasattr(scene_manager, "_recreateSpotLight"):
|
||||||
|
scene_manager._recreateSpotLight(node)
|
||||||
|
elif light_type == "point_light" and hasattr(scene_manager, "_recreatePointLight"):
|
||||||
|
scene_manager._recreatePointLight(node)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
light_obj = node.getPythonTag("rp_light_object") if node.hasPythonTag("rp_light_object") else None
|
||||||
|
if not light_obj:
|
||||||
|
return False
|
||||||
|
|
||||||
|
world_pos = node.getPos(self.render)
|
||||||
|
try:
|
||||||
|
light_obj.setPos(world_pos)
|
||||||
|
except Exception:
|
||||||
|
try:
|
||||||
|
light_obj.setPos(world_pos.x, world_pos.y, world_pos.z)
|
||||||
|
except Exception:
|
||||||
|
try:
|
||||||
|
light_obj.pos = Point3(world_pos)
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _on_transform_gizmo_drag_event(self, payload):
|
||||||
|
"""TransformGizmo 拖拽事件回调:实时同步灯光位置。"""
|
||||||
|
try:
|
||||||
|
node = payload.get("target") if isinstance(payload, dict) else None
|
||||||
|
if node and (not node.isEmpty()):
|
||||||
|
self._sync_rp_light_from_node(node)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _setup_transform_gizmo_light_sync(self):
|
||||||
|
"""为 newTransform 注册灯光同步事件钩子。"""
|
||||||
|
tg = getattr(self, "newTransform", None)
|
||||||
|
if not tg:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
from TransformGizmo.events import GizmoEvent
|
||||||
|
hooks = {
|
||||||
|
"move": {
|
||||||
|
GizmoEvent.DRAG_MOVE: [self._on_transform_gizmo_drag_event],
|
||||||
|
GizmoEvent.DRAG_END: [self._on_transform_gizmo_drag_event],
|
||||||
|
},
|
||||||
|
"rotate": {
|
||||||
|
GizmoEvent.DRAG_MOVE: [self._on_transform_gizmo_drag_event],
|
||||||
|
GizmoEvent.DRAG_END: [self._on_transform_gizmo_drag_event],
|
||||||
|
},
|
||||||
|
"scale": {
|
||||||
|
GizmoEvent.DRAG_MOVE: [self._on_transform_gizmo_drag_event],
|
||||||
|
GizmoEvent.DRAG_END: [self._on_transform_gizmo_drag_event],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
tg.set_event_hooks(hooks, replace=False)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"绑定 TransformGizmo 灯光同步事件失败: {e}")
|
||||||
|
|
||||||
def _draw_menu_bar(self):
|
def _draw_menu_bar(self):
|
||||||
self.editor_panels.draw_menu_bar()
|
self.editor_panels.draw_menu_bar()
|
||||||
|
|||||||
@ -227,6 +227,12 @@ class SceneManager:
|
|||||||
if normalize_scales:
|
if normalize_scales:
|
||||||
model.setTag("scale_normalization_applied", "true")
|
model.setTag("scale_normalization_applied", "true")
|
||||||
|
|
||||||
|
# 初始化动画标签,避免属性面板首次读取时误判“无动画”
|
||||||
|
try:
|
||||||
|
self._processModelAnimations(model)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"初始化模型动画标签失败: {e}")
|
||||||
|
|
||||||
# 添加到模型列表
|
# 添加到模型列表
|
||||||
self.models.append(model)
|
self.models.append(model)
|
||||||
|
|
||||||
@ -2086,10 +2092,15 @@ class SceneManager:
|
|||||||
def _processModelAnimations(self, model_node):
|
def _processModelAnimations(self, model_node):
|
||||||
"""处理模型动画,确保在场景加载时正确识别动画信息"""
|
"""处理模型动画,确保在场景加载时正确识别动画信息"""
|
||||||
try:
|
try:
|
||||||
# 检查模型是否已经有动画信息标签
|
# 已检测过则直接复用,避免重复开销
|
||||||
|
if model_node.hasTag("has_animations_checked"):
|
||||||
|
return model_node.hasTag("has_animations") and model_node.getTag("has_animations").lower() == "true"
|
||||||
|
|
||||||
|
# 检查模型是否已经有动画信息标签(兼容旧数据)
|
||||||
if model_node.hasTag("has_animations"):
|
if model_node.hasTag("has_animations"):
|
||||||
has_animations = model_node.getTag("has_animations").lower() == "true"
|
has_animations = model_node.getTag("has_animations").lower() == "true"
|
||||||
if has_animations:
|
if has_animations:
|
||||||
|
model_node.setTag("has_animations_checked", "true")
|
||||||
print(f"模型 {model_node.getName()} 已有动画信息")
|
print(f"模型 {model_node.getName()} 已有动画信息")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@ -2099,6 +2110,46 @@ class SceneManager:
|
|||||||
|
|
||||||
has_animations = (character_nodes.getNumPaths() > 0 or
|
has_animations = (character_nodes.getNumPaths() > 0 or
|
||||||
anim_bundle_nodes.getNumPaths() > 0)
|
anim_bundle_nodes.getNumPaths() > 0)
|
||||||
|
|
||||||
|
# 如果模型树中没检测到,再尝试通过 Actor 从文件路径检测
|
||||||
|
if not has_animations:
|
||||||
|
model_path = model_node.getTag("model_path") if model_node.hasTag("model_path") else ""
|
||||||
|
if model_path:
|
||||||
|
try:
|
||||||
|
from direct.actor.Actor import Actor
|
||||||
|
from panda3d.core import Filename
|
||||||
|
|
||||||
|
candidate_paths = [model_path]
|
||||||
|
candidate_paths.append(Filename.from_os_specific(model_path).get_fullpath())
|
||||||
|
try:
|
||||||
|
normalized = util.normalize_model_path(model_path)
|
||||||
|
if normalized:
|
||||||
|
candidate_paths.append(normalized)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
seen = set()
|
||||||
|
unique_paths = []
|
||||||
|
for p in candidate_paths:
|
||||||
|
if not p or p in seen:
|
||||||
|
continue
|
||||||
|
seen.add(p)
|
||||||
|
unique_paths.append(p)
|
||||||
|
|
||||||
|
for candidate in unique_paths:
|
||||||
|
try:
|
||||||
|
actor = Actor(candidate)
|
||||||
|
anim_names = actor.getAnimNames()
|
||||||
|
actor.cleanup()
|
||||||
|
actor.removeNode()
|
||||||
|
if anim_names:
|
||||||
|
print(f"通过 Actor 路径检测到动画: {candidate} -> {anim_names}")
|
||||||
|
has_animations = True
|
||||||
|
break
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
if has_animations:
|
if has_animations:
|
||||||
print(f"检测到模型 {model_node.getName()} 包含动画:")
|
print(f"检测到模型 {model_node.getName()} 包含动画:")
|
||||||
@ -2114,6 +2165,8 @@ class SceneManager:
|
|||||||
model_node.setTag("can_create_actor_from_memory", "true")
|
model_node.setTag("can_create_actor_from_memory", "true")
|
||||||
else:
|
else:
|
||||||
model_node.setTag("has_animations", "false")
|
model_node.setTag("has_animations", "false")
|
||||||
|
|
||||||
|
model_node.setTag("has_animations_checked", "true")
|
||||||
|
|
||||||
return has_animations
|
return has_animations
|
||||||
|
|
||||||
@ -2645,8 +2698,9 @@ class SceneManager:
|
|||||||
# 保存光源对象引用
|
# 保存光源对象引用
|
||||||
light_node.setPythonTag("rp_light_object", light)
|
light_node.setPythonTag("rp_light_object", light)
|
||||||
|
|
||||||
# 添加到管理列表
|
# 添加到管理列表(去重)
|
||||||
self.Spotlight.append(light_node)
|
if light_node not in self.Spotlight:
|
||||||
|
self.Spotlight.append(light_node)
|
||||||
|
|
||||||
# 确保灯光节点有正确的标签,以便在场景树更新时被识别
|
# 确保灯光节点有正确的标签,以便在场景树更新时被识别
|
||||||
if not light_node.hasTag("is_scene_element"):
|
if not light_node.hasTag("is_scene_element"):
|
||||||
@ -2703,8 +2757,9 @@ class SceneManager:
|
|||||||
# 保存光源对象引用
|
# 保存光源对象引用
|
||||||
light_node.setPythonTag("rp_light_object", light)
|
light_node.setPythonTag("rp_light_object", light)
|
||||||
|
|
||||||
# 添加到管理列表
|
# 添加到管理列表(去重)
|
||||||
self.Pointlight.append(light_node)
|
if light_node not in self.Pointlight:
|
||||||
|
self.Pointlight.append(light_node)
|
||||||
|
|
||||||
# 确保灯光节点有正确的标签,以便在场景树更新时被识别
|
# 确保灯光节点有正确的标签,以便在场景树更新时被识别
|
||||||
if not light_node.hasTag("is_scene_element"):
|
if not light_node.hasTag("is_scene_element"):
|
||||||
@ -2877,9 +2932,8 @@ class SceneManager:
|
|||||||
# 创建挂载节点 - 挂载到选中的父节点
|
# 创建挂载节点 - 挂载到选中的父节点
|
||||||
light_np = NodePath(light_name)
|
light_np = NodePath(light_name)
|
||||||
light_np.reparentTo(parent_node) # 挂载到父节点而不是render
|
light_np.reparentTo(parent_node) # 挂载到父节点而不是render
|
||||||
light_np.setPos(*pos)
|
|
||||||
|
|
||||||
light_np.setTransform(TransformState.makeIdentity())
|
light_np.setTransform(TransformState.makeIdentity())
|
||||||
|
light_np.setPos(*pos)
|
||||||
|
|
||||||
# 创建聚光灯对象
|
# 创建聚光灯对象
|
||||||
light = SpotLight()
|
light = SpotLight()
|
||||||
@ -2986,10 +3040,9 @@ class SceneManager:
|
|||||||
# 创建挂载节点 - 挂载到选中的父节点
|
# 创建挂载节点 - 挂载到选中的父节点
|
||||||
light_np = NodePath(light_name)
|
light_np = NodePath(light_name)
|
||||||
light_np.reparentTo(parent_node) # 挂载到父节点而不是render
|
light_np.reparentTo(parent_node) # 挂载到父节点而不是render
|
||||||
light_np.setPos(*pos)
|
|
||||||
|
|
||||||
# 确保变换矩阵有效
|
# 确保变换矩阵有效
|
||||||
light_np.setTransform(TransformState.makeIdentity())
|
light_np.setTransform(TransformState.makeIdentity())
|
||||||
|
light_np.setPos(*pos)
|
||||||
|
|
||||||
# 创建点光源对象
|
# 创建点光源对象
|
||||||
light = PointLight()
|
light = PointLight()
|
||||||
@ -4694,8 +4747,16 @@ except Exception as e:
|
|||||||
print(f"✓ RenderPipeline聚光灯创建成功,位置: {pos}")
|
print(f"✓ RenderPipeline聚光灯创建成功,位置: {pos}")
|
||||||
|
|
||||||
# 创建包装节点用于场景树显示
|
# 创建包装节点用于场景树显示
|
||||||
spotlight_node = self.world.render.attachNewNode("spotlight_wrapper")
|
light_name = f"Spotlight_{len(self.Spotlight)}"
|
||||||
spotlight_node.setPos(pos)
|
spotlight_node = self.world.render.attachNewNode(light_name)
|
||||||
|
spotlight_node.setPos(*pos)
|
||||||
|
spotlight_node.setTag("light_type", "spot_light")
|
||||||
|
spotlight_node.setTag("is_scene_element", "1")
|
||||||
|
spotlight_node.setTag("tree_item_type", "LIGHT_NODE")
|
||||||
|
spotlight_node.setTag("light_energy", str(getattr(spotlight, "energy", 5000)))
|
||||||
|
spotlight_node.setTag("created_by_user", "1")
|
||||||
|
spotlight_node.setTag("element_type", "spotlight")
|
||||||
|
spotlight_node.setPythonTag("rp_light_object", spotlight)
|
||||||
self.Spotlight.append(spotlight_node)
|
self.Spotlight.append(spotlight_node)
|
||||||
return spotlight_node
|
return spotlight_node
|
||||||
else:
|
else:
|
||||||
@ -4713,6 +4774,11 @@ except Exception as e:
|
|||||||
# 创建光源节点
|
# 创建光源节点
|
||||||
spotlight_node = self.world.render.attachNewNode(spotlight)
|
spotlight_node = self.world.render.attachNewNode(spotlight)
|
||||||
spotlight_node.setPos(pos)
|
spotlight_node.setPos(pos)
|
||||||
|
spotlight_node.setTag("light_type", "spot_light")
|
||||||
|
spotlight_node.setTag("is_scene_element", "1")
|
||||||
|
spotlight_node.setTag("tree_item_type", "LIGHT_NODE")
|
||||||
|
spotlight_node.setTag("created_by_user", "1")
|
||||||
|
spotlight_node.setTag("element_type", "spotlight")
|
||||||
|
|
||||||
# 设置聚光灯方向(向下照射)
|
# 设置聚光灯方向(向下照射)
|
||||||
spotlight_node.lookAt(pos[0], pos[1], pos[2] - 5) # 向下看5个单位
|
spotlight_node.lookAt(pos[0], pos[1], pos[2] - 5) # 向下看5个单位
|
||||||
@ -4769,8 +4835,16 @@ except Exception as e:
|
|||||||
print(f"✓ RenderPipeline点光源创建成功,位置: {pos}")
|
print(f"✓ RenderPipeline点光源创建成功,位置: {pos}")
|
||||||
|
|
||||||
# 创建包装节点用于场景树显示
|
# 创建包装节点用于场景树显示
|
||||||
pointlight_node = self.world.render.attachNewNode("pointlight_wrapper")
|
light_name = f"Pointlight_{len(self.Pointlight)}"
|
||||||
pointlight_node.setPos(pos)
|
pointlight_node = self.world.render.attachNewNode(light_name)
|
||||||
|
pointlight_node.setPos(*pos)
|
||||||
|
pointlight_node.setTag("light_type", "point_light")
|
||||||
|
pointlight_node.setTag("is_scene_element", "1")
|
||||||
|
pointlight_node.setTag("tree_item_type", "LIGHT_NODE")
|
||||||
|
pointlight_node.setTag("light_energy", str(getattr(pointlight, "energy", 3000)))
|
||||||
|
pointlight_node.setTag("created_by_user", "1")
|
||||||
|
pointlight_node.setTag("element_type", "pointlight")
|
||||||
|
pointlight_node.setPythonTag("rp_light_object", pointlight)
|
||||||
self.Pointlight.append(pointlight_node)
|
self.Pointlight.append(pointlight_node)
|
||||||
return pointlight_node
|
return pointlight_node
|
||||||
else:
|
else:
|
||||||
@ -4788,6 +4862,11 @@ except Exception as e:
|
|||||||
# 创建光源节点
|
# 创建光源节点
|
||||||
pointlight_node = self.world.render.attachNewNode(pointlight)
|
pointlight_node = self.world.render.attachNewNode(pointlight)
|
||||||
pointlight_node.setPos(pos)
|
pointlight_node.setPos(pos)
|
||||||
|
pointlight_node.setTag("light_type", "point_light")
|
||||||
|
pointlight_node.setTag("is_scene_element", "1")
|
||||||
|
pointlight_node.setTag("tree_item_type", "LIGHT_NODE")
|
||||||
|
pointlight_node.setTag("created_by_user", "1")
|
||||||
|
pointlight_node.setTag("element_type", "pointlight")
|
||||||
|
|
||||||
# 添加到光源列表
|
# 添加到光源列表
|
||||||
self.Pointlight.append(pointlight_node)
|
self.Pointlight.append(pointlight_node)
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -1054,11 +1054,21 @@ class AppActions:
|
|||||||
self.ssbo_editor.load_model(file_path)
|
self.ssbo_editor.load_model(file_path)
|
||||||
model_np = getattr(self.ssbo_editor, 'model', None)
|
model_np = getattr(self.ssbo_editor, 'model', None)
|
||||||
# Keep legacy ray-pick fallback usable by adding a collision body.
|
# Keep legacy ray-pick fallback usable by adding a collision body.
|
||||||
if model_np and hasattr(self, 'scene_manager') and self.scene_manager:
|
if model_np:
|
||||||
try:
|
# Apply vital tags manually since SSBO overrides SceneManager loader
|
||||||
self.scene_manager.setupCollision(model_np)
|
model_np.setTag("model_path", file_path)
|
||||||
except Exception as e:
|
model_np.setTag("original_path", file_path)
|
||||||
print(f"[SSBO] setupCollision failed: {e}")
|
model_np.setTag("is_model_root", "1")
|
||||||
|
model_np.setTag("is_scene_element", "1")
|
||||||
|
model_np.setTag("file", os.path.basename(file_path))
|
||||||
|
model_np.setName(os.path.basename(file_path))
|
||||||
|
|
||||||
|
if hasattr(self, 'scene_manager') and self.scene_manager:
|
||||||
|
try:
|
||||||
|
self.scene_manager.setupCollision(model_np)
|
||||||
|
self.scene_manager._processModelAnimations(model_np)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[SSBO] setup components failed: {e}")
|
||||||
return model_np
|
return model_np
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[SSBO] load_model failed: {e}")
|
print(f"[SSBO] load_model failed: {e}")
|
||||||
|
|||||||
@ -1102,29 +1102,41 @@ class EditorPanels:
|
|||||||
# 动画徽章(优化检测逻辑,避免重复创建Actor)
|
# 动画徽章(优化检测逻辑,避免重复创建Actor)
|
||||||
has_animation = False
|
has_animation = False
|
||||||
if node_type == "模型": # 只对模型类型进行动画检测
|
if node_type == "模型": # 只对模型类型进行动画检测
|
||||||
# 首先检查是否已经缓存了检测结果
|
model_path = node.getTag("model_path") if node.hasTag("model_path") else ""
|
||||||
cached_result = node.getPythonTag('animation')
|
likely_anim_format = bool(model_path and model_path.lower().endswith(('.glb', '.gltf', '.fbx', '.bam', '.egg')))
|
||||||
if cached_result is not None:
|
|
||||||
has_animation = cached_result
|
# 优先使用场景标签(导入/加载时会写入)
|
||||||
else:
|
if node.hasTag("has_animations"):
|
||||||
# 只有在未缓存时才进行检测
|
has_animation = node.getTag("has_animations").lower() == "true"
|
||||||
|
|
||||||
|
# 再做轻量结构检测(不依赖 Actor)
|
||||||
|
if not has_animation:
|
||||||
try:
|
try:
|
||||||
# 使用轻量级检测:先检查文件扩展名
|
has_character = node.findAllMatches("**/+Character").getNumPaths() > 0
|
||||||
model_path = node.getTag("model_path")
|
has_bundle = node.findAllMatches("**/+AnimBundleNode").getNumPaths() > 0
|
||||||
if model_path and model_path.lower().endswith(('.glb', '.gltf', '.fbx')):
|
has_animation = has_character or has_bundle
|
||||||
# 对于可能包含动画的格式,才进行Actor检测
|
if has_animation:
|
||||||
actor = self._getActor(node)
|
node.setTag("has_animations", "true")
|
||||||
if actor and actor.getAnimNames():
|
node.setTag("can_create_actor_from_memory", "true")
|
||||||
has_animation = True
|
except Exception:
|
||||||
# 缓存检测结果
|
pass
|
||||||
node.setPythonTag('animation', has_animation)
|
|
||||||
print(f"[动画检测] {node.getName()}: {'有动画' if has_animation else '无动画'}")
|
# 最后才尝试 Actor 检测(只缓存“有动画”,避免把失败结果永久缓存)
|
||||||
else:
|
cached_result = node.getPythonTag('animation')
|
||||||
# 对于不太可能有动画的格式,直接标记为无动画
|
if cached_result is True:
|
||||||
node.setPythonTag('animation', False)
|
has_animation = True
|
||||||
|
elif not has_animation and likely_anim_format:
|
||||||
|
try:
|
||||||
|
actor = self._getActor(node)
|
||||||
|
if actor and actor.getAnimNames():
|
||||||
|
has_animation = True
|
||||||
|
node.setTag("has_animations", "true")
|
||||||
|
node.setPythonTag('animation', True)
|
||||||
|
print(f"[动画检测] {node.getName()}: 有动画")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"动画检测失败: {e}")
|
print(f"动画检测失败: {e}")
|
||||||
node.setPythonTag('animation', False)
|
elif cached_result is False and not likely_anim_format:
|
||||||
|
has_animation = False
|
||||||
else:
|
else:
|
||||||
# 对于非模型类型,检查已有的动画标签
|
# 对于非模型类型,检查已有的动画标签
|
||||||
has_animation = hasattr(node, 'getPythonTag') and node.getPythonTag('animation')
|
has_animation = hasattr(node, 'getPythonTag') and node.getPythonTag('animation')
|
||||||
@ -1492,39 +1504,116 @@ class EditorPanels:
|
|||||||
|
|
||||||
def _draw_animation_properties(self, node):
|
def _draw_animation_properties(self, node):
|
||||||
"""绘制动画控制属性面板(优化版本,使用缓存避免重复计算)"""
|
"""绘制动画控制属性面板(优化版本,使用缓存避免重复计算)"""
|
||||||
|
anim_node = node
|
||||||
|
try:
|
||||||
|
if hasattr(self, "_resolve_animation_owner_model"):
|
||||||
|
resolved = self._resolve_animation_owner_model(node)
|
||||||
|
if resolved and not resolved.isEmpty():
|
||||||
|
anim_node = resolved
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 路径兜底:当 anim_node 缺少路径时,从 scene_manager.models 中反查祖先模型
|
||||||
|
try:
|
||||||
|
needs_path = (not anim_node.hasTag("model_path")) or (not anim_node.getTag("model_path"))
|
||||||
|
if needs_path and hasattr(self, "scene_manager") and self.scene_manager:
|
||||||
|
models = getattr(self.scene_manager, "models", [])
|
||||||
|
for model in list(models):
|
||||||
|
try:
|
||||||
|
if not model or model.isEmpty():
|
||||||
|
continue
|
||||||
|
if (model == anim_node or model.isAncestorOf(anim_node) or anim_node.isAncestorOf(model)):
|
||||||
|
if model.hasTag("model_path") and model.getTag("model_path"):
|
||||||
|
anim_node.setTag("model_path", model.getTag("model_path"))
|
||||||
|
if model.hasTag("original_path") and model.getTag("original_path"):
|
||||||
|
anim_node.setTag("original_path", model.getTag("original_path"))
|
||||||
|
break
|
||||||
|
if model.hasTag("saved_model_path") and model.getTag("saved_model_path"):
|
||||||
|
anim_node.setTag("model_path", model.getTag("saved_model_path"))
|
||||||
|
break
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 先刷新一次模型动画标签,避免“导入后未初始化”导致误判
|
||||||
|
try:
|
||||||
|
if hasattr(self, "scene_manager") and self.scene_manager and hasattr(self.scene_manager, "_processModelAnimations"):
|
||||||
|
self.scene_manager._processModelAnimations(anim_node)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
has_animation_tag = anim_node.hasTag("has_animations") and anim_node.getTag("has_animations").lower() == "true"
|
||||||
|
has_animation_nodes = False
|
||||||
|
try:
|
||||||
|
has_animation_nodes = (
|
||||||
|
anim_node.findAllMatches("**/+Character").getNumPaths() > 0 or
|
||||||
|
anim_node.findAllMatches("**/+AnimBundleNode").getNumPaths() > 0
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
# 检查是否已经缓存了动画信息
|
# 检查是否已经缓存了动画信息
|
||||||
cached_anim_info = node.getPythonTag("cached_anim_info")
|
cached_anim_info = anim_node.getPythonTag("cached_anim_info")
|
||||||
cached_processed_names = node.getPythonTag("cached_processed_names")
|
cached_processed_names = anim_node.getPythonTag("cached_processed_names")
|
||||||
|
|
||||||
|
# 如果之前缓存的是“格式未知”,但现在已有路径,强制重建缓存
|
||||||
|
try:
|
||||||
|
now_has_path = anim_node.hasTag("model_path") and bool(anim_node.getTag("model_path"))
|
||||||
|
if now_has_path and isinstance(cached_anim_info, str) and "格式: 未知" in cached_anim_info:
|
||||||
|
cached_anim_info = None
|
||||||
|
cached_processed_names = None
|
||||||
|
anim_node.setPythonTag("cached_anim_info", None)
|
||||||
|
anim_node.setPythonTag("cached_processed_names", None)
|
||||||
|
anim_node.setPythonTag("animation", None)
|
||||||
|
self._clear_animation_cache(anim_node)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 如果节点已被检测为有动画,但缓存是“无动画”,强制重新检测一次
|
||||||
|
if (has_animation_tag or has_animation_nodes) and (cached_anim_info == "无动画" or cached_processed_names == []):
|
||||||
|
cached_anim_info = None
|
||||||
|
cached_processed_names = None
|
||||||
|
anim_node.setPythonTag("cached_anim_info", None)
|
||||||
|
anim_node.setPythonTag("cached_processed_names", None)
|
||||||
|
anim_node.setPythonTag("animation", None)
|
||||||
|
|
||||||
# 只有在没有缓存时才进行完整的动画检测和处理
|
# 只有在没有缓存时才进行完整的动画检测和处理
|
||||||
if cached_anim_info is None or cached_processed_names is None:
|
if cached_anim_info is None or cached_processed_names is None:
|
||||||
# 获取Actor
|
# 获取Actor
|
||||||
actor = self._getActor(node)
|
actor = self._getActor(anim_node)
|
||||||
if not actor:
|
if not actor:
|
||||||
imgui.text_colored((0.7, 0.7, 0.7, 1.0), "此模型不包含动画")
|
if has_animation_tag or has_animation_nodes:
|
||||||
|
imgui.text_colored((1.0, 0.7, 0.3, 1.0), "检测到动画结构,但当前未成功绑定Actor")
|
||||||
|
else:
|
||||||
|
imgui.text_colored((0.7, 0.7, 0.7, 1.0), "此模型不包含动画")
|
||||||
return
|
return
|
||||||
|
|
||||||
# 获取和分析动画名称
|
# 获取和分析动画名称
|
||||||
anim_names = actor.getAnimNames()
|
anim_names = actor.getAnimNames()
|
||||||
processed_names = self._processAnimationNames(node, anim_names)
|
processed_names = self._processAnimationNames(anim_node, anim_names)
|
||||||
|
|
||||||
if not processed_names:
|
if not processed_names:
|
||||||
imgui.text_colored((0.7, 0.7, 0.7, 1.0), "未检测到动画序列")
|
imgui.text_colored((0.7, 0.7, 0.7, 1.0), "未检测到动画序列")
|
||||||
# 缓存空结果
|
# 只在明确无动画时缓存空结果,避免误缓存导致后续无法重试
|
||||||
node.setPythonTag("cached_processed_names", [])
|
if not (has_animation_tag or has_animation_nodes):
|
||||||
node.setPythonTag("cached_anim_info", "无动画")
|
anim_node.setPythonTag("cached_processed_names", [])
|
||||||
|
anim_node.setPythonTag("cached_anim_info", "无动画")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
anim_node.setTag("has_animations", "true")
|
||||||
|
anim_node.setPythonTag("animation", True)
|
||||||
|
|
||||||
# 计算并缓存动画信息
|
# 计算并缓存动画信息
|
||||||
format_info = self._getModelFormat(node)
|
format_info = self._getModelFormat(anim_node)
|
||||||
animation_info = self._analyzeAnimationQuality(actor, anim_names, format_info)
|
animation_info = self._analyzeAnimationQuality(actor, anim_names, format_info)
|
||||||
info_text = f"格式: {format_info} | 动画数量: {len(processed_names)}"
|
info_text = f"格式: {format_info} | 动画数量: {len(processed_names)}"
|
||||||
if animation_info:
|
if animation_info:
|
||||||
info_text += f" | {animation_info}"
|
info_text += f" | {animation_info}"
|
||||||
|
|
||||||
# 缓存结果
|
# 缓存结果
|
||||||
node.setPythonTag("cached_anim_info", info_text)
|
anim_node.setPythonTag("cached_anim_info", info_text)
|
||||||
node.setPythonTag("cached_processed_names", processed_names)
|
anim_node.setPythonTag("cached_processed_names", processed_names)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# 使用缓存的数据
|
# 使用缓存的数据
|
||||||
@ -1548,10 +1637,11 @@ class EditorPanels:
|
|||||||
imgui.same_line()
|
imgui.same_line()
|
||||||
|
|
||||||
# 获取当前选中的动画
|
# 获取当前选中的动画
|
||||||
current_anim = node.getPythonTag("selected_animation")
|
current_anim = anim_node.getPythonTag("selected_animation")
|
||||||
if current_anim is None:
|
valid_original_names = [original_name for _, original_name in processed_names]
|
||||||
|
if current_anim is None or current_anim not in valid_original_names:
|
||||||
current_anim = processed_names[0][1] if processed_names else ""
|
current_anim = processed_names[0][1] if processed_names else ""
|
||||||
node.setPythonTag("selected_animation", current_anim)
|
anim_node.setPythonTag("selected_animation", current_anim)
|
||||||
|
|
||||||
# 查找当前动画的索引
|
# 查找当前动画的索引
|
||||||
current_index = 0
|
current_index = 0
|
||||||
@ -1566,7 +1656,7 @@ class EditorPanels:
|
|||||||
|
|
||||||
if changed and new_index < len(processed_names):
|
if changed and new_index < len(processed_names):
|
||||||
selected_display, selected_original = processed_names[new_index]
|
selected_display, selected_original = processed_names[new_index]
|
||||||
node.setPythonTag("selected_animation", selected_original)
|
anim_node.setPythonTag("selected_animation", selected_original)
|
||||||
print(f"选择动画: {selected_display} (原始名称: {selected_original})")
|
print(f"选择动画: {selected_display} (原始名称: {selected_original})")
|
||||||
|
|
||||||
imgui.spacing()
|
imgui.spacing()
|
||||||
@ -1576,22 +1666,22 @@ class EditorPanels:
|
|||||||
|
|
||||||
# 播放按钮
|
# 播放按钮
|
||||||
if imgui.button("播放##play_animation"):
|
if imgui.button("播放##play_animation"):
|
||||||
self._playAnimation(node)
|
self._playAnimation(anim_node)
|
||||||
imgui.same_line()
|
imgui.same_line()
|
||||||
|
|
||||||
# 暂停按钮
|
# 暂停按钮
|
||||||
if imgui.button("暂停##pause_animation"):
|
if imgui.button("暂停##pause_animation"):
|
||||||
self._pauseAnimation(node)
|
self._pauseAnimation(anim_node)
|
||||||
imgui.same_line()
|
imgui.same_line()
|
||||||
|
|
||||||
# 停止按钮
|
# 停止按钮
|
||||||
if imgui.button("停止##stop_animation"):
|
if imgui.button("停止##stop_animation"):
|
||||||
self._stopAnimation(node)
|
self._stopAnimation(anim_node)
|
||||||
imgui.same_line()
|
imgui.same_line()
|
||||||
|
|
||||||
# 循环按钮
|
# 循环按钮
|
||||||
if imgui.button("循环##loop_animation"):
|
if imgui.button("循环##loop_animation"):
|
||||||
self._loopAnimation(node)
|
self._loopAnimation(anim_node)
|
||||||
|
|
||||||
imgui.spacing()
|
imgui.spacing()
|
||||||
|
|
||||||
@ -1600,16 +1690,16 @@ class EditorPanels:
|
|||||||
imgui.same_line()
|
imgui.same_line()
|
||||||
|
|
||||||
# 获取当前速度
|
# 获取当前速度
|
||||||
current_speed = node.getPythonTag("anim_speed")
|
current_speed = anim_node.getPythonTag("anim_speed")
|
||||||
if current_speed is None:
|
if current_speed is None:
|
||||||
current_speed = 1.0
|
current_speed = 1.0
|
||||||
node.setPythonTag("anim_speed", current_speed)
|
anim_node.setPythonTag("anim_speed", current_speed)
|
||||||
|
|
||||||
# 速度滑块
|
# 速度滑块
|
||||||
changed, new_speed = imgui.slider_float("##anim_speed", current_speed, 0.1, 5.0, "%.1f")
|
changed, new_speed = imgui.slider_float("##anim_speed", current_speed, 0.1, 5.0, "%.1f")
|
||||||
if changed:
|
if changed:
|
||||||
node.setPythonTag("anim_speed", new_speed)
|
anim_node.setPythonTag("anim_speed", new_speed)
|
||||||
self._setAnimationSpeed(node, new_speed)
|
self._setAnimationSpeed(anim_node, new_speed)
|
||||||
|
|
||||||
imgui.same_line()
|
imgui.same_line()
|
||||||
imgui.text_colored((0.7, 0.7, 0.7, 1.0), "倍速")
|
imgui.text_colored((0.7, 0.7, 0.7, 1.0), "倍速")
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user