refactor: extract model drag-and-drop service
This commit is contained in:
parent
0fa75b7937
commit
eeb5dd193b
576
core/model_drag_drop.py
Normal file
576
core/model_drag_drop.py
Normal file
@ -0,0 +1,576 @@
|
||||
"""
|
||||
模型拖拽服务
|
||||
|
||||
集中管理:
|
||||
1. 系统外部文件拖入窗口(Windows WM_DROPFILES)
|
||||
2. 资源管理器内部拖拽(移动资源、拖入场景、拖入场景树)
|
||||
3. 模型导入后的命名冲突处理
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import ctypes
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import List, Optional, Tuple
|
||||
|
||||
from imgui_bundle import imgui
|
||||
|
||||
try:
|
||||
from ctypes import wintypes
|
||||
except Exception: # pragma: no cover - non-Windows fallback
|
||||
wintypes = None
|
||||
|
||||
|
||||
class NativeFileDropMonitor:
|
||||
"""Windows 文件拖拽桥接,收集外部文件拖入事件。"""
|
||||
|
||||
WM_DROPFILES = 0x0233
|
||||
WM_NCDESTROY = 0x0082
|
||||
GWL_WNDPROC = -4
|
||||
|
||||
def __init__(self, world):
|
||||
self.world = world
|
||||
self.enabled = False
|
||||
self._hwnd = None
|
||||
self._window_proc = None
|
||||
self._old_wndproc = None
|
||||
self._set_wndproc = None
|
||||
self._user32 = None
|
||||
self._shell32 = None
|
||||
self._pending_events = []
|
||||
self._hook_wndproc_ptr = None
|
||||
|
||||
def start(self):
|
||||
"""启动系统文件拖拽捕获。"""
|
||||
if self.enabled:
|
||||
return
|
||||
|
||||
if os.environ.get("EG_DISABLE_NATIVE_DROP") == "1":
|
||||
print("DragDropMonitor: 已通过 EG_DISABLE_NATIVE_DROP 禁用原生拖拽")
|
||||
return
|
||||
|
||||
if os.name != "nt" or wintypes is None:
|
||||
print("DragDropMonitor: 非 Windows 平台,未启用系统拖拽桥接")
|
||||
return
|
||||
|
||||
try:
|
||||
win_handle = self.world.win.getWindowHandle() if self.world and self.world.win else None
|
||||
if not win_handle:
|
||||
return
|
||||
|
||||
self._hwnd = int(win_handle.getIntHandle())
|
||||
if not self._hwnd:
|
||||
return
|
||||
|
||||
self._user32 = ctypes.windll.user32
|
||||
self._shell32 = ctypes.windll.shell32
|
||||
|
||||
long_ptr_t = ctypes.c_ssize_t
|
||||
wparam_t = wintypes.WPARAM
|
||||
lparam_t = wintypes.LPARAM
|
||||
wndproc_t = ctypes.WINFUNCTYPE(
|
||||
long_ptr_t, wintypes.HWND, wintypes.UINT, wparam_t, lparam_t
|
||||
)
|
||||
|
||||
if ctypes.sizeof(ctypes.c_void_p) == 8:
|
||||
self._set_wndproc = self._user32.SetWindowLongPtrW
|
||||
get_wndproc = self._user32.GetWindowLongPtrW
|
||||
else:
|
||||
self._set_wndproc = self._user32.SetWindowLongW
|
||||
get_wndproc = self._user32.GetWindowLongW
|
||||
|
||||
self._set_wndproc.argtypes = [wintypes.HWND, ctypes.c_int, long_ptr_t]
|
||||
self._set_wndproc.restype = long_ptr_t
|
||||
get_wndproc.argtypes = [wintypes.HWND, ctypes.c_int]
|
||||
get_wndproc.restype = long_ptr_t
|
||||
|
||||
self._user32.CallWindowProcW.argtypes = [
|
||||
ctypes.c_void_p, wintypes.HWND, wintypes.UINT, wparam_t, lparam_t
|
||||
]
|
||||
self._user32.CallWindowProcW.restype = long_ptr_t
|
||||
self._user32.IsWindow.argtypes = [wintypes.HWND]
|
||||
self._user32.IsWindow.restype = wintypes.BOOL
|
||||
|
||||
self._shell32.DragQueryFileW.argtypes = [wintypes.HANDLE, wintypes.UINT, wintypes.LPWSTR, wintypes.UINT]
|
||||
self._shell32.DragQueryFileW.restype = wintypes.UINT
|
||||
self._shell32.DragQueryPoint.argtypes = [wintypes.HANDLE, ctypes.POINTER(wintypes.POINT)]
|
||||
self._shell32.DragQueryPoint.restype = wintypes.BOOL
|
||||
self._shell32.DragFinish.argtypes = [wintypes.HANDLE]
|
||||
self._shell32.DragFinish.restype = None
|
||||
self._shell32.DragAcceptFiles.argtypes = [wintypes.HWND, wintypes.BOOL]
|
||||
self._shell32.DragAcceptFiles.restype = None
|
||||
|
||||
self._old_wndproc = get_wndproc(wintypes.HWND(self._hwnd), self.GWL_WNDPROC)
|
||||
if not self._old_wndproc:
|
||||
raise RuntimeError("GetWindowLongPtrW returned null WndProc")
|
||||
|
||||
def _window_proc(hwnd, msg, wparam, lparam):
|
||||
try:
|
||||
if msg == self.WM_DROPFILES:
|
||||
files, drop_pos = self._extract_drop_files(wparam)
|
||||
if files:
|
||||
self._pending_events.append((files, drop_pos))
|
||||
return 0
|
||||
if msg == self.WM_NCDESTROY:
|
||||
result = self._user32.CallWindowProcW(
|
||||
ctypes.c_void_p(self._old_wndproc), hwnd, msg, wparam, lparam
|
||||
)
|
||||
self.enabled = False
|
||||
self._hwnd = None
|
||||
self._old_wndproc = None
|
||||
self._hook_wndproc_ptr = None
|
||||
return result
|
||||
except Exception as cb_err:
|
||||
print(f"[DragDropMonitor] 回调异常: {cb_err}")
|
||||
|
||||
if not self._old_wndproc:
|
||||
return 0
|
||||
return self._user32.CallWindowProcW(
|
||||
ctypes.c_void_p(self._old_wndproc), hwnd, msg, wparam, lparam
|
||||
)
|
||||
|
||||
self._window_proc = wndproc_t(_window_proc)
|
||||
self._hook_wndproc_ptr = ctypes.cast(self._window_proc, ctypes.c_void_p).value
|
||||
if not self._hook_wndproc_ptr:
|
||||
raise RuntimeError("无法获取 WndProc 回调指针")
|
||||
|
||||
self._set_wndproc(
|
||||
wintypes.HWND(self._hwnd),
|
||||
self.GWL_WNDPROC,
|
||||
self._hook_wndproc_ptr,
|
||||
)
|
||||
installed_wndproc = get_wndproc(wintypes.HWND(self._hwnd), self.GWL_WNDPROC)
|
||||
if int(installed_wndproc) != int(self._hook_wndproc_ptr):
|
||||
raise RuntimeError("WndProc 安装校验失败")
|
||||
|
||||
self._shell32.DragAcceptFiles(wintypes.HWND(self._hwnd), True)
|
||||
self.enabled = True
|
||||
print("拖拽监控已启用")
|
||||
except Exception as e:
|
||||
print(f"拖拽监控启动失败: {e}")
|
||||
self.enabled = False
|
||||
|
||||
def stop(self):
|
||||
"""恢复窗口过程并停止拖拽捕获。"""
|
||||
if not self.enabled or os.name != "nt" or wintypes is None:
|
||||
return
|
||||
|
||||
try:
|
||||
if self._shell32 and self._hwnd:
|
||||
self._shell32.DragAcceptFiles(wintypes.HWND(self._hwnd), False)
|
||||
if (
|
||||
self._set_wndproc
|
||||
and self._hwnd
|
||||
and self._old_wndproc
|
||||
and self._user32
|
||||
and self._user32.IsWindow(wintypes.HWND(self._hwnd))
|
||||
):
|
||||
self._set_wndproc(
|
||||
wintypes.HWND(self._hwnd),
|
||||
self.GWL_WNDPROC,
|
||||
self._old_wndproc,
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
finally:
|
||||
self.enabled = False
|
||||
self._window_proc = None
|
||||
self._hook_wndproc_ptr = None
|
||||
|
||||
def _extract_drop_files(self, hdrop):
|
||||
"""从 Win32 HDROP 中提取文件路径和投放点。"""
|
||||
files = []
|
||||
drop_pos = None
|
||||
if not self._shell32 or wintypes is None:
|
||||
return files, drop_pos
|
||||
|
||||
hdrop_handle = wintypes.HANDLE(int(hdrop))
|
||||
if not hdrop_handle:
|
||||
return files, drop_pos
|
||||
|
||||
try:
|
||||
file_count = self._shell32.DragQueryFileW(hdrop_handle, 0xFFFFFFFF, None, 0)
|
||||
for i in range(file_count):
|
||||
path_len = self._shell32.DragQueryFileW(hdrop_handle, i, None, 0)
|
||||
buffer = ctypes.create_unicode_buffer(path_len + 1)
|
||||
self._shell32.DragQueryFileW(hdrop_handle, i, buffer, path_len + 1)
|
||||
files.append(buffer.value)
|
||||
|
||||
point = wintypes.POINT()
|
||||
if self._shell32.DragQueryPoint(hdrop_handle, ctypes.byref(point)):
|
||||
drop_pos = (int(point.x), int(point.y))
|
||||
except Exception as e:
|
||||
print(f"[DragDropMonitor] 解析拖拽数据失败: {e}")
|
||||
finally:
|
||||
try:
|
||||
self._shell32.DragFinish(hdrop_handle)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return files, drop_pos
|
||||
|
||||
def add_file_from_external(self, file_path):
|
||||
"""手动压入外部文件(保留给其他集成调用)。"""
|
||||
if file_path:
|
||||
self._pending_events.append(([str(file_path)], None))
|
||||
|
||||
def pop_pending_events(self):
|
||||
"""弹出并清空待处理的外部拖拽事件。"""
|
||||
events = list(self._pending_events)
|
||||
self._pending_events.clear()
|
||||
return events
|
||||
|
||||
|
||||
class ModelDragDropService:
|
||||
"""模型拖拽业务入口。"""
|
||||
|
||||
SUPPORTED_MODEL_EXTS = {".gltf", ".glb", ".fbx", ".bam", ".egg", ".obj"}
|
||||
|
||||
def __init__(self, app):
|
||||
self.app = app
|
||||
|
||||
def setup_drag_drop_support(self):
|
||||
"""初始化系统外部拖拽支持。"""
|
||||
try:
|
||||
monitor = NativeFileDropMonitor(self.app)
|
||||
monitor.start()
|
||||
self.app.drag_drop_monitor = monitor
|
||||
except Exception as e:
|
||||
print(f"拖拽监控启动失败: {e}")
|
||||
|
||||
def is_point_in_resource_manager(self, drop_pos):
|
||||
"""判断投放坐标是否命中资源管理器窗口。"""
|
||||
if not drop_pos:
|
||||
return False
|
||||
rect = getattr(self.app, "_resource_manager_window_rect", None)
|
||||
if not rect:
|
||||
return False
|
||||
|
||||
x, y = drop_pos
|
||||
rx, ry, rw, rh = rect
|
||||
return (rx <= x <= rx + rw) and (ry <= y <= ry + rh)
|
||||
|
||||
def resolve_resource_drop_target_dir(self, drop_pos):
|
||||
"""根据投放坐标解析资源管理器中的目标目录。"""
|
||||
rm = getattr(self.app, "resource_manager", None)
|
||||
default_dir = rm.current_path if rm else None
|
||||
targets = getattr(self.app, "_resource_drop_targets", None) or []
|
||||
if not drop_pos or not targets:
|
||||
return default_dir
|
||||
|
||||
x, y = drop_pos
|
||||
hits = []
|
||||
for tx, ty, tw, th, path in targets:
|
||||
if tx <= x <= tx + tw and ty <= y <= ty + th:
|
||||
hits.append((tw * th, path))
|
||||
|
||||
if not hits:
|
||||
return default_dir
|
||||
|
||||
hits.sort(key=lambda item: item[0])
|
||||
target_path = Path(hits[0][1])
|
||||
if target_path.exists() and target_path.is_dir():
|
||||
return target_path
|
||||
return default_dir
|
||||
|
||||
def process_external_drop_events(self):
|
||||
"""处理外部拖入(系统文件拖拽)事件。"""
|
||||
monitor = getattr(self.app, "drag_drop_monitor", None)
|
||||
if not monitor:
|
||||
return
|
||||
|
||||
events = monitor.pop_pending_events()
|
||||
if not events:
|
||||
return
|
||||
|
||||
for file_paths, drop_pos in events:
|
||||
self.handle_external_drop(file_paths, drop_pos)
|
||||
|
||||
def handle_external_drop(self, file_paths, drop_pos=None):
|
||||
"""把外部拖入文件分发到资源管理器或场景。"""
|
||||
if not file_paths:
|
||||
return
|
||||
|
||||
paths = [Path(p) for p in file_paths if p]
|
||||
if not paths:
|
||||
return
|
||||
|
||||
if self.is_point_in_resource_manager(drop_pos):
|
||||
target_dir = self.resolve_resource_drop_target_dir(drop_pos)
|
||||
imported, errors = self.app.resource_manager.import_external_files(paths, target_dir=target_dir)
|
||||
if imported:
|
||||
target_label = self.app.resource_manager.get_relative_path(target_dir) if target_dir else "资源管理器"
|
||||
self.app.add_success_message(f"已导入 {len(imported)} 个外部资源到 {target_label}")
|
||||
if errors:
|
||||
self.app.add_warning_message(f"有 {len(errors)} 个资源导入失败,请查看控制台日志")
|
||||
for err in errors:
|
||||
print(f"[资源导入] {err}")
|
||||
return
|
||||
|
||||
imported_models = []
|
||||
for path in paths:
|
||||
if not path.exists() or not self._is_supported_model(path):
|
||||
continue
|
||||
try:
|
||||
model_node = self.app._import_model_for_runtime(str(path), prefer_scene_manager=True)
|
||||
if not model_node:
|
||||
continue
|
||||
self._postprocess_imported_model(model_node)
|
||||
imported_models.append(model_node)
|
||||
except Exception as e:
|
||||
print(f"[外部拖拽] 导入模型失败 {path}: {e}")
|
||||
|
||||
if imported_models:
|
||||
if getattr(self.app, "selection", None):
|
||||
try:
|
||||
self.app.selection.updateSelection(imported_models[-1])
|
||||
except Exception as e:
|
||||
print(f"[外部拖拽] 更新选择失败: {e}")
|
||||
self.app.add_success_message(f"外部拖拽导入场景成功: {len(imported_models)} 个模型")
|
||||
else:
|
||||
self.app.add_info_message("将文件拖放到“资源管理器”窗口可导入到项目资源")
|
||||
|
||||
def draw_drag_drop_interface(self):
|
||||
"""每帧处理资源管理器内部拖拽的释放事件。"""
|
||||
rm = getattr(self.app, "resource_manager", None)
|
||||
if not rm:
|
||||
return
|
||||
|
||||
if rm.is_dragging():
|
||||
self.app.is_dragging = True
|
||||
self.app.dragged_files = rm.get_dragged_files()
|
||||
|
||||
if self.app.is_dragging and self.app.dragged_files and imgui.is_mouse_released(0):
|
||||
mouse_pos = imgui.get_mouse_pos()
|
||||
self.handle_internal_drag_drop_completion((float(mouse_pos.x), float(mouse_pos.y)))
|
||||
|
||||
def handle_internal_drag_drop_completion(self, mouse_pos: Optional[Tuple[float, float]] = None):
|
||||
"""处理资源管理器内部拖拽落点。"""
|
||||
rm = self.app.resource_manager
|
||||
dragged_files = list(self.app.dragged_files) if self.app.dragged_files else rm.get_dragged_files()
|
||||
if not dragged_files:
|
||||
self._reset_drag_state()
|
||||
return
|
||||
|
||||
drop_kind, target_dir, target_parent = self._resolve_drop_target(mouse_pos)
|
||||
|
||||
if drop_kind == "resource_manager":
|
||||
moved, errors = rm.move_files_to_directory(dragged_files, target_dir)
|
||||
if moved:
|
||||
label = rm.get_relative_path(target_dir) if target_dir else "资源管理器"
|
||||
self.app.add_success_message(f"已移动 {len(moved)} 个资源到 {label}")
|
||||
if errors:
|
||||
self.app.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.app._import_model_for_runtime(str(path), prefer_scene_manager=True)
|
||||
if not model_node:
|
||||
continue
|
||||
|
||||
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._postprocess_imported_model(model_node)
|
||||
imported_models.append(model_node)
|
||||
except Exception as e:
|
||||
print(f"[拖拽导入] 导入失败 {path}: {e}")
|
||||
|
||||
if imported_models and getattr(self.app, "selection", None):
|
||||
try:
|
||||
self.app.selection.updateSelection(imported_models[-1])
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if imported_models:
|
||||
self.app.add_success_message(f"拖拽导入成功: {len(imported_models)} 个模型")
|
||||
|
||||
self._reset_drag_state()
|
||||
|
||||
def add_dragged_file(self, file_path):
|
||||
"""添加拖拽的文件。"""
|
||||
if file_path not in self.app.dragged_files:
|
||||
self.app.dragged_files.append(file_path)
|
||||
self.app.is_dragging = True
|
||||
self.app.show_drag_overlay = True
|
||||
print(f"检测到拖拽文件: {file_path}")
|
||||
|
||||
def clear_dragged_files(self):
|
||||
"""清空拖拽文件列表。"""
|
||||
self.app.dragged_files.clear()
|
||||
self.app.is_dragging = False
|
||||
self.app.show_drag_overlay = False
|
||||
|
||||
def process_dragged_files(self):
|
||||
"""处理拖拽的文件。"""
|
||||
if not self.app.dragged_files:
|
||||
return
|
||||
|
||||
imported_count = 0
|
||||
for file_path in self.app.dragged_files:
|
||||
if self.import_model_from_path(file_path):
|
||||
imported_count += 1
|
||||
|
||||
if imported_count > 0:
|
||||
self.app.add_success_message(f"成功导入 {imported_count} 个模型文件")
|
||||
else:
|
||||
self.app.add_error_message("没有成功导入任何文件")
|
||||
|
||||
self.clear_dragged_files()
|
||||
|
||||
def import_model_from_path(self, file_path):
|
||||
"""从路径导入模型的内部方法。"""
|
||||
try:
|
||||
path = Path(file_path)
|
||||
if not path.exists():
|
||||
self.app.add_error_message(f"文件不存在: {path}")
|
||||
return False
|
||||
|
||||
if not self._is_supported_model(path):
|
||||
self.app.add_error_message(f"不支持的文件格式: {path.suffix.lower()}")
|
||||
return False
|
||||
|
||||
model_node = self.app._import_model_for_runtime(str(path), prefer_scene_manager=True)
|
||||
if not model_node:
|
||||
self.app.add_error_message(f"导入模型失败: {path}")
|
||||
return False
|
||||
|
||||
self._postprocess_imported_model(model_node, set_origin=True)
|
||||
if getattr(self.app, "selection", None):
|
||||
self.app.selection.updateSelection(model_node)
|
||||
|
||||
self.app.add_success_message(f"成功导入模型: {path.name}")
|
||||
return True
|
||||
except Exception as e:
|
||||
self.app.add_error_message(f"导入模型时发生错误: {e}")
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def draw_drag_overlay():
|
||||
"""兼容接口:Unity 风格不再绘制拖拽遮罩。"""
|
||||
return
|
||||
|
||||
@staticmethod
|
||||
def draw_drag_status():
|
||||
"""兼容接口:Unity 风格不再绘制拖拽状态窗。"""
|
||||
return
|
||||
|
||||
def _postprocess_imported_model(self, model_node, set_origin=False):
|
||||
if getattr(self.app, "scene_manager", None):
|
||||
try:
|
||||
self.app.scene_manager.processMaterials(model_node)
|
||||
except Exception:
|
||||
pass
|
||||
if set_origin:
|
||||
try:
|
||||
model_node.setPos(0, 0, 0)
|
||||
except Exception:
|
||||
pass
|
||||
self._ensure_unique_name(model_node)
|
||||
|
||||
def _resolve_drop_target(self, mouse_pos: Optional[Tuple[float, float]]):
|
||||
point = mouse_pos
|
||||
if point is None:
|
||||
m = imgui.get_mouse_pos()
|
||||
point = (float(m.x), float(m.y))
|
||||
|
||||
if self._point_in_rect(point, getattr(self.app, "_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.app, "_scene_tree_window_rect", None)):
|
||||
return "scene_tree", None, getattr(self.app, "_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.app, 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 _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
|
||||
|
||||
@classmethod
|
||||
def _is_supported_model(cls, path: Path):
|
||||
return path.suffix.lower() in cls.SUPPORTED_MODEL_EXTS
|
||||
|
||||
@staticmethod
|
||||
def _is_valid_drop_parent(node):
|
||||
if not node:
|
||||
return False
|
||||
try:
|
||||
return not node.isEmpty()
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def _ensure_unique_name(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:
|
||||
candidate = f"{stem}_{index}{suffix}" if suffix else f"{stem}_{index}"
|
||||
if candidate not in existing_names:
|
||||
model_node.setName(candidate)
|
||||
return
|
||||
index += 1
|
||||
|
||||
def _reset_drag_state(self):
|
||||
self.app.is_dragging = False
|
||||
self.app.dragged_files.clear()
|
||||
self.app.show_drag_overlay = False
|
||||
if getattr(self.app, "resource_manager", None):
|
||||
self.app.resource_manager.clear_drag()
|
||||
self.app._drag_scene_tree_hover_node = None
|
||||
357
main.py
357
main.py
@ -14,8 +14,6 @@ from imgui_bundle import imgui, imgui_ctx
|
||||
import sys
|
||||
import os
|
||||
import warnings
|
||||
import ctypes
|
||||
from ctypes import wintypes
|
||||
from pathlib import Path
|
||||
|
||||
# 导入MyWorld类和必要的模块
|
||||
@ -44,209 +42,10 @@ from ui.panels.object_factory import ObjectFactory
|
||||
from ui.panels.animation_tools import AnimationTools
|
||||
from ui.panels.property_helpers import PropertyHelpers
|
||||
from ui.panels.app_actions import AppActions
|
||||
from core.model_drag_drop import ModelDragDropService
|
||||
from ssbo_component.ssbo_editor import SSBOEditor
|
||||
from TransformGizmo.transform_gizmo import TransformGizmo
|
||||
|
||||
# 拖拽监控类
|
||||
class DragDropMonitor:
|
||||
"""Windows 文件拖拽桥接,收集外部文件拖入事件。"""
|
||||
|
||||
WM_DROPFILES = 0x0233
|
||||
WM_NCDESTROY = 0x0082
|
||||
GWL_WNDPROC = -4
|
||||
|
||||
def __init__(self, world):
|
||||
self.world = world
|
||||
self.enabled = False
|
||||
self._hwnd = None
|
||||
self._window_proc = None
|
||||
self._old_wndproc = None
|
||||
self._set_wndproc = None
|
||||
self._user32 = None
|
||||
self._shell32 = None
|
||||
self._pending_events = []
|
||||
self._hook_wndproc_ptr = None
|
||||
|
||||
def start(self):
|
||||
"""启动系统文件拖拽捕获。"""
|
||||
if self.enabled:
|
||||
return
|
||||
|
||||
if os.environ.get("EG_DISABLE_NATIVE_DROP") == "1":
|
||||
print("DragDropMonitor: 已通过 EG_DISABLE_NATIVE_DROP 禁用原生拖拽")
|
||||
return
|
||||
|
||||
if os.name != "nt":
|
||||
print("DragDropMonitor: 非 Windows 平台,未启用系统拖拽桥接")
|
||||
return
|
||||
|
||||
try:
|
||||
win_handle = self.world.win.getWindowHandle() if self.world and self.world.win else None
|
||||
if not win_handle:
|
||||
return
|
||||
|
||||
self._hwnd = int(win_handle.getIntHandle())
|
||||
if not self._hwnd:
|
||||
return
|
||||
|
||||
self._user32 = ctypes.windll.user32
|
||||
self._shell32 = ctypes.windll.shell32
|
||||
|
||||
long_ptr_t = ctypes.c_ssize_t
|
||||
wparam_t = wintypes.WPARAM
|
||||
lparam_t = wintypes.LPARAM
|
||||
wndproc_t = ctypes.WINFUNCTYPE(
|
||||
long_ptr_t, wintypes.HWND, wintypes.UINT, wparam_t, lparam_t
|
||||
)
|
||||
|
||||
if ctypes.sizeof(ctypes.c_void_p) == 8:
|
||||
self._set_wndproc = self._user32.SetWindowLongPtrW
|
||||
get_wndproc = self._user32.GetWindowLongPtrW
|
||||
else:
|
||||
self._set_wndproc = self._user32.SetWindowLongW
|
||||
get_wndproc = self._user32.GetWindowLongW
|
||||
|
||||
self._set_wndproc.argtypes = [wintypes.HWND, ctypes.c_int, long_ptr_t]
|
||||
self._set_wndproc.restype = long_ptr_t
|
||||
get_wndproc.argtypes = [wintypes.HWND, ctypes.c_int]
|
||||
get_wndproc.restype = long_ptr_t
|
||||
|
||||
self._user32.CallWindowProcW.argtypes = [
|
||||
ctypes.c_void_p, wintypes.HWND, wintypes.UINT, wparam_t, lparam_t
|
||||
]
|
||||
self._user32.CallWindowProcW.restype = long_ptr_t
|
||||
self._user32.IsWindow.argtypes = [wintypes.HWND]
|
||||
self._user32.IsWindow.restype = wintypes.BOOL
|
||||
|
||||
self._shell32.DragQueryFileW.argtypes = [wintypes.HANDLE, wintypes.UINT, wintypes.LPWSTR, wintypes.UINT]
|
||||
self._shell32.DragQueryFileW.restype = wintypes.UINT
|
||||
self._shell32.DragQueryPoint.argtypes = [wintypes.HANDLE, ctypes.POINTER(wintypes.POINT)]
|
||||
self._shell32.DragQueryPoint.restype = wintypes.BOOL
|
||||
self._shell32.DragFinish.argtypes = [wintypes.HANDLE]
|
||||
self._shell32.DragFinish.restype = None
|
||||
self._shell32.DragAcceptFiles.argtypes = [wintypes.HWND, wintypes.BOOL]
|
||||
self._shell32.DragAcceptFiles.restype = None
|
||||
|
||||
self._old_wndproc = get_wndproc(wintypes.HWND(self._hwnd), self.GWL_WNDPROC)
|
||||
if not self._old_wndproc:
|
||||
raise RuntimeError("GetWindowLongPtrW returned null WndProc")
|
||||
|
||||
def _window_proc(hwnd, msg, wparam, lparam):
|
||||
try:
|
||||
if msg == self.WM_DROPFILES:
|
||||
files, drop_pos = self._extract_drop_files(wparam)
|
||||
if files:
|
||||
self._pending_events.append((files, drop_pos))
|
||||
return 0
|
||||
if msg == self.WM_NCDESTROY:
|
||||
result = self._user32.CallWindowProcW(
|
||||
ctypes.c_void_p(self._old_wndproc), hwnd, msg, wparam, lparam
|
||||
)
|
||||
self.enabled = False
|
||||
self._hwnd = None
|
||||
self._old_wndproc = None
|
||||
self._hook_wndproc_ptr = None
|
||||
return result
|
||||
except Exception as cb_err:
|
||||
print(f"[DragDropMonitor] 回调异常: {cb_err}")
|
||||
|
||||
if not self._old_wndproc:
|
||||
return 0
|
||||
return self._user32.CallWindowProcW(
|
||||
ctypes.c_void_p(self._old_wndproc), hwnd, msg, wparam, lparam
|
||||
)
|
||||
|
||||
self._window_proc = wndproc_t(_window_proc)
|
||||
self._hook_wndproc_ptr = ctypes.cast(self._window_proc, ctypes.c_void_p).value
|
||||
if not self._hook_wndproc_ptr:
|
||||
raise RuntimeError("无法获取 WndProc 回调指针")
|
||||
|
||||
self._set_wndproc(
|
||||
wintypes.HWND(self._hwnd),
|
||||
self.GWL_WNDPROC,
|
||||
self._hook_wndproc_ptr,
|
||||
)
|
||||
installed_wndproc = get_wndproc(wintypes.HWND(self._hwnd), self.GWL_WNDPROC)
|
||||
if int(installed_wndproc) != int(self._hook_wndproc_ptr):
|
||||
raise RuntimeError("WndProc 安装校验失败")
|
||||
|
||||
self._shell32.DragAcceptFiles(wintypes.HWND(self._hwnd), True)
|
||||
self.enabled = True
|
||||
print("拖拽监控已启用")
|
||||
except Exception as e:
|
||||
print(f"拖拽监控启动失败: {e}")
|
||||
self.enabled = False
|
||||
|
||||
def stop(self):
|
||||
"""恢复窗口过程并停止拖拽捕获。"""
|
||||
if not self.enabled or os.name != "nt":
|
||||
return
|
||||
|
||||
try:
|
||||
if self._shell32 and self._hwnd:
|
||||
self._shell32.DragAcceptFiles(wintypes.HWND(self._hwnd), False)
|
||||
if (
|
||||
self._set_wndproc
|
||||
and self._hwnd
|
||||
and self._old_wndproc
|
||||
and self._user32
|
||||
and self._user32.IsWindow(wintypes.HWND(self._hwnd))
|
||||
):
|
||||
self._set_wndproc(
|
||||
wintypes.HWND(self._hwnd),
|
||||
self.GWL_WNDPROC,
|
||||
self._old_wndproc,
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
finally:
|
||||
self.enabled = False
|
||||
self._window_proc = None
|
||||
self._hook_wndproc_ptr = None
|
||||
|
||||
def _extract_drop_files(self, hdrop):
|
||||
"""从 Win32 HDROP 中提取文件路径和投放点。"""
|
||||
files = []
|
||||
drop_pos = None
|
||||
if not self._shell32:
|
||||
return files, drop_pos
|
||||
|
||||
hdrop_handle = wintypes.HANDLE(int(hdrop))
|
||||
if not hdrop_handle:
|
||||
return files, drop_pos
|
||||
|
||||
try:
|
||||
file_count = self._shell32.DragQueryFileW(hdrop_handle, 0xFFFFFFFF, None, 0)
|
||||
for i in range(file_count):
|
||||
path_len = self._shell32.DragQueryFileW(hdrop_handle, i, None, 0)
|
||||
buffer = ctypes.create_unicode_buffer(path_len + 1)
|
||||
self._shell32.DragQueryFileW(hdrop_handle, i, buffer, path_len + 1)
|
||||
files.append(buffer.value)
|
||||
|
||||
point = wintypes.POINT()
|
||||
if self._shell32.DragQueryPoint(hdrop_handle, ctypes.byref(point)):
|
||||
drop_pos = (int(point.x), int(point.y))
|
||||
except Exception as e:
|
||||
print(f"[DragDropMonitor] 解析拖拽数据失败: {e}")
|
||||
finally:
|
||||
try:
|
||||
self._shell32.DragFinish(hdrop_handle)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return files, drop_pos
|
||||
|
||||
def add_file_from_external(self, file_path):
|
||||
"""手动压入外部文件(保留给其他集成调用)。"""
|
||||
if file_path:
|
||||
self._pending_events.append(([str(file_path)], None))
|
||||
|
||||
def pop_pending_events(self):
|
||||
"""弹出并清空待处理的外部拖拽事件。"""
|
||||
events = list(self._pending_events)
|
||||
self._pending_events.clear()
|
||||
return events
|
||||
|
||||
try:
|
||||
# 尝试导入视频管理器,避免循环导入
|
||||
import importlib.util
|
||||
@ -540,6 +339,7 @@ class MyWorld(CoreWorld):
|
||||
self._console_window_rect = None
|
||||
self._toolbar_window_rect = None
|
||||
self._drag_scene_tree_hover_node = None
|
||||
self.model_drag_drop = ModelDragDropService(self)
|
||||
self.showLUIEditor = not self.use_ssbo_mouse_picking
|
||||
|
||||
# 导入功能状态
|
||||
@ -1509,174 +1309,39 @@ class MyWorld(CoreWorld):
|
||||
|
||||
def setup_drag_drop_support(self):
|
||||
"""初始化拖拽支持。"""
|
||||
try:
|
||||
self.drag_drop_monitor = DragDropMonitor(self)
|
||||
self.drag_drop_monitor.start()
|
||||
print("拖拽监控已启用")
|
||||
except Exception as e:
|
||||
print(f"拖拽监控启动失败: {e}")
|
||||
return self.model_drag_drop.setup_drag_drop_support()
|
||||
|
||||
def _is_point_in_resource_manager(self, drop_pos):
|
||||
"""判断投放坐标是否命中资源管理器窗口。"""
|
||||
if not drop_pos:
|
||||
return False
|
||||
rect = getattr(self, "_resource_manager_window_rect", None)
|
||||
if not rect:
|
||||
return False
|
||||
|
||||
x, y = drop_pos
|
||||
rx, ry, rw, rh = rect
|
||||
return (rx <= x <= rx + rw) and (ry <= y <= ry + rh)
|
||||
return self.model_drag_drop.is_point_in_resource_manager(drop_pos)
|
||||
|
||||
def _resolve_resource_drop_target_dir(self, drop_pos):
|
||||
"""根据投放坐标解析资源管理器中的目标目录。"""
|
||||
rm = getattr(self, "resource_manager", None)
|
||||
default_dir = rm.current_path if rm else None
|
||||
targets = getattr(self, "_resource_drop_targets", None) or []
|
||||
if not drop_pos or not targets:
|
||||
return default_dir
|
||||
|
||||
x, y = drop_pos
|
||||
hits = []
|
||||
for tx, ty, tw, th, path in targets:
|
||||
if tx <= x <= tx + tw and ty <= y <= ty + th:
|
||||
hits.append((tw * th, path))
|
||||
|
||||
if not hits:
|
||||
return default_dir
|
||||
|
||||
# Pick the smallest hit-area target to prefer deeper folder rows.
|
||||
hits.sort(key=lambda item: item[0])
|
||||
target_path = Path(hits[0][1])
|
||||
if target_path.exists() and target_path.is_dir():
|
||||
return target_path
|
||||
return default_dir
|
||||
return self.model_drag_drop.resolve_resource_drop_target_dir(drop_pos)
|
||||
|
||||
def _process_external_drop_events(self):
|
||||
"""处理外部拖入(系统文件拖拽)事件。"""
|
||||
if not self.drag_drop_monitor:
|
||||
return
|
||||
|
||||
events = self.drag_drop_monitor.pop_pending_events()
|
||||
if not events:
|
||||
return
|
||||
|
||||
for file_paths, drop_pos in events:
|
||||
self._handle_external_drop(file_paths, drop_pos)
|
||||
return self.model_drag_drop.process_external_drop_events()
|
||||
|
||||
def _handle_external_drop(self, file_paths, drop_pos=None):
|
||||
"""把外部拖入文件分发到资源管理器或场景。"""
|
||||
if not file_paths:
|
||||
return
|
||||
|
||||
paths = [Path(p) for p in file_paths if p]
|
||||
if not paths:
|
||||
return
|
||||
|
||||
if self._is_point_in_resource_manager(drop_pos):
|
||||
target_dir = self._resolve_resource_drop_target_dir(drop_pos)
|
||||
imported, errors = self.resource_manager.import_external_files(paths, target_dir=target_dir)
|
||||
if imported:
|
||||
target_label = self.resource_manager.get_relative_path(target_dir) if target_dir else "资源管理器"
|
||||
self.add_success_message(f"已导入 {len(imported)} 个外部资源到 {target_label}")
|
||||
if errors:
|
||||
self.add_warning_message(f"有 {len(errors)} 个资源导入失败,请查看控制台日志")
|
||||
for err in errors:
|
||||
print(f"[资源导入] {err}")
|
||||
return
|
||||
|
||||
supported_formats = {'.gltf', '.glb', '.fbx', '.bam', '.egg', '.obj'}
|
||||
imported_count = 0
|
||||
last_imported_model = None
|
||||
for path in paths:
|
||||
if path.exists() and path.suffix.lower() in supported_formats:
|
||||
try:
|
||||
model_node = self._import_model_for_runtime(str(path), prefer_scene_manager=True)
|
||||
if model_node:
|
||||
imported_count += 1
|
||||
last_imported_model = model_node
|
||||
except Exception as e:
|
||||
print(f"[外部拖拽] 导入模型失败 {path}: {e}")
|
||||
|
||||
if imported_count > 0:
|
||||
if self.selection and last_imported_model:
|
||||
try:
|
||||
self.selection.updateSelection(last_imported_model)
|
||||
except Exception as e:
|
||||
print(f"[外部拖拽] 更新选择失败: {e}")
|
||||
self.add_success_message(f"外部拖拽导入场景成功: {imported_count} 个模型")
|
||||
else:
|
||||
self.add_info_message("将文件拖放到“资源管理器”窗口可导入到项目资源")
|
||||
return self.model_drag_drop.handle_external_drop(file_paths, drop_pos)
|
||||
|
||||
def add_dragged_file(self, file_path):
|
||||
"""添加拖拽的文件"""
|
||||
if file_path not in self.dragged_files:
|
||||
self.dragged_files.append(file_path)
|
||||
self.is_dragging = True
|
||||
self.show_drag_overlay = True
|
||||
print(f"检测到拖拽文件: {file_path}")
|
||||
return self.model_drag_drop.add_dragged_file(file_path)
|
||||
|
||||
def clear_dragged_files(self):
|
||||
"""清空拖拽文件列表"""
|
||||
self.dragged_files.clear()
|
||||
self.is_dragging = False
|
||||
self.show_drag_overlay = False
|
||||
return self.model_drag_drop.clear_dragged_files()
|
||||
|
||||
def process_dragged_files(self):
|
||||
"""处理拖拽的文件"""
|
||||
if not self.dragged_files:
|
||||
return
|
||||
|
||||
imported_count = 0
|
||||
for file_path in self.dragged_files:
|
||||
if self._import_model_from_path(file_path):
|
||||
imported_count += 1
|
||||
|
||||
if imported_count > 0:
|
||||
self.add_message("success", f"成功导入 {imported_count} 个模型文件")
|
||||
else:
|
||||
self.add_message("error", "没有成功导入任何文件")
|
||||
|
||||
self.clear_dragged_files()
|
||||
return self.model_drag_drop.process_dragged_files()
|
||||
|
||||
def _import_model_from_path(self, file_path):
|
||||
"""从路径导入模型的内部方法"""
|
||||
try:
|
||||
# 检查文件是否存在
|
||||
if not os.path.exists(file_path):
|
||||
self.add_message("error", f"文件不存在: {file_path}")
|
||||
return False
|
||||
|
||||
# 检查文件格式
|
||||
supported_formats = ['.gltf', '.glb', '.fbx', '.bam', '.egg', '.obj']
|
||||
file_ext = os.path.splitext(file_path)[1].lower()
|
||||
|
||||
if file_ext not in supported_formats:
|
||||
self.add_message("error", f"不支持的文件格式: {file_ext}")
|
||||
return False
|
||||
|
||||
# 导入模型
|
||||
model_node = self._import_model_for_runtime(file_path, prefer_scene_manager=True)
|
||||
|
||||
if model_node:
|
||||
# 应用材质确保颜色正常
|
||||
self.scene_manager.processMaterials(model_node)
|
||||
|
||||
# 设置模型位置
|
||||
model_node.setPos(0, 0, 0)
|
||||
|
||||
# 更新当前选中模型
|
||||
self.selection.updateSelection(model_node)
|
||||
|
||||
self.add_message("success", f"成功导入模型: {os.path.basename(file_path)}")
|
||||
return True
|
||||
else:
|
||||
self.add_message("error", f"导入模型失败: {file_path}")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
self.add_message("error", f"导入模型时发生错误: {str(e)}")
|
||||
return False
|
||||
return self.model_drag_drop.import_model_from_path(file_path)
|
||||
|
||||
def _draw_drag_drop_interface(self, *args, **kwargs):
|
||||
return self.interaction_panels._draw_drag_drop_interface(*args, **kwargs)
|
||||
|
||||
@ -1,5 +1,3 @@
|
||||
import os
|
||||
from pathlib import Path
|
||||
from imgui_bundle import imgui, imgui_ctx
|
||||
|
||||
|
||||
@ -20,173 +18,21 @@ class InteractionPanels:
|
||||
|
||||
|
||||
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()
|
||||
"""委托到核心拖拽服务。"""
|
||||
return self.model_drag_drop.draw_drag_drop_interface()
|
||||
|
||||
|
||||
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
|
||||
"""委托到核心拖拽服务。"""
|
||||
return self.model_drag_drop.handle_internal_drag_drop_completion()
|
||||
|
||||
|
||||
def _draw_drag_overlay(self):
|
||||
"""已弃用:保留兼容接口,不再绘制遮罩。"""
|
||||
return
|
||||
return self.model_drag_drop.draw_drag_overlay()
|
||||
|
||||
|
||||
def _draw_drag_status(self):
|
||||
"""已弃用:保留兼容接口,不再绘制状态窗。"""
|
||||
return
|
||||
return self.model_drag_drop.draw_drag_status()
|
||||
|
||||
|
||||
def _draw_context_menus(self):
|
||||
|
||||
Loading…
Reference in New Issue
Block a user