EG/ui/panels/editor_panels_right.py

767 lines
34 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 imgui_bundle import imgui, imgui_ctx
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 _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):
self.app.showPropertyPanel = True # 确保窗口保持打开
# --- 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 = None
if hasattr(self.app, 'selection') and self.app.selection and hasattr(self.app.selection, 'selectedNode'):
selected_node = self.app.selection.selectedNode
# SSBO mode may select a proxy node for gizmo operations.
# Resolve proxy back to a real scene node so property panel stays meaningful.
try:
if (selected_node and not selected_node.isEmpty() and
selected_node.hasTag("is_ssbo_proxy")):
ssbo_editor = getattr(self.app, "ssbo_editor", None)
controller = getattr(ssbo_editor, "controller", None) if ssbo_editor else None
if ssbo_editor and controller:
resolved = None
if getattr(ssbo_editor, "selected_ids", None):
first_gid = ssbo_editor.selected_ids[0]
key = controller.id_to_name.get(first_gid)
if key:
resolved = controller.key_to_node.get(key)
if (resolved is None or resolved.isEmpty()) and getattr(ssbo_editor, "selected_name", None):
resolved = controller.key_to_node.get(ssbo_editor.selected_name)
if resolved and not resolved.isEmpty():
selected_node = resolved
except Exception:
pass
if selected_node and not selected_node.isEmpty():
self._draw_node_properties(selected_node)
else:
# 无选中对象时显示提示模仿Qt版本的空状态样式
imgui.spacing()
imgui.spacing()
# 居中显示提示信息
window_width = imgui.get_window_width()
text_width = 200 # 估算文本宽度
text_pos_x = (window_width - text_width) / 2
imgui.set_cursor_pos_x(text_pos_x)
imgui.text_colored((0.5, 0.5, 0.5, 1.0), "🔍 未选择任何对象")
imgui.set_cursor_pos_x(text_pos_x - 20)
imgui.text("请从场景树中选择一个对象")
imgui.set_cursor_pos_x(text_pos_x + 10)
imgui.text("以查看其属性")
imgui.spacing()
imgui.spacing()
# 添加一些分隔线和装饰
imgui.separator()
# 显示快速提示
imgui.text("💡 快速提示:")
imgui.bullet_text("单击场景树中的对象进行选择")
imgui.bullet_text("使用 F 键快速聚焦到选中对象")
imgui.bullet_text("使用 Delete 键删除选中对象")
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)
# 添加一些间距模仿Qt版本的布局
imgui.spacing()
# 物体名称组使用Qt版本的样式
if imgui.collapsing_header("物体名称"):
# 第一行:可见性复选框和名称输入
user_visible = node.getPythonTag("user_visible")
if user_visible is None:
user_visible = True
node.setPythonTag("user_visible", True)
# 可见性复选框模仿Qt版本的样式
changed, is_visible = imgui.checkbox("##visibility", user_visible)
if changed:
node.setPythonTag("user_visible", is_visible)
if is_visible:
node.show()
else:
node.hide()
imgui.same_line()
imgui.text("可见")
imgui.same_line()
imgui.spacing()
imgui.same_line()
# 名称输入框模仿Qt版本的样式
imgui.text("名称:")
imgui.same_line()
changed, new_name = imgui.input_text("##name", node_name, 256)
if changed and hasattr(self.app, 'selection'):
# 更新场景树中的名称
self.app._update_node_name(node, new_name)
# 添加分隔线
imgui.separator()
# 状态徽章模仿Qt版本的徽章样式
self.app._draw_status_badges(node)
imgui.spacing()
# 变换属性组
if imgui.collapsing_header("变换 Transform"):
self.app._draw_transform_properties(node)
# 根据节点类型显示特定属性组
if node_type == "GUI元素":
if imgui.collapsing_header("GUI信息"):
self.app._draw_gui_properties(node)
elif node_type == "光源":
if imgui.collapsing_header("光源属性"):
self.app._draw_light_properties(node)
elif node_type == "模型":
if imgui.collapsing_header("模型属性"):
self.app._draw_model_properties(node)
# 动画控制组(只对模型显示)
if imgui.collapsing_header("动画控制"):
self.app._draw_animation_properties(node)
# 外观属性组(通用)
if imgui.collapsing_header("外观属性"):
self.app._draw_appearance_properties(node)
# 碰撞检测组
if imgui.collapsing_header("碰撞检测"):
self.app._draw_collision_properties(node)
# 操作按钮组
if imgui.collapsing_header("操作"):
self.app._draw_property_actions(node)
def _draw_status_badges(self, node):
"""绘制状态徽章模仿Qt版本的徽章样式"""
imgui.text("状态标签: ")
# 可见性状态徽章
is_visible = not node.is_hidden()
visibility_color = (0.176, 1.0, 0.769, 1.0) if is_visible else (0.953, 0.616, 0.471, 1.0)
visibility_text = "可见" if is_visible else "隐藏"
imgui.same_line()
imgui.text_colored(visibility_color, f"[{visibility_text}]")
# 节点类型徽章
node_type = self._get_node_type_from_node(node)
type_colors = {
"GUI元素": (0.188, 0.404, 0.753, 1.0), # 主题蓝色
"光源": (1.0, 0.8, 0.2, 1.0), # 黄色
"模型": (0.6, 0.8, 1.0, 1.0), # 浅蓝色
"相机": (0.8, 0.8, 0.2, 1.0), # 橙色
"几何体": (0.5, 0.5, 0.5, 1.0), # 灰色
}
if node_type in type_colors:
imgui.same_line()
imgui.text_colored(type_colors[node_type], f"[{node_type}]")
# 功能性徽章
badges = []
# 碰撞体徽章
has_collision = hasattr(node, 'getChild') and any('Collision' in child.getName() for child in node.getChildren() if child.getName())
if has_collision:
badges.append(("碰撞", (0.2, 0.4, 0.8, 1.0))) # 蓝色
# 脚本徽章
has_script = hasattr(node, 'getPythonTag') and node.getPythonTag('script')
if has_script:
badges.append(("脚本", (0.8, 0.4, 0.8, 1.0))) # 紫色
# 动画徽章优化检测逻辑避免重复创建Actor
has_animation = False
if node_type == "模型": # 只对模型类型进行动画检测
model_path = node.getTag("model_path") if node.hasTag("model_path") else ""
likely_anim_format = bool(model_path and model_path.lower().endswith(('.glb', '.gltf', '.fbx', '.bam', '.egg')))
# 优先使用场景标签(导入/加载时会写入)
if node.hasTag("has_animations"):
has_animation = node.getTag("has_animations").lower() == "true"
# 再做轻量结构检测(不依赖 Actor
if not has_animation:
try:
has_character = node.findAllMatches("**/+Character").getNumPaths() > 0
has_bundle = node.findAllMatches("**/+AnimBundleNode").getNumPaths() > 0
has_animation = has_character or has_bundle
if has_animation:
node.setTag("has_animations", "true")
node.setTag("can_create_actor_from_memory", "true")
except Exception:
pass
# 最后才尝试 Actor 检测(只缓存“有动画”,避免把失败结果永久缓存)
cached_result = node.getPythonTag('animation')
if cached_result is True:
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:
print(f"动画检测失败: {e}")
elif cached_result is False and not likely_anim_format:
has_animation = False
else:
# 对于非模型类型,检查已有的动画标签
has_animation = hasattr(node, 'getPythonTag') and node.getPythonTag('animation')
if has_animation:
badges.append(("动画", (0.4, 0.8, 0.4, 1.0))) # 绿色
# 材质徽章
has_material = hasattr(node, 'getMaterial') and node.getMaterial()
if has_material:
badges.append(("材质", (0.8, 0.6, 0.2, 1.0))) # 金色
# 绘制功能性徽章
for badge_text, badge_color in badges:
imgui.same_line()
imgui.text_colored(badge_color, f"[{badge_text}]")
# 如果没有特殊徽章,显示默认状态
if not badges:
imgui.same_line()
imgui.text_colored((0.5, 0.5, 0.5, 1.0), "[标准对象]")
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:
gui_element.alpha = new_alpha
if hasattr(gui_element, 'setTransparency'):
# 将0.0-1.0范围转换为Panda3D的透明度格式
panda_transparency = int((1.0 - new_alpha) * 255)
gui_element.setTransparency(panda_transparency)
# 渲染顺序
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("光源属性")
# 光源颜色
if hasattr(node, 'getColor'):
try:
color = node.getColor()
# 确保颜色是有效的
if not color or len(color) < 3:
color = (1.0, 1.0, 1.0, 1.0) # 默认白色
except:
color = (1.0, 1.0, 1.0, 1.0) # 默认白色
changed, new_r = imgui.drag_float("颜色 R", color[0], 0.01, 0.0, 1.0)
if changed: node.setColor(new_r, color[1], color[2], color[3] if len(color) > 3 else 1.0)
changed, new_g = imgui.drag_float("颜色 G", color[1], 0.01, 0.0, 1.0)
if changed: node.setColor(color[0], new_g, color[2], color[3] if len(color) > 3 else 1.0)
changed, new_b = imgui.drag_float("颜色 B", color[2], 0.01, 0.0, 1.0)
if changed: node.setColor(color[0], color[1], new_b, color[3] if len(color) > 3 else 1.0)
# 光源强度
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
# 先刷新一次模型动画标签,避免“导入后未初始化”导致误判
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 = 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)
# 只有在没有缓存时才进行完整的动画检测和处理
if cached_anim_info is None or cached_processed_names is None:
# 获取Actor
actor = self._getActor(anim_node)
if not actor:
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
# 获取和分析动画名称
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 imgui.button("播放##play_animation"):
self._playAnimation(anim_node)
imgui.same_line()
# 暂停按钮
if imgui.button("暂停##pause_animation"):
self._pauseAnimation(anim_node)
imgui.same_line()
# 停止按钮
if imgui.button("停止##stop_animation"):
self._stopAnimation(anim_node)
imgui.same_line()
# 循环按钮
if imgui.button("循环##loop_animation"):
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 imgui.button("重置变换"):
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 imgui.button(visibility_text):
if is_visible:
node.hide()
else:
node.show()
imgui.same_line()
# 聚焦到对象
if imgui.button("聚焦"):
if hasattr(self, 'selection') and self.selection:
self.selection.focusCameraOnSelectedNodeAdvanced()
# 删除对象
imgui.same_line()
if imgui.button("删除"):
if hasattr(self, 'selection') and self.selection:
self.selection.deleteSelectedNode()