EG/ui/panels/editor_panels_right.py

902 lines
39 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from pathlib import Path
from imgui_bundle import imgui, imgui_ctx
from panda3d.core import TransparencyAttrib
from ui.panels.editor_panels_right_collision import EditorPanelsRightCollisionMixin
from ui.panels.editor_panels_right_material import EditorPanelsRightMaterialMixin
from ui.panels.editor_panels_right_transform import EditorPanelsRightTransformMixin
class EditorPanelsRightMixin(
EditorPanelsRightTransformMixin,
EditorPanelsRightMaterialMixin,
EditorPanelsRightCollisionMixin,
):
"""Right panel aggregator mixin."""
def _apply_gui_alpha(self, gui_element, alpha_value):
alpha_value = max(0.0, min(1.0, float(alpha_value)))
gui_element.alpha = alpha_value
if hasattr(gui_element, "setTransparency"):
gui_element.setTransparency(
TransparencyAttrib.MAlpha if alpha_value < 0.999 else TransparencyAttrib.MNone
)
color_scale = None
if hasattr(gui_element, "getColorScale"):
try:
color_scale = gui_element.getColorScale()
except Exception:
color_scale = None
if color_scale is not None and hasattr(gui_element, "setColorScale"):
try:
gui_element.setColorScale(
float(color_scale[0]),
float(color_scale[1]),
float(color_scale[2]),
alpha_value,
)
return
except Exception:
pass
if hasattr(gui_element, "setAlphaScale"):
try:
gui_element.setAlphaScale(alpha_value)
return
except Exception:
pass
if hasattr(gui_element, "setColorScale"):
try:
gui_element.setColorScale(1.0, 1.0, 1.0, alpha_value)
except Exception:
pass
def _get_cached_animation_structure_state(self, anim_node, force_refresh=False):
if not force_refresh:
cached_state = anim_node.getPythonTag("cached_has_animation_nodes")
if cached_state is not None:
return bool(cached_state)
has_animation_nodes = False
try:
has_animation_nodes = (
anim_node.findAllMatches("**/+Character").getNumPaths() > 0 or
anim_node.findAllMatches("**/+AnimBundleNode").getNumPaths() > 0
)
except Exception:
has_animation_nodes = False
anim_node.setPythonTag("cached_has_animation_nodes", bool(has_animation_nodes))
return has_animation_nodes
def _record_visibility_change(self, node, old_visible, new_visible):
if not hasattr(self.app, "command_manager") or not self.app.command_manager:
node.setPythonTag("user_visible", bool(new_visible))
if new_visible:
node.show()
else:
node.hide()
return
from core.Command_System import VisibilityNodeCommand
self.app.command_manager.execute_command(
VisibilityNodeCommand(node, old_visible, new_visible, world=self.app)
)
def _record_name_change(self, node, old_name, new_name):
if old_name == new_name:
return
if hasattr(self.app, "command_manager") and self.app.command_manager:
from core.Command_System import RenameNodeCommand
self.app.command_manager.record_command(
RenameNodeCommand(node, old_name, new_name, world=self.app)
)
else:
self.app._update_node_name(node, new_name)
def _ensure_light_edit_sessions(self):
if not hasattr(self, "_light_edit_sessions"):
self._light_edit_sessions = {}
return self._light_edit_sessions
def _begin_light_edit_session(self, node, session_key):
sessions = self._ensure_light_edit_sessions()
sessions.setdefault(session_key, self.app._capture_light_snapshot(node))
def _record_light_snapshot_command(self, node, before_snapshot, after_snapshot):
if not hasattr(self.app, "command_manager") or not self.app.command_manager:
return
if self.app._light_snapshots_equal(before_snapshot, after_snapshot):
return
try:
from core.Command_System import SnapshotStateCommand
self.app.command_manager.record_command(
SnapshotStateCommand(
lambda state, target_node=node: self.app._apply_light_snapshot(target_node, state),
before_snapshot,
after_snapshot,
)
)
except Exception:
pass
def _finish_light_edit_session(self, node, session_key):
if not imgui.is_item_deactivated_after_edit():
return
sessions = self._ensure_light_edit_sessions()
before_snapshot = sessions.pop(session_key, None)
if before_snapshot is None:
return
after_snapshot = self.app._capture_light_snapshot(node)
self._record_light_snapshot_command(node, before_snapshot, after_snapshot)
def _draw_property_panel(self):
"""绘制属性面板"""
# 使用面板类型的窗口标志支持docking
flags = self.app.style_manager.get_window_flags("panel")
with self.app.style_manager.begin_styled_window("属性面板", self.app.showPropertyPanel, flags) as (_, opened):
if not opened:
self.app.showPropertyPanel = False
self.app._property_panel_window_rect = None
return
self.app.showPropertyPanel = opened
window_pos = imgui.get_window_pos()
window_size = imgui.get_window_size()
self.app._property_panel_window_rect = (
float(window_pos.x),
float(window_pos.y),
float(window_size.x),
float(window_size.y),
)
# --- LUI Component Properties ---
# 优先检查 LUI 组件选择
if hasattr(self.app, 'lui_manager'):
lui_selected_index = getattr(self.app.lui_manager, "selected_index", -1)
if lui_selected_index >= 0 and self.app.lui_manager.luiFunction:
self.app.lui_manager.luiFunction._draw_component_properties(
self.app.lui_manager,
lui_selected_index
)
return
# --- Scene Node Properties ---
selected_node = self.app._get_selection_source_node()
if selected_node and not selected_node.isEmpty():
self._draw_node_properties(selected_node)
else:
ssbo_summary = self.app._get_ssbo_selection_summary()
if ssbo_summary:
self._draw_ssbo_selection_summary(ssbo_summary)
return
self._draw_empty_property_state()
def _draw_empty_property_state(self):
imgui.separator_text("属性")
imgui.text_disabled("当前没有选中对象")
imgui.spacing()
imgui.bullet_text("从左侧场景树或场景视口中选择一个对象")
imgui.bullet_text("按 F 聚焦到对象,按 Delete 删除对象")
def _draw_property_section(self, title, draw_callback, default_open=False):
flags = imgui.TreeNodeFlags_.span_avail_width.value
if default_open:
flags |= imgui.TreeNodeFlags_.default_open.value
if imgui.collapsing_header(title, flags):
draw_callback()
imgui.spacing()
def _draw_ssbo_selection_summary(self, summary):
"""Render a safe summary for SSBO group selections without exposing wrong node properties."""
imgui.separator_text("SSBO 选择")
imgui.text(f"名称: {summary.get('display_name') or '未命名'}")
imgui.text(f"对象数量: {summary.get('object_count', 0)}")
if summary.get("is_root"):
imgui.text_colored((0.5, 0.8, 1.0, 1.0), "当前选中的是 SSBO 根模型")
elif summary.get("is_group"):
imgui.text_colored((1.0, 0.8, 0.3, 1.0), "当前选中的是 SSBO 组合节点")
imgui.text_wrapped("这个选择对应多个动态对象。这里先显示摘要,避免误改到错误节点。")
imgui.separator()
imgui.bullet_text("在左侧展开到叶子节点后查看单对象属性")
imgui.bullet_text("继续使用场景中的 Gizmo 做组合移动")
else:
imgui.text_colored((0.7, 0.7, 0.7, 1.0), "当前 SSBO 选择没有可直接映射的单个场景节点")
def _draw_node_properties(self, node):
"""绘制节点属性"""
if not node or node.isEmpty():
# 停止变换监控
self.app.stop_transform_monitoring()
return
# 检查是否需要重新启动变换监控
if self.app._monitored_node != node:
self.app.stop_transform_monitoring()
self.app.start_transform_monitoring(node)
# 获取节点基本信息
node_name = node.getName() or "未命名节点"
node_type = self.app._get_node_type_from_node(node)
self._draw_object_overview(node, node_name, node_type)
imgui.spacing()
self._draw_property_section("变换", lambda: self.app._draw_transform_properties(node), default_open=True)
if node_type == "GUI元素":
self._draw_property_section("GUI", lambda: self.app._draw_gui_properties(node), default_open=True)
elif node_type == "光源":
self._draw_property_section("光源", lambda: self.app._draw_light_properties(node), default_open=True)
elif node_type == "模型":
has_animation = False
try:
if node.hasTag("has_animations"):
has_animation = node.getTag("has_animations").lower() == "true"
if not has_animation:
has_animation = node.getPythonTag("animation") is True
except Exception:
has_animation = False
self._draw_property_section("模型", lambda: self.app._draw_model_properties(node), default_open=True)
self._draw_property_section("动画", lambda: self.app._draw_animation_properties(node), default_open=has_animation)
material_target = self.app._get_selection_material_node()
if not material_target or material_target.isEmpty():
material_target = node
self._draw_property_section("材质", lambda target=material_target: self.app._draw_appearance_properties(target), default_open=True)
self._draw_property_section("碰撞", lambda: self.app._draw_collision_properties(node), default_open=False)
self._draw_property_section("操作", lambda: self.app._draw_property_actions(node), default_open=False)
def _draw_object_overview(self, node, node_name, node_type):
imgui.separator_text("对象")
user_visible = node.getPythonTag("user_visible")
if user_visible is None:
user_visible = True
node.setPythonTag("user_visible", True)
changed, is_visible = imgui.checkbox("##node_enabled", user_visible)
if changed:
self._record_visibility_change(node, user_visible, is_visible)
imgui.same_line()
imgui.text_disabled("启用")
imgui.same_line()
imgui.set_next_item_width(-1)
changed, new_name = imgui.input_text("##name", node_name, 256)
if changed and hasattr(self.app, "selection"):
if not hasattr(self.app, "_name_edit_session"):
self.app._name_edit_session = None
if not self.app._name_edit_session or self.app._name_edit_session[0] != node:
self.app._name_edit_session = (node, node_name)
self.app._update_node_name(node, new_name)
if imgui.is_item_deactivated_after_edit():
session = getattr(self.app, "_name_edit_session", None)
if session and session[0] == node:
_, original_name = session
self._record_name_change(node, original_name, node.getName())
self.app._name_edit_session = None
parent_name = "SceneRoot"
try:
parent = node.getParent()
if parent and not parent.isEmpty():
parent_name = parent.getName() or "SceneRoot"
except Exception:
parent = None
imgui.text_disabled(f"{node_type} · 父级: {parent_name}")
self._draw_status_badges(node, node_type)
def _node_has_collision_badge(self, node):
child_count = 0
try:
child_count = int(node.getNumChildren())
except Exception:
child_count = 0
cached = None
try:
cached = node.getPythonTag("cached_collision_badge_state")
except Exception:
cached = None
if isinstance(cached, dict) and cached.get("child_count") == child_count:
return bool(cached.get("has_collision", False))
has_collision = False
try:
for child in node.getChildren():
child_name = child.getName()
if child_name and "Collision" in child_name:
has_collision = True
break
except Exception:
has_collision = False
try:
node.setPythonTag(
"cached_collision_badge_state",
{"child_count": child_count, "has_collision": bool(has_collision)},
)
except Exception:
pass
return has_collision
def _draw_status_badges(self, node, node_type=None):
"""绘制精简后的对象状态徽章行。"""
if node_type is None:
node_type = self.app._get_node_type_from_node(node)
badges = []
if node.is_hidden():
badges.append(("已隐藏", (0.65, 0.65, 0.65, 1.0)))
has_collision = hasattr(node, "getChild") and self._node_has_collision_badge(node)
if has_collision:
badges.append(("碰撞", (0.35, 0.65, 1.0, 1.0)))
has_script = hasattr(node, "getPythonTag") and node.getPythonTag("script")
if has_script:
badges.append(("脚本", (0.86, 0.48, 0.86, 1.0)))
has_animation = False
if node_type == "模型":
if node.hasTag("has_animations"):
has_animation = node.getTag("has_animations").lower() == "true"
if not has_animation:
cached_result = node.getPythonTag("animation")
if cached_result is True:
has_animation = True
elif cached_result is False:
has_animation = False
else:
has_animation = hasattr(node, "getPythonTag") and node.getPythonTag("animation")
if has_animation:
badges.append(("动画", (0.45, 0.85, 0.55, 1.0)))
has_material = hasattr(node, "getMaterial") and node.getMaterial()
if has_material:
badges.append(("材质", (0.9, 0.72, 0.35, 1.0)))
if not badges:
return
for index, (badge_text, badge_color) in enumerate(badges):
if index > 0:
imgui.same_line()
imgui.text_colored(badge_color, f"[{badge_text}]")
def _draw_gui_properties(self, node):
"""绘制GUI元素属性"""
# 获取GUI元素
gui_element = None
if hasattr(node, 'getPythonTag'):
gui_element = node.getPythonTag('gui_element')
if not gui_element:
imgui.text("无GUI元素数据")
return
# GUI类型信息
gui_type = getattr(gui_element, 'gui_type', 'UNKNOWN')
imgui.text(f"GUI类型: {gui_type}")
# 基本属性
if imgui.collapsing_header("基本属性"):
# 文本内容 (适用于按钮、标签等)
if hasattr(gui_element, 'text'):
changed, new_text = imgui.input_text("文本内容", gui_element.text, 256)
if changed and hasattr(self, 'gui_manager'):
self.gui_manager.editGUIElement(gui_element, 'text', new_text)
# GUI ID
gui_id = getattr(gui_element, 'id', '')
changed, new_id = imgui.input_text("GUI ID", gui_id, 64)
if changed and hasattr(self, 'gui_manager'):
gui_element.id = new_id
# 变换属性
if imgui.collapsing_header("变换属性"):
# 位置
pos = gui_element.getPos()
imgui.text("位置")
if gui_type in ["button", "label", "entry", "2d_image"]:
# 2D GUI组件使用屏幕坐标
imgui.text("屏幕坐标")
logical_x = pos.getX() / 0.1
logical_z = pos.getZ() / 0.1
changed, new_x = imgui.input_float("X##gui_pos_x", logical_x, 1.0, 10.0, "%.1f")
if changed and hasattr(self, 'gui_manager'):
gui_element.setX(new_x * 0.1)
changed, new_z = imgui.input_float("Y##gui_pos_y", logical_z, 1.0, 10.0, "%.1f")
if changed and hasattr(self, 'gui_manager'):
gui_element.setZ(new_z * 0.1)
else:
# 3D GUI组件使用世界坐标
imgui.text("世界坐标")
changed, new_x = imgui.input_float("X##gui_world_x", pos.getX(), 0.1, 1.0, "%.3f")
if changed and hasattr(self, 'gui_manager'):
gui_element.setX(new_x)
changed, new_y = imgui.input_float("Y##gui_world_y", pos.getY(), 0.1, 1.0, "%.3f")
if changed and hasattr(self, 'gui_manager'):
gui_element.setY(new_y)
changed, new_z = imgui.input_float("Z##gui_world_z", pos.getZ(), 0.1, 1.0, "%.3f")
if changed and hasattr(self, 'gui_manager'):
gui_element.setZ(new_z)
# 缩放
scale = gui_element.getScale()
imgui.text("缩放")
changed, new_sx = imgui.input_float("X##gui_scale_x", scale.getX(), 0.1, 1.0, "%.3f")
if changed and hasattr(self, 'gui_manager'):
gui_element.setSx(new_sx)
changed, new_sy = imgui.input_float("Y##gui_scale_y", scale.getY(), 0.1, 1.0, "%.3f")
if changed and hasattr(self, 'gui_manager'):
gui_element.setSy(new_sy)
changed, new_sz = imgui.input_float("Z##gui_scale_z", scale.getZ(), 0.1, 1.0, "%.3f")
if changed and hasattr(self, 'gui_manager'):
gui_element.setSz(new_sz)
# 旋转
hpr = gui_element.getHpr()
imgui.text("旋转")
changed, new_h = imgui.input_float("H##gui_rot_h", hpr.getX(), 1.0, 10.0, "%.1f")
if changed and hasattr(self, 'gui_manager'):
gui_element.setH(new_h)
changed, new_p = imgui.input_float("P##gui_rot_p", hpr.getY(), 1.0, 10.0, "%.1f")
if changed and hasattr(self, 'gui_manager'):
gui_element.setP(new_p)
changed, new_r = imgui.input_float("R##gui_rot_r", hpr.getZ(), 1.0, 10.0, "%.1f")
if changed and hasattr(self, 'gui_manager'):
gui_element.setR(new_r)
# 外观属性
if imgui.collapsing_header("外观属性"):
# 大小
if hasattr(gui_element, 'size'):
size = gui_element.size
imgui.text("大小")
changed, new_w = imgui.input_float("宽度", size[0], 1.0, 10.0, "%.1f")
if changed and hasattr(self, 'gui_manager'):
new_size = (new_w, size[1])
self.gui_manager.editGUIElement(gui_element, 'size', new_size)
changed, new_h = imgui.input_float("高度", size[1], 1.0, 10.0, "%.1f")
if changed and hasattr(self, 'gui_manager'):
new_size = (size[0], new_h)
self.gui_manager.editGUIElement(gui_element, 'size', new_size)
# 颜色
if hasattr(gui_element, 'getColor'):
try:
color = gui_element.getColor()
# 确保颜色是有效的
if not color or (hasattr(color, '__len__') and len(color) < 3):
color = (1.0, 1.0, 1.0, 1.0) # 默认白色
except:
color = (1.0, 1.0, 1.0, 1.0) # 默认白色
imgui.text("颜色")
# 获取颜色值
if hasattr(color, 'getX'):
# 如果是Panda3D的Vec4对象
r, g, b = color.getX(), color.getY(), color.getZ()
else:
# 如果是元组或其他格式
r, g, b = color[0], color[1], color[2]
changed, new_r = imgui.slider_float("R##gui_color_r", r, 0.0, 1.0)
if changed: gui_element.setColor(new_r, g, b, 1.0)
changed, new_g = imgui.slider_float("G##gui_color_g", g, 0.0, 1.0)
if changed: gui_element.setColor(r, new_g, b, 1.0)
changed, new_b = imgui.slider_float("B##gui_color_b", b, 0.0, 1.0)
if changed: gui_element.setColor(r, g, new_b, 1.0)
# 透明度
imgui.text("透明度")
current_alpha = getattr(gui_element, 'alpha', 1.0)
changed, new_alpha = imgui.slider_float("Alpha", current_alpha, 0.0, 1.0)
if changed:
self._apply_gui_alpha(gui_element, new_alpha)
# 渲染顺序
imgui.text("渲染顺序")
current_sort = getattr(gui_element, 'sort', 0)
changed, new_sort = imgui.input_int("Sort Order", current_sort)
if changed:
gui_element.sort = new_sort
if hasattr(gui_element, 'setBin'):
gui_element.setBin('fixed', new_sort)
# 字体设置适用于文本类型的GUI元素
if gui_type in ["button", "label", "entry"]:
imgui.text("字体设置")
# 字体选择
current_font = getattr(gui_element, 'font_path', '')
if imgui.button(f"字体: {Path(current_font).name if current_font else '默认'}##font_select"):
self.show_font_selector(
gui_element,
'font_path',
current_font,
lambda font_path: self._apply_gui_font(gui_element, font_path)
)
# 字体大小
current_size = getattr(gui_element, 'font_size', 12)
changed, new_size = imgui.slider_float("字体大小", current_size, 8.0, 72.0)
if changed:
gui_element.font_size = new_size
self._apply_gui_font_size(gui_element, new_size)
# 字体样式
imgui.text("字体样式")
is_bold = getattr(gui_element, 'font_bold', False)
changed, new_bold = imgui.checkbox("粗体", is_bold)
if changed:
gui_element.font_bold = new_bold
self._apply_gui_font_style(gui_element)
imgui.same_line()
is_italic = getattr(gui_element, 'font_italic', False)
changed, new_italic = imgui.checkbox("斜体", is_italic)
if changed:
gui_element.font_italic = new_italic
self._apply_gui_font_style(gui_element)
def _draw_light_properties(self, node):
"""绘制光源属性"""
imgui.text("光源属性")
color = self.app._get_light_color(node)
changed, new_color = imgui.color_edit3(
"颜色##light_color",
(color[0], color[1], color[2]),
imgui.ColorEditFlags_.display_rgb.value,
)
if imgui.is_item_activated():
self._begin_light_edit_session(node, "light_color")
if changed:
self.app._apply_light_color(node, (new_color[0], new_color[1], new_color[2], color[3]))
self._finish_light_edit_session(node, "light_color")
# 光源强度
imgui.text("光源强度: (暂不支持编辑)")
def _draw_model_properties(self, node):
"""绘制模型属性"""
# 获取模型信息
model_path = node.getTag("model_path") if node.hasTag("model_path") else "未知"
imgui.text("模型路径:")
imgui.same_line()
imgui.text_colored((0.7, 0.7, 0.7, 1.0), model_path)
# 模型基本信息
imgui.text("模型名称:")
imgui.same_line()
model_name = node.getName() or "未命名模型"
imgui.text_colored((0.7, 0.7, 0.7, 1.0), model_name)
# 模型位置信息
imgui.text("位置:")
imgui.same_line()
pos = node.getPos()
imgui.text_colored((0.7, 0.7, 0.7, 1.0), f"X:{pos.x:.2f} Y:{pos.y:.2f} Z:{pos.z:.2f}")
# 模型缩放信息
imgui.text("缩放:")
imgui.same_line()
scale = node.getScale()
imgui.text_colored((0.7, 0.7, 0.7, 1.0), f"X:{scale.x:.2f} Y:{scale.y:.2f} Z:{scale.z:.2f}")
# 模型旋转信息
imgui.text("旋转:")
imgui.same_line()
hpr = node.getHpr()
imgui.text_colored((0.7, 0.7, 0.7, 1.0), f"H:{hpr.x:.1f}° P:{hpr.y:.1f}° R:{hpr.z:.1f}°")
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
has_animation_checked = (
anim_node.hasTag("has_animations_checked") and
anim_node.getTag("has_animations_checked").lower() == "true"
)
# 只在模型还没做过动画探测时刷新标签,避免属性面板每帧重复扫描。
try:
if (
not has_animation_checked and
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_cached_animation = anim_node.getPythonTag("animation") is True
has_animation_nodes = (
has_animation_tag or
has_cached_animation or
self._get_cached_animation_structure_state(anim_node)
)
# 检查是否已经缓存了动画信息
cached_anim_info = anim_node.getPythonTag("cached_anim_info")
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)
should_force_probe = False
if not (has_animation_tag or has_animation_nodes or has_cached_animation):
imgui.text_colored((0.7, 0.7, 0.7, 1.0), "此模型未检测到动画结构")
if self.app.style_manager.draw_toolbar_button(
"尝试强制检测",
size=(108, 26),
tooltip="重新扫描当前模型的动画结构",
):
should_force_probe = True
anim_node.setPythonTag("cached_anim_info", None)
anim_node.setPythonTag("cached_processed_names", None)
has_animation_nodes = self._get_cached_animation_structure_state(anim_node, force_refresh=True)
else:
return
# 只有在没有缓存时才进行完整的动画检测和处理
if cached_anim_info is None or cached_processed_names is None:
# 获取Actor
actor = self._getActor(anim_node) if (should_force_probe or has_animation_tag or has_animation_nodes or has_cached_animation) else None
if not actor:
if has_animation_tag or has_animation_nodes:
imgui.text_colored((1.0, 0.7, 0.3, 1.0), "检测到动画结构但当前未成功绑定Actor")
elif should_force_probe:
imgui.text_colored((0.9, 0.6, 0.3, 1.0), "强制检测未发现可播放动画")
anim_node.setPythonTag("animation", False)
anim_node.setPythonTag("cached_processed_names", [])
anim_node.setPythonTag("cached_anim_info", "无动画")
else:
imgui.text_colored((0.7, 0.7, 0.7, 1.0), "此模型不包含动画")
return
# 获取和分析动画名称
anim_names = actor.getAnimNames()
processed_names = self._processAnimationNames(anim_node, anim_names)
if not processed_names:
imgui.text_colored((0.7, 0.7, 0.7, 1.0), "未检测到动画序列")
# 只在明确无动画时缓存空结果,避免误缓存导致后续无法重试
if not (has_animation_tag or has_animation_nodes):
anim_node.setPythonTag("cached_processed_names", [])
anim_node.setPythonTag("cached_anim_info", "无动画")
return
anim_node.setTag("has_animations", "true")
anim_node.setPythonTag("animation", True)
# 计算并缓存动画信息
format_info = self._getModelFormat(anim_node)
animation_info = self._analyzeAnimationQuality(actor, anim_names, format_info)
info_text = f"格式: {format_info} | 动画数量: {len(processed_names)}"
if animation_info:
info_text += f" | {animation_info}"
# 缓存结果
anim_node.setPythonTag("cached_anim_info", info_text)
anim_node.setPythonTag("cached_processed_names", processed_names)
else:
# 使用缓存的数据
info_text = cached_anim_info
processed_names = cached_processed_names
# 如果缓存的空结果,直接返回
if not processed_names:
imgui.text_colored((0.7, 0.7, 0.7, 1.0), "未检测到动画序列")
return
# 显示动画信息(使用缓存的数据)
imgui.text("信息:")
imgui.same_line()
imgui.text_colored((0.7, 0.7, 0.7, 1.0), info_text)
imgui.spacing()
# 动画选择下拉框
imgui.text("动画名称:")
imgui.same_line()
# 获取当前选中的动画
current_anim = anim_node.getPythonTag("selected_animation")
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 ""
anim_node.setPythonTag("selected_animation", current_anim)
# 查找当前动画的索引
current_index = 0
for i, (display_name, original_name) in enumerate(processed_names):
if original_name == current_anim:
current_index = i
break
# 创建下拉框选项
animation_options = [display_name for display_name, _ in processed_names]
changed, new_index = imgui.combo("##animation_combo", current_index, animation_options)
if changed and new_index < len(processed_names):
selected_display, selected_original = processed_names[new_index]
anim_node.setPythonTag("selected_animation", selected_original)
print(f"选择动画: {selected_display} (原始名称: {selected_original})")
imgui.spacing()
# 控制按钮组
imgui.text("控制:")
# 播放按钮
if self.app.style_manager.draw_toolbar_button("播放", size=(60, 26), tooltip="播放当前动画"):
self._playAnimation(anim_node)
imgui.same_line()
# 暂停按钮
if self.app.style_manager.draw_toolbar_button("暂停", size=(60, 26), tooltip="暂停当前动画"):
self._pauseAnimation(anim_node)
imgui.same_line()
# 停止按钮
if self.app.style_manager.draw_toolbar_button("停止", size=(60, 26), tooltip="停止当前动画"):
self._stopAnimation(anim_node)
imgui.same_line()
# 循环按钮
if self.app.style_manager.draw_toolbar_button("循环", size=(60, 26), tooltip="循环播放当前动画"):
self._loopAnimation(anim_node)
imgui.spacing()
# 播放速度控制
imgui.text("播放速度:")
imgui.same_line()
# 获取当前速度
current_speed = anim_node.getPythonTag("anim_speed")
if current_speed is None:
current_speed = 1.0
anim_node.setPythonTag("anim_speed", current_speed)
# 速度滑块
changed, new_speed = imgui.slider_float("##anim_speed", current_speed, 0.1, 5.0, "%.1f")
if changed:
anim_node.setPythonTag("anim_speed", new_speed)
self._setAnimationSpeed(anim_node, new_speed)
imgui.same_line()
imgui.text_colored((0.7, 0.7, 0.7, 1.0), "倍速")
def _draw_property_actions(self, node):
"""绘制属性操作按钮"""
# 重置变换
if self.app.style_manager.draw_toolbar_button("重置变换", size=(84, 28), tooltip="将位置、旋转、缩放恢复为默认值"):
if hasattr(self.app, "command_manager") and self.app.command_manager:
from core.Command_System import (
CompositeCommand,
MoveNodeCommand,
RotateNodeCommand,
ScaleNodeCommand,
)
current_pos = tuple(float(v) for v in node.getPos())
current_hpr = tuple(float(v) for v in node.getHpr())
current_scale = tuple(float(v) for v in node.getScale())
self.app.command_manager.execute_command(
CompositeCommand(
[
MoveNodeCommand(node, current_pos, (0.0, 0.0, 0.0)),
RotateNodeCommand(node, current_hpr, (0.0, 0.0, 0.0)),
ScaleNodeCommand(node, current_scale, (1.0, 1.0, 1.0)),
]
)
)
else:
node.setPos(0, 0, 0)
node.setHpr(0, 0, 0)
node.setScale(1, 1, 1)
imgui.same_line()
# 切换可见性
is_visible = not node.is_hidden()
visibility_text = "隐藏" if is_visible else "显示"
if self.app.style_manager.draw_toolbar_button(
visibility_text,
size=(64, 28),
tooltip="切换当前对象的可见状态",
):
self._record_visibility_change(node, is_visible, not is_visible)
imgui.same_line()
# 聚焦到对象
if self.app.style_manager.draw_toolbar_button("聚焦", size=(64, 28), tooltip="将相机聚焦到当前对象"):
if hasattr(self, 'selection') and self.selection:
self.selection.focusCameraOnSelectedNodeAdvanced()
# 删除对象
imgui.same_line()
if self.app.style_manager.draw_toolbar_button("删除", size=(64, 28), tooltip="删除当前对象"):
if hasattr(self, 'selection') and self.selection:
self.selection.deleteSelectedNode()