299 lines
11 KiB
Python
299 lines
11 KiB
Python
import os
|
|
from pathlib import Path
|
|
from imgui_bundle import imgui, imgui_ctx
|
|
|
|
|
|
class InteractionPanels:
|
|
"""Drag-and-drop and context menu interaction panels."""
|
|
|
|
def __init__(self, app):
|
|
self.app = app
|
|
|
|
def __getattr__(self, name):
|
|
return getattr(self.app, name)
|
|
|
|
def __setattr__(self, name, value):
|
|
if name == "app" or name in self.__dict__ or hasattr(type(self), name):
|
|
object.__setattr__(self, name, value)
|
|
else:
|
|
setattr(self.app, name, value)
|
|
|
|
|
|
def _draw_drag_drop_interface(self):
|
|
"""处理资源管理器到场景/树的拖拽完成。"""
|
|
if self.resource_manager.is_dragging():
|
|
self.is_dragging = True
|
|
self.dragged_files = self.resource_manager.get_dragged_files()
|
|
|
|
if self.is_dragging and self.dragged_files and imgui.is_mouse_released(0):
|
|
self._handle_drag_drop_completion()
|
|
|
|
|
|
def _handle_drag_drop_completion(self):
|
|
"""处理资源管理器内部拖拽落点。"""
|
|
dragged_files = list(self.dragged_files) if self.dragged_files else self.resource_manager.get_dragged_files()
|
|
if not dragged_files:
|
|
self._reset_drag_state()
|
|
return
|
|
|
|
mouse_pos = imgui.get_mouse_pos()
|
|
drop_kind, target_dir, target_parent = self._resolve_drop_target(mouse_pos)
|
|
|
|
if drop_kind == "resource_manager":
|
|
moved, errors = self.resource_manager.move_files_to_directory(dragged_files, target_dir)
|
|
if moved:
|
|
label = self.resource_manager.get_relative_path(target_dir) if target_dir else "资源管理器"
|
|
self.add_success_message(f"已移动 {len(moved)} 个资源到 {label}")
|
|
if errors:
|
|
self.add_warning_message(f"有 {len(errors)} 个资源移动失败,请查看控制台")
|
|
for err in errors:
|
|
print(f"[资源移动] {err}")
|
|
self._reset_drag_state()
|
|
return
|
|
|
|
if drop_kind not in ("scene_view", "scene_tree"):
|
|
self._reset_drag_state()
|
|
return
|
|
|
|
imported_models = []
|
|
for file_path in dragged_files:
|
|
path = Path(file_path)
|
|
if not self._is_supported_model(path):
|
|
continue
|
|
try:
|
|
model_node = self._import_model_for_runtime(str(path), prefer_scene_manager=True)
|
|
if not model_node:
|
|
continue
|
|
if getattr(self, "scene_manager", None):
|
|
try:
|
|
self.scene_manager.processMaterials(model_node)
|
|
except Exception:
|
|
pass
|
|
if drop_kind == "scene_tree" and self._is_valid_drop_parent(target_parent):
|
|
try:
|
|
model_node.wrtReparentTo(target_parent)
|
|
except Exception:
|
|
model_node.reparentTo(target_parent)
|
|
self._ensure_unique_name(model_node)
|
|
imported_models.append(model_node)
|
|
except Exception as e:
|
|
print(f"[拖拽导入] 导入失败 {path}: {e}")
|
|
|
|
if imported_models:
|
|
if self.selection:
|
|
try:
|
|
self.selection.updateSelection(imported_models[-1])
|
|
except Exception:
|
|
pass
|
|
self.add_success_message(f"拖拽导入成功: {len(imported_models)} 个模型")
|
|
|
|
self._reset_drag_state()
|
|
|
|
|
|
@staticmethod
|
|
def _point_in_rect(point, rect):
|
|
if not point or not rect:
|
|
return False
|
|
x, y = point
|
|
rx, ry, rw, rh = rect
|
|
return rx <= x <= rx + rw and ry <= y <= ry + rh
|
|
|
|
|
|
def _resolve_drop_target(self, mouse_pos):
|
|
point = (float(mouse_pos.x), float(mouse_pos.y))
|
|
if self._point_in_rect(point, getattr(self, "_resource_manager_window_rect", None)):
|
|
target_dir = self._resolve_resource_drop_target_dir(point)
|
|
return "resource_manager", target_dir, None
|
|
|
|
if self._point_in_rect(point, getattr(self, "_scene_tree_window_rect", None)):
|
|
return "scene_tree", None, getattr(self, "_drag_scene_tree_hover_node", None)
|
|
|
|
for rect_name in ("_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 "ui_panel", None, None
|
|
|
|
if imgui.get_io().want_capture_mouse:
|
|
return "ui_panel", None, None
|
|
|
|
return "scene_view", None, None
|
|
|
|
|
|
@staticmethod
|
|
def _is_supported_model(path):
|
|
return path.suffix.lower() in {'.gltf', '.glb', '.fbx', '.bam', '.egg', '.obj'}
|
|
|
|
|
|
@staticmethod
|
|
def _is_valid_drop_parent(node):
|
|
if not node:
|
|
return False
|
|
try:
|
|
return not node.isEmpty()
|
|
except Exception:
|
|
return False
|
|
|
|
|
|
def _ensure_unique_name(self, model_node):
|
|
if not model_node or model_node.isEmpty():
|
|
return
|
|
parent = model_node.getParent()
|
|
if not parent or parent.isEmpty():
|
|
return
|
|
|
|
current_name = model_node.getName() or "model"
|
|
stem, suffix = os.path.splitext(current_name)
|
|
if not stem:
|
|
stem = current_name
|
|
|
|
existing_names = set()
|
|
try:
|
|
siblings = parent.getChildren()
|
|
for i in range(siblings.getNumPaths()):
|
|
sibling = siblings.getPath(i)
|
|
if sibling == model_node:
|
|
continue
|
|
existing_names.add(sibling.getName())
|
|
except Exception:
|
|
return
|
|
|
|
if current_name not in existing_names:
|
|
return
|
|
|
|
index = 1
|
|
while True:
|
|
if suffix:
|
|
candidate = f"{stem}_{index}{suffix}"
|
|
else:
|
|
candidate = f"{stem}_{index}"
|
|
if candidate not in existing_names:
|
|
model_node.setName(candidate)
|
|
return
|
|
index += 1
|
|
|
|
|
|
def _reset_drag_state(self):
|
|
self.is_dragging = False
|
|
self.dragged_files.clear()
|
|
self.show_drag_overlay = False
|
|
self.resource_manager.clear_drag()
|
|
self._drag_scene_tree_hover_node = None
|
|
|
|
|
|
def _draw_drag_overlay(self):
|
|
"""已弃用:保留兼容接口,不再绘制遮罩。"""
|
|
return
|
|
|
|
|
|
def _draw_drag_status(self):
|
|
"""已弃用:保留兼容接口,不再绘制状态窗。"""
|
|
return
|
|
|
|
|
|
def _draw_context_menus(self):
|
|
"""绘制右键菜单"""
|
|
# 节点右键菜单
|
|
if hasattr(self, '_context_menu_node') and self._context_menu_node:
|
|
imgui.open_popup("节点右键菜单")
|
|
self._context_menu_node = None
|
|
|
|
if imgui.begin_popup("节点右键菜单"):
|
|
if imgui.menu_item("删除节点", "", False, True)[1]:
|
|
if hasattr(self, '_context_menu_target') and self._context_menu_target:
|
|
self._delete_node(self._context_menu_target)
|
|
imgui.close_current_popup()
|
|
|
|
if imgui.menu_item("重命名", "", False, True)[1]:
|
|
self._renaming_node = True
|
|
imgui.close_current_popup()
|
|
|
|
imgui.separator()
|
|
|
|
if imgui.menu_item("复制", "", False, True)[1]:
|
|
if hasattr(self, '_context_menu_target') and self._context_menu_target:
|
|
self._copy_node(self._context_menu_target)
|
|
imgui.close_current_popup()
|
|
|
|
if imgui.menu_item("聚焦", "", False, True)[1]:
|
|
if hasattr(self, '_context_menu_target') and self._context_menu_target:
|
|
if hasattr(self, 'selection') and self.selection:
|
|
self.selection.updateSelection(self._context_menu_target)
|
|
self.selection.focusCameraOnSelectedNodeAdvanced()
|
|
imgui.close_current_popup()
|
|
|
|
imgui.end_popup()
|
|
|
|
# 重命名对话框
|
|
if hasattr(self, '_renaming_node') and self._renaming_node:
|
|
imgui.open_popup("重命名节点")
|
|
if not hasattr(self, '_rename_buffer'):
|
|
self._rename_buffer = ""
|
|
if hasattr(self, '_context_menu_target') and self._context_menu_target:
|
|
self._rename_buffer = self._context_menu_target.getName() or ""
|
|
|
|
if imgui.begin_popup("重命名节点"):
|
|
changed, new_name = imgui.input_text("新名称", self._rename_buffer, 256)
|
|
if changed:
|
|
self._rename_buffer = new_name
|
|
|
|
if imgui.button("确定"):
|
|
if hasattr(self, '_context_menu_target') and self._context_menu_target:
|
|
self._context_menu_target.setName(self._rename_buffer)
|
|
self._renaming_node = False
|
|
imgui.close_current_popup()
|
|
|
|
imgui.same_line()
|
|
if imgui.button("取消"):
|
|
self._renaming_node = False
|
|
imgui.close_current_popup()
|
|
|
|
imgui.end_popup()
|
|
|
|
|
|
def _delete_node_simple(self, node):
|
|
"""删除节点 - 简化版本"""
|
|
if not node or node.isEmpty():
|
|
return
|
|
|
|
# 从场景管理器中删除
|
|
if hasattr(self, 'scene_manager') and self.scene_manager:
|
|
if hasattr(self.scene_manager, 'models') and node in self.scene_manager.models:
|
|
self.scene_manager.models.remove(node)
|
|
|
|
# 从GUI管理器中删除
|
|
if hasattr(self, 'gui_manager') and self.gui_manager:
|
|
gui_element = None
|
|
if hasattr(node, 'getPythonTag'):
|
|
gui_element = node.getPythonTag('gui_element')
|
|
if gui_element and hasattr(self.gui_manager, 'gui_elements'):
|
|
if gui_element in self.gui_manager.gui_elements:
|
|
self.gui_manager.gui_elements.remove(gui_element)
|
|
|
|
# 使用主删除方法
|
|
self._delete_node(node)
|
|
|
|
# 获取节点名称(在删除之前)
|
|
node_name = node.getName() if not node.isEmpty() else '未命名节点'
|
|
|
|
# 删除节点本身
|
|
node.removeNode()
|
|
|
|
# 清除选择
|
|
if hasattr(self, 'selection') and self.selection:
|
|
if self.selection.selectedNode == node:
|
|
self.selection.clearSelection()
|
|
|
|
# 添加成功消息
|
|
self.add_success_message(f"已删除节点: {node_name}")
|
|
|
|
|
|
def _copy_node(self, node):
|
|
"""复制节点"""
|
|
if not node or node.isEmpty():
|
|
return
|
|
|
|
# 这里可以实现节点复制逻辑
|
|
# 暂时只显示消息
|
|
self.add_info_message(f"复制功能暂未实现: {node.getName() or '未命名节点'}")
|
|
|
|
# ==================== 创建功能实现 ====================
|