动画
This commit is contained in:
parent
6a99c3cc2a
commit
ab6d543dc0
@ -1003,10 +1003,24 @@ class AnimationTools:
|
||||
try:
|
||||
if node and (not node.isEmpty()):
|
||||
# If the user explicitly selected the animated skeleton node,
|
||||
# respect that directly. More aggressive driver remapping made
|
||||
# playback regress to "cannot play at all".
|
||||
if self._node_has_animation_nodes(node):
|
||||
# respect that directly only when it also carries visible geom.
|
||||
# Skeleton-only nodes like Armature still need a deeper renderable
|
||||
# driver node, otherwise playback can report success but show no motion.
|
||||
if self._node_has_animation_nodes(node) and self._node_has_geom(node):
|
||||
return node
|
||||
driver = self._find_animation_driver_node(node)
|
||||
if driver and (not driver.isEmpty()):
|
||||
for tag_name in ("model_path", "original_path", "saved_model_path", "file"):
|
||||
try:
|
||||
if (
|
||||
(not driver.hasTag(tag_name) or not driver.getTag(tag_name))
|
||||
and node.hasTag(tag_name)
|
||||
and node.getTag(tag_name)
|
||||
):
|
||||
driver.setTag(tag_name, node.getTag(tag_name))
|
||||
except Exception:
|
||||
continue
|
||||
return driver
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
@ -35,7 +35,8 @@ class AppActions:
|
||||
while current and not current.isEmpty():
|
||||
if current.getName() == "render":
|
||||
break
|
||||
if current.hasTag("tree_item_type") or current.hasTag("is_model_root") or current.hasTag("is_scene_element"):
|
||||
if current.hasTag("tree_item_type") or current.hasTag("is_model_root") or current.hasTag(
|
||||
"is_scene_element"):
|
||||
return current
|
||||
current = current.getParent()
|
||||
|
||||
@ -60,8 +61,8 @@ class AppActions:
|
||||
return False
|
||||
try:
|
||||
has_animation = (
|
||||
model.findAllMatches("**/+Character").getNumPaths() > 0
|
||||
or model.findAllMatches("**/+AnimBundleNode").getNumPaths() > 0
|
||||
model.findAllMatches("**/+Character").getNumPaths() > 0
|
||||
or model.findAllMatches("**/+AnimBundleNode").getNumPaths() > 0
|
||||
)
|
||||
finally:
|
||||
try:
|
||||
@ -72,14 +73,13 @@ class AppActions:
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def _toggle_hot_reload(self):
|
||||
"""切换热重载状态"""
|
||||
if hasattr(self, 'script_manager') and self.script_manager:
|
||||
try:
|
||||
current_state = getattr(self.script_manager, 'hot_reload_enabled', False)
|
||||
self._set_hot_reload_enabled(not current_state)
|
||||
|
||||
|
||||
new_state = "启用" if not current_state else "禁用"
|
||||
self.add_success_message(f"热重载已{new_state}")
|
||||
print(f"[脚本系统] 热重载已{new_state}")
|
||||
@ -93,28 +93,27 @@ class AppActions:
|
||||
raise RuntimeError("脚本管理器未初始化")
|
||||
self.script_manager.set_hot_reload_enabled(enabled)
|
||||
self.hotReloadEnabled = self.script_manager.hot_reload_enabled
|
||||
|
||||
|
||||
def _create_new_script(self):
|
||||
"""创建新脚本"""
|
||||
if not hasattr(self, '_new_script_name') or not self._new_script_name.strip():
|
||||
self.add_error_message("请输入脚本名称")
|
||||
return
|
||||
|
||||
|
||||
script_name = self._new_script_name.strip()
|
||||
if not script_name.endswith('.py'):
|
||||
script_name += '.py'
|
||||
|
||||
|
||||
# 确定模板类型
|
||||
template_map = {
|
||||
0: "basic",
|
||||
1: "movement",
|
||||
1: "movement",
|
||||
2: "rotation",
|
||||
3: "scale",
|
||||
4: "animation"
|
||||
}
|
||||
template_type = template_map.get(getattr(self, '_selected_template', 0), "basic")
|
||||
|
||||
|
||||
try:
|
||||
if hasattr(self, 'script_manager') and self.script_manager:
|
||||
result = self.script_manager.create_script_file(script_name, template_type)
|
||||
@ -130,7 +129,6 @@ class AppActions:
|
||||
except Exception as e:
|
||||
self.add_error_message(f"创建脚本失败: {str(e)}")
|
||||
print(f"[脚本系统] 创建脚本失败: {e}")
|
||||
|
||||
|
||||
def _refresh_scripts_list(self):
|
||||
"""刷新脚本列表"""
|
||||
@ -145,7 +143,6 @@ class AppActions:
|
||||
except Exception as e:
|
||||
self.add_error_message(f"刷新脚本列表失败: {str(e)}")
|
||||
print(f"[脚本系统] 刷新脚本列表失败: {e}")
|
||||
|
||||
|
||||
def _reload_all_scripts(self):
|
||||
"""重载所有脚本"""
|
||||
@ -154,11 +151,11 @@ class AppActions:
|
||||
# 获取所有可用脚本并逐个重载
|
||||
available_scripts = self.script_manager.get_available_scripts()
|
||||
success_count = 0
|
||||
|
||||
|
||||
for script_name in available_scripts:
|
||||
if self.script_manager.reload_script(script_name):
|
||||
success_count += 1
|
||||
|
||||
|
||||
self.add_success_message(f"重载完成: {success_count}/{len(available_scripts)} 个脚本成功")
|
||||
print(f"[脚本系统] 重载脚本: {success_count}/{len(available_scripts)} 成功")
|
||||
else:
|
||||
@ -166,13 +163,11 @@ class AppActions:
|
||||
except Exception as e:
|
||||
self.add_error_message(f"重载脚本失败: {str(e)}")
|
||||
print(f"[脚本系统] 重载脚本失败: {e}")
|
||||
|
||||
|
||||
def _on_script_selected(self, script_name):
|
||||
"""处理脚本选择事件"""
|
||||
print(f"[脚本系统] 选择脚本: {script_name}")
|
||||
self.add_info_message(f"已选择脚本: {script_name}")
|
||||
|
||||
|
||||
def _edit_script(self, script_name):
|
||||
"""编辑脚本"""
|
||||
@ -182,11 +177,11 @@ class AppActions:
|
||||
script_info = self.script_manager.get_script_info(script_name)
|
||||
if script_info and script_info.get("file"):
|
||||
script_path = script_info["file"]
|
||||
|
||||
|
||||
# 打开系统默认编辑器
|
||||
import subprocess
|
||||
import platform
|
||||
|
||||
|
||||
system = platform.system()
|
||||
try:
|
||||
if system == "Windows":
|
||||
@ -195,7 +190,7 @@ class AppActions:
|
||||
subprocess.run(['open', script_path])
|
||||
else: # Linux
|
||||
subprocess.run(['xdg-open', script_path])
|
||||
|
||||
|
||||
self.add_success_message(f"已打开脚本编辑器: {script_name}")
|
||||
print(f"[脚本系统] 编辑脚本: {script_path}")
|
||||
except Exception as e:
|
||||
@ -207,16 +202,15 @@ class AppActions:
|
||||
except Exception as e:
|
||||
self.add_error_message(f"编辑脚本失败: {str(e)}")
|
||||
print(f"[脚本系统] 编辑脚本失败: {e}")
|
||||
|
||||
|
||||
def _mount_script_to_selected(self, script_name):
|
||||
"""挂载脚本到选中对象"""
|
||||
selected_node = self._get_selection_node()
|
||||
|
||||
|
||||
if not selected_node or selected_node.isEmpty():
|
||||
self.add_error_message("请先选择一个对象")
|
||||
return
|
||||
|
||||
|
||||
try:
|
||||
if hasattr(self, 'script_manager') and self.script_manager:
|
||||
script_component = self.script_manager.add_script_to_object(selected_node, script_name)
|
||||
@ -230,16 +224,15 @@ class AppActions:
|
||||
except Exception as e:
|
||||
self.add_error_message(f"挂载脚本失败: {str(e)}")
|
||||
print(f"[脚本系统] 挂载脚本失败: {e}")
|
||||
|
||||
|
||||
def _unmount_script_from_selected(self, script_name):
|
||||
"""从选中对象卸载脚本"""
|
||||
selected_node = self._get_selection_node()
|
||||
|
||||
|
||||
if not selected_node or selected_node.isEmpty():
|
||||
self.add_error_message("请先选择一个对象")
|
||||
return
|
||||
|
||||
|
||||
try:
|
||||
if hasattr(self, 'script_manager') and self.script_manager:
|
||||
result = self.script_manager.remove_script_from_object(selected_node, script_name)
|
||||
@ -253,21 +246,18 @@ class AppActions:
|
||||
except Exception as e:
|
||||
self.add_error_message(f"卸载脚本失败: {str(e)}")
|
||||
print(f"[脚本系统] 卸载脚本失败: {e}")
|
||||
|
||||
|
||||
# ==================== 菜单处理函数 ====================
|
||||
|
||||
|
||||
def _on_new_project(self):
|
||||
"""处理新建项目菜单项"""
|
||||
self.add_info_message("打开新建项目对话框")
|
||||
self.show_new_project_dialog = True
|
||||
|
||||
|
||||
def _on_open_project(self):
|
||||
"""处理打开项目菜单项"""
|
||||
self.add_info_message("打开项目对话框")
|
||||
self.show_open_project_dialog = True
|
||||
|
||||
|
||||
def _on_save_project(self):
|
||||
"""处理保存项目菜单项"""
|
||||
@ -278,7 +268,7 @@ class AppActions:
|
||||
self.add_warning_message("没有当前项目路径,请先创建或打开项目")
|
||||
self.show_save_as_dialog = True
|
||||
return
|
||||
|
||||
|
||||
# 直接调用保存逻辑,避免Qt依赖
|
||||
if self._save_project_impl():
|
||||
self.add_success_message("项目保存成功")
|
||||
@ -288,7 +278,6 @@ class AppActions:
|
||||
self.add_error_message(f"项目保存失败: {e}")
|
||||
else:
|
||||
self.add_error_message("项目管理器未初始化")
|
||||
|
||||
|
||||
def _on_save_as_project(self):
|
||||
"""处理另存为项目菜单项"""
|
||||
@ -311,7 +300,8 @@ class AppActions:
|
||||
_, active_profile = project_manager._get_active_build_profile()
|
||||
profile_output_dir = str((active_profile or {}).get("output_dir", "") or "").strip()
|
||||
if layout and profile_output_dir:
|
||||
self.build_output_path = os.path.normpath(os.path.join(current_project_path, profile_output_dir.replace("/", os.sep)))
|
||||
self.build_output_path = os.path.normpath(
|
||||
os.path.join(current_project_path, profile_output_dir.replace("/", os.sep)))
|
||||
else:
|
||||
self.build_output_path = layout.builds_root if layout else os.path.dirname(current_project_path)
|
||||
else:
|
||||
@ -362,12 +352,10 @@ class AppActions:
|
||||
self.add_error_message(f"项目打包失败: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def _show_about_dialog(self):
|
||||
"""显示关于对话框。"""
|
||||
self.show_about_dialog = True
|
||||
|
||||
|
||||
def _get_documentation_target(self):
|
||||
"""返回优先级最高的本地帮助文档。"""
|
||||
candidate_names = [
|
||||
@ -383,7 +371,6 @@ class AppActions:
|
||||
return candidate_path
|
||||
return None
|
||||
|
||||
|
||||
def _open_documentation(self):
|
||||
"""打开本地项目文档。"""
|
||||
doc_path = self._get_documentation_target()
|
||||
@ -401,57 +388,46 @@ class AppActions:
|
||||
except Exception as e:
|
||||
self.add_error_message(f"打开文档失败: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def _on_exit(self):
|
||||
"""处理退出菜单项"""
|
||||
self.add_info_message("退出应用程序")
|
||||
self.userExit()
|
||||
|
||||
|
||||
# ==================== 键盘事件处理函数 ====================
|
||||
|
||||
|
||||
def _on_ctrl_pressed(self):
|
||||
"""Ctrl键按下"""
|
||||
self.ctrl_pressed = True
|
||||
|
||||
|
||||
def _on_ctrl_released(self):
|
||||
"""Ctrl键释放"""
|
||||
self.ctrl_pressed = False
|
||||
|
||||
|
||||
def _on_alt_pressed(self):
|
||||
"""Alt键按下"""
|
||||
self.alt_pressed = True
|
||||
|
||||
|
||||
def _on_alt_released(self):
|
||||
"""Alt键释放"""
|
||||
self.alt_pressed = False
|
||||
|
||||
|
||||
def _on_n_pressed(self):
|
||||
"""N键按下 - 检查Ctrl+N组合键"""
|
||||
if self.ctrl_pressed:
|
||||
self._on_new_project()
|
||||
|
||||
|
||||
def _on_o_pressed(self):
|
||||
"""O键按下 - 检查Ctrl+O组合键"""
|
||||
if self.ctrl_pressed:
|
||||
self._on_open_project()
|
||||
|
||||
|
||||
|
||||
|
||||
def _on_f4_pressed(self):
|
||||
"""F4键按下 - 检查Alt+F4组合键"""
|
||||
if self.alt_pressed:
|
||||
self._on_exit()
|
||||
|
||||
|
||||
# 移除了单独的按键处理方法,现在直接使用组合键事件
|
||||
|
||||
|
||||
def _on_delete_pressed(self):
|
||||
"""Delete键按下 - 删除选中节点"""
|
||||
@ -464,24 +440,23 @@ class AppActions:
|
||||
if self.lui_manager.selected_index >= 0:
|
||||
self.lui_manager.selected_index = -1
|
||||
print("✓ 已取消LUI组件选中")
|
||||
|
||||
|
||||
# 2. 取消 3D 场景选择
|
||||
if hasattr(self, 'selection') and self.selection:
|
||||
if self._get_selection_node() or self._get_ssbo_selection_summary():
|
||||
self.selection.clearSelection()
|
||||
print("✓ 已取消场景节点选中")
|
||||
|
||||
|
||||
def _on_wheel_up(self):
|
||||
"""滚轮向上滚动 - 相机前进"""
|
||||
try:
|
||||
if not self.camera_control_enabled:
|
||||
return
|
||||
|
||||
|
||||
# 检查鼠标是否在ImGui窗口上
|
||||
if self._is_mouse_over_imgui():
|
||||
return
|
||||
|
||||
|
||||
# 沿相机前向向量移动
|
||||
forward = self.camera.getMat().getRow3(1)
|
||||
distance = 20.0 * globalClock.getDt()
|
||||
@ -490,7 +465,6 @@ class AppActions:
|
||||
self.camera.setPos(newPos)
|
||||
except Exception as e:
|
||||
print(f"滚轮前进失败: {e}")
|
||||
|
||||
|
||||
def _on_wheel_down(self):
|
||||
"""滚轮向下滚动 - 相机后退"""
|
||||
@ -498,7 +472,7 @@ class AppActions:
|
||||
# 检查鼠标是否在ImGui窗口上
|
||||
if self._is_mouse_over_imgui():
|
||||
return
|
||||
|
||||
|
||||
# 沿相机前向向量移动
|
||||
forward = self.camera.getMat().getRow3(1)
|
||||
distance = -20.0 * globalClock.getDt()
|
||||
@ -507,7 +481,6 @@ class AppActions:
|
||||
self.camera.setPos(newPos)
|
||||
except Exception as e:
|
||||
print(f"滚轮后退失败: {e}")
|
||||
|
||||
|
||||
def _is_mouse_over_imgui(self):
|
||||
"""检测鼠标是否在ImGui窗口上"""
|
||||
@ -532,7 +505,6 @@ class AppActions:
|
||||
except Exception as e:
|
||||
print(f"ImGui界面检测失败: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def processImGuiMouseClick(self, x, y):
|
||||
"""处理ImGui鼠标点击事件,返回是否消费了该事件"""
|
||||
@ -554,7 +526,7 @@ class AppActions:
|
||||
pass
|
||||
|
||||
return False
|
||||
|
||||
|
||||
except Exception as e:
|
||||
print(f"ImGui鼠标点击处理失败: {e}")
|
||||
return False
|
||||
@ -585,54 +557,49 @@ class AppActions:
|
||||
|
||||
def _is_point_in_known_imgui_rects(self, point):
|
||||
for rect_name in (
|
||||
"_resource_manager_window_rect",
|
||||
"_scene_tree_window_rect",
|
||||
"_property_panel_window_rect",
|
||||
"_script_panel_window_rect",
|
||||
"_console_window_rect",
|
||||
"_toolbar_window_rect",
|
||||
"_resource_manager_window_rect",
|
||||
"_scene_tree_window_rect",
|
||||
"_property_panel_window_rect",
|
||||
"_script_panel_window_rect",
|
||||
"_console_window_rect",
|
||||
"_toolbar_window_rect",
|
||||
):
|
||||
if self._point_in_rect(point, getattr(self, rect_name, None)):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
# ==================== 消息系统 ====================
|
||||
|
||||
|
||||
def add_message(self, text, color=(1.0, 1.0, 1.0, 1.0)):
|
||||
"""添加消息到消息列表,同时输出到终端"""
|
||||
import datetime
|
||||
timestamp = datetime.datetime.now().strftime("%H:%M:%S")
|
||||
|
||||
|
||||
# 输出到终端
|
||||
print(f"[{timestamp}] {text}")
|
||||
|
||||
|
||||
# 添加到GUI消息列表
|
||||
self.messages.append({
|
||||
'text': text,
|
||||
'color': color,
|
||||
'timestamp': timestamp
|
||||
})
|
||||
|
||||
|
||||
# 限制消息数量
|
||||
if len(self.messages) > self.max_messages:
|
||||
self.messages = self.messages[-self.max_messages:]
|
||||
|
||||
|
||||
def add_success_message(self, text):
|
||||
"""添加成功消息"""
|
||||
self.add_message(f"✓ {text}", (0.176, 1.0, 0.769, 1.0))
|
||||
|
||||
|
||||
def add_error_message(self, text):
|
||||
"""添加错误消息"""
|
||||
self.add_message(f"✗ {text}", (1.0, 0.3, 0.3, 1.0))
|
||||
|
||||
|
||||
def add_warning_message(self, text):
|
||||
"""添加警告消息"""
|
||||
self.add_message(f"⚠ {text}", (0.953, 0.616, 0.471, 1.0))
|
||||
|
||||
|
||||
def add_info_message(self, text):
|
||||
"""添加信息消息"""
|
||||
@ -693,7 +660,8 @@ class AppActions:
|
||||
else:
|
||||
selected_node = None
|
||||
try:
|
||||
selected_node = selection.getSelectedNode() if hasattr(selection, "getSelectedNode") else getattr(selection, "selectedNode", None)
|
||||
selected_node = selection.getSelectedNode() if hasattr(selection, "getSelectedNode") else getattr(
|
||||
selection, "selectedNode", None)
|
||||
except Exception:
|
||||
selected_node = None
|
||||
if not self._is_history_node_valid(selected_node, require_attached=True):
|
||||
@ -746,7 +714,6 @@ class AppActions:
|
||||
self.add_warning_message("没有可撤销的操作")
|
||||
except Exception as e:
|
||||
self.add_error_message(f"撤销操作失败: {e}")
|
||||
|
||||
|
||||
def _on_redo(self):
|
||||
"""处理重做操作"""
|
||||
@ -771,7 +738,6 @@ class AppActions:
|
||||
self.add_warning_message("没有可重做的操作")
|
||||
except Exception as e:
|
||||
self.add_error_message(f"重做操作失败: {e}")
|
||||
|
||||
|
||||
def _on_copy(self):
|
||||
"""处理复制操作"""
|
||||
@ -779,18 +745,18 @@ class AppActions:
|
||||
if not hasattr(self, 'selection') or not self.selection:
|
||||
self.add_error_message("选择系统未初始化")
|
||||
return
|
||||
|
||||
|
||||
# 获取当前选中的节点
|
||||
selected_node = self._resolve_cut_copy_node(self._get_selection_source_node())
|
||||
if not selected_node:
|
||||
self.add_warning_message("没有选中的节点")
|
||||
return
|
||||
|
||||
|
||||
# 检查节点有效性(不能复制根节点)
|
||||
if selected_node.getName() == "render":
|
||||
self.add_warning_message("不能复制根节点")
|
||||
return
|
||||
|
||||
|
||||
# 序列化节点
|
||||
if hasattr(self, 'scene_manager') and self.scene_manager:
|
||||
node_data = self.scene_manager.serializeNodeForCopy(selected_node)
|
||||
@ -806,7 +772,6 @@ class AppActions:
|
||||
self.add_error_message("场景管理器未初始化")
|
||||
except Exception as e:
|
||||
self.add_error_message(f"复制操作失败: {e}")
|
||||
|
||||
|
||||
def _on_cut(self):
|
||||
"""处理剪切操作"""
|
||||
@ -814,19 +779,19 @@ class AppActions:
|
||||
if not hasattr(self, 'selection') or not self.selection:
|
||||
self.add_error_message("选择系统未初始化")
|
||||
return
|
||||
|
||||
|
||||
# 获取当前选中的节点
|
||||
selected_node = self._resolve_cut_copy_node(self._get_selection_source_node())
|
||||
if not selected_node:
|
||||
self.add_warning_message("没有选中的节点")
|
||||
return
|
||||
|
||||
|
||||
# 检查节点有效性(不能剪切根节点和系统节点)
|
||||
node_name = selected_node.getName()
|
||||
if node_name == "render":
|
||||
self.add_warning_message("不能剪切根节点")
|
||||
return
|
||||
|
||||
|
||||
# 序列化节点
|
||||
if hasattr(self, 'scene_manager') and self.scene_manager:
|
||||
node_data = self.scene_manager.serializeNodeForCopy(selected_node)
|
||||
@ -835,7 +800,7 @@ class AppActions:
|
||||
# Cut preserves the source node references for cloning in paste
|
||||
self.clipboard_source_nodes = [selected_node]
|
||||
self.clipboard_mode = "cut"
|
||||
|
||||
|
||||
# 删除原节点
|
||||
if self._delete_node(selected_node):
|
||||
self.selection.clearSelection()
|
||||
@ -848,7 +813,6 @@ class AppActions:
|
||||
self.add_error_message("场景管理器未初始化")
|
||||
except Exception as e:
|
||||
self.add_error_message(f"剪切操作失败: {e}")
|
||||
|
||||
|
||||
def _on_paste(self):
|
||||
"""处理粘贴操作"""
|
||||
@ -856,11 +820,11 @@ class AppActions:
|
||||
if not self.clipboard:
|
||||
self.add_warning_message("剪切板为空")
|
||||
return
|
||||
|
||||
|
||||
if not hasattr(self, 'scene_manager') or not self.scene_manager:
|
||||
self.add_error_message("场景管理器未初始化")
|
||||
return
|
||||
|
||||
|
||||
# 确定粘贴目标父节点
|
||||
parent_node = None
|
||||
if hasattr(self, 'selection') and self.selection:
|
||||
@ -873,11 +837,11 @@ class AppActions:
|
||||
else:
|
||||
p = selected_node.getParent()
|
||||
parent_node = p if p and not p.isEmpty() else self.render
|
||||
|
||||
|
||||
# 如果没有选中节点,使用渲染根节点
|
||||
if not parent_node:
|
||||
parent_node = self.render
|
||||
|
||||
|
||||
# 反序列化并添加节点
|
||||
created_any = False
|
||||
last_created_node = None
|
||||
@ -897,10 +861,12 @@ class AppActions:
|
||||
else:
|
||||
created = source_node.copyTo(parent)
|
||||
if hasattr(self.scene_manager, "_generateUniqueName"):
|
||||
unique_name = self.scene_manager._generateUniqueName(source_node.getName(), parent)
|
||||
unique_name = self.scene_manager._generateUniqueName(source_node.getName(),
|
||||
parent)
|
||||
created.setName(unique_name)
|
||||
# Preserve model source tags so later cut/paste can rebuild real model.
|
||||
for _tag in ("model_path", "saved_model_path", "original_path", "file", "element_type"):
|
||||
for _tag in ("model_path", "saved_model_path", "original_path", "file",
|
||||
"element_type"):
|
||||
if source_node.hasTag(_tag):
|
||||
created.setTag(_tag, source_node.getTag(_tag))
|
||||
# Offset slightly so the new node can be seen immediately.
|
||||
@ -929,7 +895,8 @@ class AppActions:
|
||||
if hasattr(self.command_manager, "pop_last_command"):
|
||||
last_cmd = self.command_manager.pop_last_command()
|
||||
|
||||
if isinstance(last_cmd, DeleteNodeCommand) and getattr(last_cmd, "node", None) == source_node:
|
||||
if isinstance(last_cmd, DeleteNodeCommand) and getattr(last_cmd, "node",
|
||||
None) == source_node:
|
||||
attach_cmd.execute()
|
||||
self.command_manager.record_command(CompositeCommand([last_cmd, attach_cmd]))
|
||||
new_node = source_node
|
||||
@ -991,7 +958,6 @@ class AppActions:
|
||||
self.clipboard_mode = ""
|
||||
except Exception as e:
|
||||
self.add_error_message(f"粘贴操作失败: {e}")
|
||||
|
||||
|
||||
def _on_delete(self):
|
||||
"""处理删除操作"""
|
||||
@ -999,19 +965,19 @@ class AppActions:
|
||||
if not hasattr(self, 'selection') or not self.selection:
|
||||
self.add_error_message("选择系统未初始化")
|
||||
return
|
||||
|
||||
|
||||
# 获取当前选中的节点
|
||||
selected_node = self._get_selection_source_node()
|
||||
if not selected_node:
|
||||
self.add_warning_message("没有选中的节点")
|
||||
return
|
||||
|
||||
|
||||
# 检查节点有效性(不能删除根节点)
|
||||
node_name = selected_node.getName()
|
||||
if node_name == "render":
|
||||
self.add_warning_message("不能删除根节点")
|
||||
return
|
||||
|
||||
|
||||
# 删除节点
|
||||
if hasattr(self, 'scene_manager') and self.scene_manager:
|
||||
self._delete_node(selected_node)
|
||||
@ -1021,14 +987,13 @@ class AppActions:
|
||||
self.add_error_message("场景管理器未初始化")
|
||||
except Exception as e:
|
||||
self.add_error_message(f"删除操作失败: {e}")
|
||||
|
||||
|
||||
def _delete_node(self, node):
|
||||
"""删除节点的通用方法 - 使用命令系统"""
|
||||
try:
|
||||
if not node or node.isEmpty():
|
||||
return False
|
||||
|
||||
|
||||
node_name = node.getName() or "未命名节点"
|
||||
parent = node.getParent()
|
||||
ssbo_editor = getattr(self, "ssbo_editor", None)
|
||||
@ -1070,7 +1035,7 @@ class AppActions:
|
||||
pass
|
||||
print(f"[SSBO] 已从源模型树移除并重建运行时: {node_name}")
|
||||
return True
|
||||
|
||||
|
||||
# 创建删除命令
|
||||
if hasattr(self, 'command_manager') and self.command_manager:
|
||||
from core.Command_System import DeleteNodeCommand
|
||||
@ -1088,26 +1053,25 @@ class AppActions:
|
||||
ssbo_editor.on_model_deleted(node)
|
||||
except Exception as e:
|
||||
print(f"[SSBO] 删除模型后清理失败: {e}")
|
||||
|
||||
|
||||
print(f"[删除] 成功删除节点: {node_name}")
|
||||
return True
|
||||
|
||||
|
||||
except Exception as e:
|
||||
print(f"[删除] 删除节点失败: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def _perform_node_cleanup(self, node):
|
||||
"""执行节点清理逻辑"""
|
||||
try:
|
||||
node_name = node.getName() or "未命名节点"
|
||||
|
||||
|
||||
# 从场景管理器的模型列表中移除(如果是模型)
|
||||
if hasattr(self, 'scene_manager') and self.scene_manager:
|
||||
if node in self.scene_manager.models:
|
||||
self.scene_manager.models.remove(node)
|
||||
print(f"[场景管理器] 从模型列表移除: {node_name}")
|
||||
|
||||
|
||||
# 停止所有与该节点相关的脚本
|
||||
if hasattr(self, 'script_manager') and self.script_manager:
|
||||
try:
|
||||
@ -1117,7 +1081,7 @@ class AppActions:
|
||||
print(f"[脚本系统] 移除节点 {node_name} 的所有脚本")
|
||||
except Exception as e:
|
||||
print(f"[脚本系统] 移除脚本失败: {e}")
|
||||
|
||||
|
||||
# 清理碰撞体
|
||||
if hasattr(self, 'collision_manager') and self.collision_manager:
|
||||
try:
|
||||
@ -1125,7 +1089,7 @@ class AppActions:
|
||||
print(f"[碰撞系统] 移除节点 {node_name} 的碰撞体")
|
||||
except Exception as e:
|
||||
print(f"[碰撞系统] 移除碰撞体失败: {e}")
|
||||
|
||||
|
||||
# 清理Actor缓存(如果有动画)
|
||||
if hasattr(self, '_actor_cache') and node in self._actor_cache:
|
||||
actor = self._actor_cache[node]
|
||||
@ -1141,19 +1105,18 @@ class AppActions:
|
||||
print(f"[动画系统] 清理Actor缓存失败: {e}")
|
||||
finally:
|
||||
del self._actor_cache[node]
|
||||
|
||||
|
||||
except Exception as e:
|
||||
print(f"[清理] 节点清理失败: {e}")
|
||||
|
||||
|
||||
# ==================== 对话框绘制函数 ====================
|
||||
|
||||
|
||||
def _create_new_project(self, name, path):
|
||||
"""创建新项目的实际实现"""
|
||||
if not hasattr(self, 'project_manager') or not self.project_manager:
|
||||
print("✗ 项目管理器未初始化")
|
||||
return
|
||||
|
||||
|
||||
try:
|
||||
if self._create_new_project_impl(name, path):
|
||||
print(f"✓ 项目创建成功: {name}")
|
||||
@ -1161,14 +1124,13 @@ class AppActions:
|
||||
print(f"✗ 项目创建失败: {name}")
|
||||
except Exception as e:
|
||||
print(f"✗ 项目创建失败: {e}")
|
||||
|
||||
|
||||
def _open_project_path(self, path):
|
||||
"""打开项目的实际实现"""
|
||||
if not hasattr(self, 'project_manager') or not self.project_manager:
|
||||
print("✗ 项目管理器未初始化")
|
||||
return
|
||||
|
||||
|
||||
try:
|
||||
print(f"打开项目: {path}")
|
||||
if self._open_project_impl(path):
|
||||
@ -1177,16 +1139,14 @@ class AppActions:
|
||||
print(f"✗ 项目打开失败: {path}")
|
||||
except Exception as e:
|
||||
print(f"✗ 项目打开失败: {e}")
|
||||
|
||||
|
||||
# ==================== 项目管理具体实现 ====================
|
||||
|
||||
|
||||
def _save_project_impl(self):
|
||||
"""保存项目的具体实现(不依赖Qt)"""
|
||||
if not hasattr(self, 'project_manager') or not self.project_manager:
|
||||
return False
|
||||
return self.project_manager.saveProject()
|
||||
|
||||
|
||||
def _open_project_impl(self, project_path):
|
||||
"""打开项目的具体实现(不依赖Qt)"""
|
||||
@ -1202,16 +1162,15 @@ class AppActions:
|
||||
|
||||
project_name = os.path.basename(project_path)
|
||||
self._update_window_title(project_name)
|
||||
|
||||
|
||||
print(f"✓ 项目打开成功: {project_path}")
|
||||
self.add_success_message(f"项目打开成功: {project_name}")
|
||||
return True
|
||||
|
||||
|
||||
except Exception as e:
|
||||
print(f"✗ 打开项目时发生错误: {e}")
|
||||
self.add_error_message(f"打开项目时发生错误: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def _create_new_project_impl(self, name, path):
|
||||
"""创建新项目的具体实现(不依赖Qt)"""
|
||||
@ -1225,7 +1184,6 @@ class AppActions:
|
||||
print(f"创建项目失败: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def _save_project_as_impl(self, name, path):
|
||||
"""将当前项目保存到新的项目目录。"""
|
||||
if not hasattr(self, "project_manager") or not self.project_manager:
|
||||
@ -1291,7 +1249,8 @@ class AppActions:
|
||||
if previous_project_path:
|
||||
self.project_manager._sync_project_script_manager(previous_project_path, reload_scripts=True)
|
||||
self.project_manager._sync_resource_manager_root(previous_project_path)
|
||||
self._update_window_title(os.path.basename(previous_project_path) if previous_project_path else "未命名项目")
|
||||
self._update_window_title(
|
||||
os.path.basename(previous_project_path) if previous_project_path else "未命名项目")
|
||||
self.add_error_message("项目另存为失败")
|
||||
return False
|
||||
|
||||
@ -1318,7 +1277,6 @@ class AppActions:
|
||||
source_file = os.path.join(root, file_name)
|
||||
target_file = os.path.join(destination_root, file_name)
|
||||
shutil.copy2(source_file, target_file)
|
||||
|
||||
|
||||
def _update_window_title(self, project_name):
|
||||
"""更新窗口标题"""
|
||||
@ -1329,9 +1287,8 @@ class AppActions:
|
||||
print(f"窗口标题已更新: MetaCore - {project_name}")
|
||||
except Exception as e:
|
||||
print(f"更新窗口标题失败: {e}")
|
||||
|
||||
|
||||
# ==================== 路径浏览器辅助方法 ====================
|
||||
|
||||
|
||||
def _refresh_ssbo_runtime_import_bindings(self, file_path=None, scene_package_import=False):
|
||||
ssbo_editor = getattr(self, 'ssbo_editor', None)
|
||||
@ -1545,8 +1502,6 @@ class AppActions:
|
||||
except Exception:
|
||||
self._scene_tree_epoch = 1
|
||||
animated_model = bool(file_path and self._model_file_has_animation(file_path))
|
||||
lower_path = str(file_path).lower() if file_path else ""
|
||||
is_gltf_family = lower_path.endswith((".glb", ".gltf"))
|
||||
if animated_model:
|
||||
prefer_scene_manager = True
|
||||
ssbo_editor = getattr(self, 'ssbo_editor', None)
|
||||
@ -1556,11 +1511,6 @@ class AppActions:
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if is_gltf_family:
|
||||
fallback_model = self._import_model_via_gltf_fallback(file_path)
|
||||
if fallback_model:
|
||||
return fallback_model
|
||||
|
||||
if self.use_ssbo_mouse_picking and getattr(self, 'ssbo_editor', None):
|
||||
if animated_model:
|
||||
print(f"[AnimationImport] 检测到动画模型,跳过SSBO导入: {file_path}")
|
||||
@ -1627,9 +1577,9 @@ class AppActions:
|
||||
except Exception:
|
||||
camera_count = 0
|
||||
for camera_name in (
|
||||
"gizmo_overlay_cam",
|
||||
"pick_camera",
|
||||
"selection_outline_mask_camera",
|
||||
"gizmo_overlay_cam",
|
||||
"pick_camera",
|
||||
"selection_outline_mask_camera",
|
||||
):
|
||||
try:
|
||||
special_camera_counts[camera_name] = render.find_all_matches(
|
||||
@ -1648,11 +1598,11 @@ class AppActions:
|
||||
interesting_tasks = [
|
||||
name for name in task_names
|
||||
if (
|
||||
"gizmo" in str(name).lower()
|
||||
or "outline" in str(name).lower()
|
||||
or "pick" in str(name).lower()
|
||||
or "lui" in str(name).lower()
|
||||
or "canvas" in str(name).lower()
|
||||
"gizmo" in str(name).lower()
|
||||
or "outline" in str(name).lower()
|
||||
or "pick" in str(name).lower()
|
||||
or "lui" in str(name).lower()
|
||||
or "canvas" in str(name).lower()
|
||||
)
|
||||
]
|
||||
|
||||
@ -1667,12 +1617,12 @@ class AppActions:
|
||||
pass
|
||||
|
||||
def _import_model_with_menu_logic(
|
||||
self,
|
||||
file_path,
|
||||
select_model=True,
|
||||
set_origin=True,
|
||||
show_info_message=True,
|
||||
show_success_message=True,
|
||||
self,
|
||||
file_path,
|
||||
select_model=True,
|
||||
set_origin=True,
|
||||
show_info_message=True,
|
||||
show_success_message=True,
|
||||
):
|
||||
"""统一的单文件导入入口,保持与菜单导入一致的处理流程。"""
|
||||
try:
|
||||
@ -1733,33 +1683,17 @@ class AppActions:
|
||||
|
||||
if hasattr(self.scene_manager, 'models'):
|
||||
if getattr(self, "use_ssbo_mouse_picking", False):
|
||||
try:
|
||||
existing_models = []
|
||||
for existing in getattr(self.scene_manager, "models", []):
|
||||
try:
|
||||
if existing and not existing.isEmpty():
|
||||
existing_models.append(existing)
|
||||
except Exception:
|
||||
continue
|
||||
if model_node and all(existing != model_node for existing in existing_models):
|
||||
existing_models.append(model_node)
|
||||
self.scene_manager.models = existing_models
|
||||
except Exception:
|
||||
self.scene_manager.models = [model_node] if model_node else []
|
||||
self.scene_manager.models = [model_node]
|
||||
elif model_node not in self.scene_manager.models:
|
||||
self.scene_manager.models.append(model_node)
|
||||
|
||||
if select_model:
|
||||
if (
|
||||
getattr(self, "use_ssbo_mouse_picking", False)
|
||||
and getattr(self, "ssbo_editor", None)
|
||||
and getattr(self.ssbo_editor, "last_import_tree_key", None)
|
||||
getattr(self, "use_ssbo_mouse_picking", False)
|
||||
and getattr(self, "ssbo_editor", None)
|
||||
and getattr(self.ssbo_editor, "last_import_tree_key", None)
|
||||
):
|
||||
self.ssbo_editor.clear_selection(sync_world_selection=False)
|
||||
if hasattr(self.ssbo_editor, "_sync_editor_selection_reference"):
|
||||
self.ssbo_editor._sync_editor_selection_reference(None)
|
||||
if show_info_message:
|
||||
self.add_info_message("模型导入完成,已保持 SSBO 静态模式以避免导入后掉帧")
|
||||
self.ssbo_editor.select_node(self.ssbo_editor.last_import_tree_key)
|
||||
elif hasattr(self, 'selection') and self.selection:
|
||||
self.selection.updateSelection(model_node)
|
||||
|
||||
@ -1784,7 +1718,6 @@ class AppActions:
|
||||
"""处理导入模型菜单项"""
|
||||
self.add_info_message("打开系统文件选择器")
|
||||
self.show_import_dialog = True
|
||||
|
||||
|
||||
def _import_model(self):
|
||||
"""导入模型的具体实现"""
|
||||
|
||||
Loading…
Reference in New Issue
Block a user