模型拖拽
This commit is contained in:
parent
69d83c6ab5
commit
0fa75b7937
1
EG
Submodule
1
EG
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit 69e2bda47e9713705ad5c45a08b6fc643a2b51f6
|
||||||
@ -4,6 +4,7 @@
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import platform
|
import platform
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@ -337,10 +338,137 @@ class ResourceManager:
|
|||||||
return str(path.relative_to(self.project_root))
|
return str(path.relative_to(self.project_root))
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return str(path)
|
return str(path)
|
||||||
|
|
||||||
|
def _normalize_target_dir(self, target_dir: Optional[Path]) -> Path:
|
||||||
|
"""归一化目标目录,不合法时回退到当前目录。"""
|
||||||
|
candidate = Path(target_dir) if target_dir else self.current_path
|
||||||
|
if candidate.exists() and candidate.is_dir():
|
||||||
|
return candidate.resolve()
|
||||||
|
if self.current_path.exists() and self.current_path.is_dir():
|
||||||
|
return self.current_path.resolve()
|
||||||
|
return self.project_root.resolve()
|
||||||
|
|
||||||
|
def _get_unique_destination_path(self, target_dir: Path, source_name: str) -> Path:
|
||||||
|
"""生成不冲突的目标路径。"""
|
||||||
|
source_path = Path(source_name)
|
||||||
|
stem = source_path.stem or source_path.name or "item"
|
||||||
|
suffix = source_path.suffix
|
||||||
|
|
||||||
|
candidate = target_dir / source_path.name
|
||||||
|
if not candidate.exists():
|
||||||
|
return candidate
|
||||||
|
|
||||||
|
index = 1
|
||||||
|
while True:
|
||||||
|
if suffix:
|
||||||
|
unique_name = f"{stem}_{index}{suffix}"
|
||||||
|
else:
|
||||||
|
unique_name = f"{stem}_{index}"
|
||||||
|
candidate = target_dir / unique_name
|
||||||
|
if not candidate.exists():
|
||||||
|
return candidate
|
||||||
|
index += 1
|
||||||
|
|
||||||
|
def import_external_files(self, file_paths: List[Path], target_dir: Optional[Path] = None):
|
||||||
|
"""将外部文件导入资源目录(复制),返回(成功列表, 错误列表)。"""
|
||||||
|
destination_root = self._normalize_target_dir(target_dir)
|
||||||
|
destination_root.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
imported = []
|
||||||
|
errors = []
|
||||||
|
|
||||||
|
for path in file_paths:
|
||||||
|
src = Path(path)
|
||||||
|
if not src.exists():
|
||||||
|
errors.append(f"文件不存在: {src}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
dst = self._get_unique_destination_path(destination_root, src.name)
|
||||||
|
try:
|
||||||
|
if src.is_dir():
|
||||||
|
shutil.copytree(src, dst)
|
||||||
|
else:
|
||||||
|
shutil.copy2(src, dst)
|
||||||
|
imported.append(dst)
|
||||||
|
except Exception as e:
|
||||||
|
errors.append(f"导入失败 {src}: {e}")
|
||||||
|
|
||||||
|
if imported:
|
||||||
|
self.force_refresh()
|
||||||
|
|
||||||
|
return imported, errors
|
||||||
|
|
||||||
|
def move_files_to_directory(self, file_paths: List[Path], target_dir: Optional[Path]):
|
||||||
|
"""资源管理器内部移动文件/文件夹,返回(成功列表, 错误列表)。"""
|
||||||
|
destination_root = self._normalize_target_dir(target_dir)
|
||||||
|
destination_root.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
moved = []
|
||||||
|
errors = []
|
||||||
|
moved_map = {}
|
||||||
|
|
||||||
|
destination_root_resolved = destination_root.resolve()
|
||||||
|
|
||||||
|
for path in file_paths:
|
||||||
|
src = Path(path)
|
||||||
|
if not src.exists():
|
||||||
|
errors.append(f"源文件不存在: {src}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
src_resolved = src.resolve()
|
||||||
|
except Exception:
|
||||||
|
src_resolved = src
|
||||||
|
|
||||||
|
if src_resolved == destination_root_resolved:
|
||||||
|
continue
|
||||||
|
if src_resolved.parent == destination_root_resolved:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if src.is_dir():
|
||||||
|
try:
|
||||||
|
if destination_root_resolved.is_relative_to(src_resolved):
|
||||||
|
errors.append(f"不能将目录移动到其子目录中: {src}")
|
||||||
|
continue
|
||||||
|
except Exception:
|
||||||
|
# Python 兼容保护:is_relative_to 不可用时跳过该校验
|
||||||
|
pass
|
||||||
|
|
||||||
|
dst = self._get_unique_destination_path(destination_root, src.name)
|
||||||
|
try:
|
||||||
|
shutil.move(str(src), str(dst))
|
||||||
|
moved.append(dst)
|
||||||
|
moved_map[src_resolved] = dst.resolve()
|
||||||
|
except Exception as e:
|
||||||
|
errors.append(f"移动失败 {src}: {e}")
|
||||||
|
|
||||||
|
if moved:
|
||||||
|
updated_selection = set()
|
||||||
|
for selected in self.selected_files:
|
||||||
|
try:
|
||||||
|
selected_resolved = selected.resolve()
|
||||||
|
except Exception:
|
||||||
|
selected_resolved = selected
|
||||||
|
if selected_resolved in moved_map:
|
||||||
|
updated_selection.add(moved_map[selected_resolved])
|
||||||
|
elif selected.exists():
|
||||||
|
updated_selection.add(selected)
|
||||||
|
self.selected_files = updated_selection
|
||||||
|
if self.focused_file:
|
||||||
|
try:
|
||||||
|
focused_resolved = self.focused_file.resolve()
|
||||||
|
except Exception:
|
||||||
|
focused_resolved = self.focused_file
|
||||||
|
if focused_resolved in moved_map:
|
||||||
|
self.focused_file = moved_map[focused_resolved]
|
||||||
|
|
||||||
|
self.force_refresh()
|
||||||
|
|
||||||
|
return moved, errors
|
||||||
|
|
||||||
def start_drag(self, files: List[Path]):
|
def start_drag(self, files: List[Path]):
|
||||||
"""开始拖拽操作"""
|
"""开始拖拽操作"""
|
||||||
self.dragged_files = files.copy()
|
self.dragged_files = [Path(f) for f in files if Path(f).exists()]
|
||||||
|
|
||||||
def clear_drag(self):
|
def clear_drag(self):
|
||||||
"""清除拖拽状态"""
|
"""清除拖拽状态"""
|
||||||
@ -364,4 +492,4 @@ class ResourceManager:
|
|||||||
for file_path in self.dragged_files:
|
for file_path in self.dragged_files:
|
||||||
if file_path.suffix.lower() in supported_extensions:
|
if file_path.suffix.lower() in supported_extensions:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|||||||
30
imgui.ini
30
imgui.ini
@ -24,26 +24,26 @@ Size=832,45
|
|||||||
Collapsed=0
|
Collapsed=0
|
||||||
|
|
||||||
[Window][工具栏]
|
[Window][工具栏]
|
||||||
Pos=273,20
|
Pos=327,20
|
||||||
Size=1276,32
|
Size=1862,32
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x0000000D,0
|
DockId=0x0000000D,0
|
||||||
|
|
||||||
[Window][场景树]
|
[Window][场景树]
|
||||||
Pos=0,20
|
Pos=0,20
|
||||||
Size=271,634
|
Size=325,854
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000007,0
|
DockId=0x00000007,0
|
||||||
|
|
||||||
[Window][属性面板]
|
[Window][属性面板]
|
||||||
Pos=1551,20
|
Pos=2191,20
|
||||||
Size=369,989
|
Size=369,1331
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000003,0
|
DockId=0x00000003,0
|
||||||
|
|
||||||
[Window][控制台]
|
[Window][控制台]
|
||||||
Pos=0,656
|
Pos=0,876
|
||||||
Size=271,353
|
Size=325,475
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000008,0
|
DockId=0x00000008,0
|
||||||
|
|
||||||
@ -60,7 +60,7 @@ Collapsed=0
|
|||||||
|
|
||||||
[Window][WindowOverViewport_11111111]
|
[Window][WindowOverViewport_11111111]
|
||||||
Pos=0,20
|
Pos=0,20
|
||||||
Size=1920,989
|
Size=2560,1331
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
|
|
||||||
[Window][测试窗口1]
|
[Window][测试窗口1]
|
||||||
@ -99,8 +99,8 @@ Size=600,500
|
|||||||
Collapsed=0
|
Collapsed=0
|
||||||
|
|
||||||
[Window][资源管理器]
|
[Window][资源管理器]
|
||||||
Pos=273,817
|
Pos=327,1013
|
||||||
Size=1276,192
|
Size=1862,338
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000006,0
|
DockId=0x00000006,0
|
||||||
|
|
||||||
@ -201,16 +201,16 @@ Size=600,400
|
|||||||
Collapsed=0
|
Collapsed=0
|
||||||
|
|
||||||
[Docking][Data]
|
[Docking][Data]
|
||||||
DockSpace ID=0x08BD597D Window=0x1BBC0F80 Pos=0,20 Size=1920,989 Split=X
|
DockSpace ID=0x08BD597D Window=0x1BBC0F80 Pos=0,20 Size=2560,1331 Split=X
|
||||||
DockNode ID=0x00000001 Parent=0x08BD597D SizeRef=1549,989 Split=X
|
DockNode ID=0x00000001 Parent=0x08BD597D SizeRef=1549,989 Split=X
|
||||||
DockNode ID=0x00000009 Parent=0x00000001 SizeRef=271,989 Split=Y Selected=0xE0015051
|
DockNode ID=0x00000009 Parent=0x00000001 SizeRef=325,989 Split=Y Selected=0xE0015051
|
||||||
DockNode ID=0x00000007 Parent=0x00000009 SizeRef=271,634 Selected=0xE0015051
|
DockNode ID=0x00000007 Parent=0x00000009 SizeRef=271,634 Selected=0xE0015051
|
||||||
DockNode ID=0x00000008 Parent=0x00000009 SizeRef=271,353 Selected=0x5428E753
|
DockNode ID=0x00000008 Parent=0x00000009 SizeRef=271,353 Selected=0x5428E753
|
||||||
DockNode ID=0x0000000A Parent=0x00000001 SizeRef=1276,989 Split=Y
|
DockNode ID=0x0000000A Parent=0x00000001 SizeRef=1862,989 Split=Y
|
||||||
DockNode ID=0x0000000D Parent=0x0000000A SizeRef=1318,32 HiddenTabBar=1 Selected=0x43A39006
|
DockNode ID=0x0000000D Parent=0x0000000A SizeRef=1318,32 HiddenTabBar=1 Selected=0x43A39006
|
||||||
DockNode ID=0x0000000E Parent=0x0000000A SizeRef=1318,955 Split=Y
|
DockNode ID=0x0000000E Parent=0x0000000A SizeRef=1318,955 Split=Y
|
||||||
DockNode ID=0x00000005 Parent=0x0000000E SizeRef=1341,761 CentralNode=1
|
DockNode ID=0x00000005 Parent=0x0000000E SizeRef=1341,957 CentralNode=1
|
||||||
DockNode ID=0x00000006 Parent=0x0000000E SizeRef=1341,192 Selected=0x3A2E05C3
|
DockNode ID=0x00000006 Parent=0x0000000E SizeRef=1341,338 Selected=0x3A2E05C3
|
||||||
DockNode ID=0x00000002 Parent=0x08BD597D SizeRef=369,989 Split=Y Selected=0x3188AB8D
|
DockNode ID=0x00000002 Parent=0x08BD597D SizeRef=369,989 Split=Y Selected=0x3188AB8D
|
||||||
DockNode ID=0x00000003 Parent=0x00000002 SizeRef=351,390 Selected=0x5DB6FF37
|
DockNode ID=0x00000003 Parent=0x00000002 SizeRef=351,390 Selected=0x5DB6FF37
|
||||||
DockNode ID=0x00000004 Parent=0x00000002 SizeRef=351,597 Selected=0x1EB923B7
|
DockNode ID=0x00000004 Parent=0x00000002 SizeRef=351,597 Selected=0x1EB923B7
|
||||||
|
|||||||
372
main.py
372
main.py
@ -14,8 +14,8 @@ from imgui_bundle import imgui, imgui_ctx
|
|||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
import warnings
|
import warnings
|
||||||
import threading
|
import ctypes
|
||||||
import time
|
from ctypes import wintypes
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
# 导入MyWorld类和必要的模块
|
# 导入MyWorld类和必要的模块
|
||||||
@ -49,54 +49,203 @@ from TransformGizmo.transform_gizmo import TransformGizmo
|
|||||||
|
|
||||||
# 拖拽监控类
|
# 拖拽监控类
|
||||||
class DragDropMonitor:
|
class DragDropMonitor:
|
||||||
"""拖拽文件监控器"""
|
"""Windows 文件拖拽桥接,收集外部文件拖入事件。"""
|
||||||
|
|
||||||
|
WM_DROPFILES = 0x0233
|
||||||
|
WM_NCDESTROY = 0x0082
|
||||||
|
GWL_WNDPROC = -4
|
||||||
|
|
||||||
def __init__(self, world):
|
def __init__(self, world):
|
||||||
self.world = world
|
self.world = world
|
||||||
self.running = False
|
self.enabled = False
|
||||||
self.thread = None
|
self._hwnd = None
|
||||||
|
self._window_proc = None
|
||||||
# 支持的文件格式
|
self._old_wndproc = None
|
||||||
self.supported_formats = ['.gltf', '.glb', '.fbx', '.bam', '.egg', '.obj']
|
self._set_wndproc = None
|
||||||
|
self._user32 = None
|
||||||
|
self._shell32 = None
|
||||||
|
self._pending_events = []
|
||||||
|
self._hook_wndproc_ptr = None
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
"""启动监控"""
|
"""启动系统文件拖拽捕获。"""
|
||||||
if not self.running:
|
if self.enabled:
|
||||||
self.running = True
|
return
|
||||||
self.thread = threading.Thread(target=self._monitor_loop, daemon=True)
|
|
||||||
self.thread.start()
|
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):
|
def stop(self):
|
||||||
"""停止监控"""
|
"""恢复窗口过程并停止拖拽捕获。"""
|
||||||
self.running = False
|
if not self.enabled or os.name != "nt":
|
||||||
if self.thread:
|
return
|
||||||
self.thread.join()
|
|
||||||
|
try:
|
||||||
def _monitor_loop(self):
|
if self._shell32 and self._hwnd:
|
||||||
"""监控循环"""
|
self._shell32.DragAcceptFiles(wintypes.HWND(self._hwnd), False)
|
||||||
while self.running:
|
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:
|
try:
|
||||||
# 这里可以实现具体的拖拽检测逻辑
|
self._shell32.DragFinish(hdrop_handle)
|
||||||
# 由于Panda3D限制,我们使用简化的实现
|
except Exception:
|
||||||
time.sleep(0.1)
|
pass
|
||||||
|
|
||||||
# 检查是否有新的拖拽文件
|
return files, drop_pos
|
||||||
self._check_for_dropped_files()
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"拖拽监控错误: {e}")
|
|
||||||
time.sleep(1)
|
|
||||||
|
|
||||||
def _check_for_dropped_files(self):
|
|
||||||
"""检查是否有拖拽的文件"""
|
|
||||||
# 这里可以实现具体的文件检测逻辑
|
|
||||||
# 由于系统限制,我们提供一个占位符实现
|
|
||||||
pass
|
|
||||||
|
|
||||||
def add_file_from_external(self, file_path):
|
def add_file_from_external(self, file_path):
|
||||||
"""从外部添加文件路径(用于系统级拖拽集成)"""
|
"""手动压入外部文件(保留给其他集成调用)。"""
|
||||||
if self.world:
|
if file_path:
|
||||||
self.add_dragged_file(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:
|
try:
|
||||||
# 尝试导入视频管理器,避免循环导入
|
# 尝试导入视频管理器,避免循环导入
|
||||||
@ -383,6 +532,14 @@ class MyWorld(CoreWorld):
|
|||||||
self.is_dragging = False
|
self.is_dragging = False
|
||||||
self.show_drag_overlay = False
|
self.show_drag_overlay = False
|
||||||
self.drag_drop_monitor = None
|
self.drag_drop_monitor = None
|
||||||
|
self._resource_manager_window_rect = None
|
||||||
|
self._resource_drop_targets = []
|
||||||
|
self._scene_tree_window_rect = None
|
||||||
|
self._property_panel_window_rect = None
|
||||||
|
self._script_panel_window_rect = None
|
||||||
|
self._console_window_rect = None
|
||||||
|
self._toolbar_window_rect = None
|
||||||
|
self._drag_scene_tree_hover_node = None
|
||||||
self.showLUIEditor = not self.use_ssbo_mouse_picking
|
self.showLUIEditor = not self.use_ssbo_mouse_picking
|
||||||
|
|
||||||
# 导入功能状态
|
# 导入功能状态
|
||||||
@ -717,12 +874,25 @@ class MyWorld(CoreWorld):
|
|||||||
display_size = imgui.get_io().display_size
|
display_size = imgui.get_io().display_size
|
||||||
window_width = display_size.x
|
window_width = display_size.x
|
||||||
window_height = display_size.y
|
window_height = display_size.y
|
||||||
|
|
||||||
|
# 每帧重置窗口命中区域,由各面板绘制时重新填充
|
||||||
|
self._resource_manager_window_rect = None
|
||||||
|
self._resource_drop_targets = []
|
||||||
|
self._scene_tree_window_rect = None
|
||||||
|
self._property_panel_window_rect = None
|
||||||
|
self._script_panel_window_rect = None
|
||||||
|
self._console_window_rect = None
|
||||||
|
self._toolbar_window_rect = None
|
||||||
|
self._drag_scene_tree_hover_node = None
|
||||||
|
|
||||||
# 绘制菜单栏
|
# 绘制菜单栏
|
||||||
self._draw_menu_bar()
|
self._draw_menu_bar()
|
||||||
|
|
||||||
# 绘制停靠布局
|
# 绘制停靠布局
|
||||||
self._draw_docked_layout(window_width, window_height)
|
self._draw_docked_layout(window_width, window_height)
|
||||||
|
|
||||||
|
# 处理系统层外部拖入
|
||||||
|
self._process_external_drop_events()
|
||||||
|
|
||||||
# 绘制纹理测试窗口
|
# 绘制纹理测试窗口
|
||||||
if self.testTexture:
|
if self.testTexture:
|
||||||
@ -784,12 +954,6 @@ class MyWorld(CoreWorld):
|
|||||||
# 更新变换监控
|
# 更新变换监控
|
||||||
dt = imgui.get_io().delta_time
|
dt = imgui.get_io().delta_time
|
||||||
self.update_transform_monitoring(dt)
|
self.update_transform_monitoring(dt)
|
||||||
|
|
||||||
# 检查鼠标释放事件(用于处理拖拽结束)
|
|
||||||
if imgui.is_mouse_released(0) and self.is_dragging:
|
|
||||||
if not imgui.is_any_window_hovered():
|
|
||||||
# 在3D视图中释放,处理模型导入
|
|
||||||
self._handle_drag_drop_completion()
|
|
||||||
|
|
||||||
def _draw_docked_layout(self, window_width, window_height):
|
def _draw_docked_layout(self, window_width, window_height):
|
||||||
"""绘制可停靠的布局(支持拖拽)"""
|
"""绘制可停靠的布局(支持拖拽)"""
|
||||||
@ -1342,16 +1506,108 @@ class MyWorld(CoreWorld):
|
|||||||
|
|
||||||
def _refresh_heightmap_browser(self, *args, **kwargs):
|
def _refresh_heightmap_browser(self, *args, **kwargs):
|
||||||
return self.dialog_panels._refresh_heightmap_browser(*args, **kwargs)
|
return self.dialog_panels._refresh_heightmap_browser(*args, **kwargs)
|
||||||
|
|
||||||
def setup_drag_drop_support(self):
|
def setup_drag_drop_support(self):
|
||||||
"""设置拖拽支持"""
|
"""初始化拖拽支持。"""
|
||||||
try:
|
try:
|
||||||
# 启动拖拽监控线程
|
|
||||||
self.drag_drop_monitor = DragDropMonitor(self)
|
self.drag_drop_monitor = DragDropMonitor(self)
|
||||||
self.drag_drop_monitor.start()
|
self.drag_drop_monitor.start()
|
||||||
print("✓ 拖拽监控已启动")
|
print("拖拽监控已启用")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"⚠ 拖拽监控启动失败: {e}")
|
print(f"拖拽监控启动失败: {e}")
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
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("将文件拖放到“资源管理器”窗口可导入到项目资源")
|
||||||
|
|
||||||
def add_dragged_file(self, file_path):
|
def add_dragged_file(self, file_path):
|
||||||
"""添加拖拽的文件"""
|
"""添加拖拽的文件"""
|
||||||
if file_path not in self.dragged_files:
|
if file_path not in self.dragged_files:
|
||||||
@ -1400,7 +1656,7 @@ class MyWorld(CoreWorld):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
# 导入模型
|
# 导入模型
|
||||||
model_node = self._import_model_for_runtime(file_path)
|
model_node = self._import_model_for_runtime(file_path, prefer_scene_manager=True)
|
||||||
|
|
||||||
if model_node:
|
if model_node:
|
||||||
# 应用材质确保颜色正常
|
# 应用材质确保颜色正常
|
||||||
@ -1409,8 +1665,8 @@ class MyWorld(CoreWorld):
|
|||||||
# 设置模型位置
|
# 设置模型位置
|
||||||
model_node.setPos(0, 0, 0)
|
model_node.setPos(0, 0, 0)
|
||||||
|
|
||||||
# 添加到选择系统
|
# 更新当前选中模型
|
||||||
self.selection.select_node(model_node)
|
self.selection.updateSelection(model_node)
|
||||||
|
|
||||||
self.add_message("success", f"成功导入模型: {os.path.basename(file_path)}")
|
self.add_message("success", f"成功导入模型: {os.path.basename(file_path)}")
|
||||||
return True
|
return True
|
||||||
|
|||||||
@ -1025,13 +1025,28 @@ class AppActions:
|
|||||||
# ==================== 路径浏览器辅助方法 ====================
|
# ==================== 路径浏览器辅助方法 ====================
|
||||||
|
|
||||||
|
|
||||||
def _import_model_for_runtime(self, file_path):
|
def _import_model_for_runtime(self, file_path, prefer_scene_manager=False):
|
||||||
"""Import model through the active runtime path.
|
"""Import model through the active runtime path.
|
||||||
SSBO mode: load via SSBOEditor only (avoid duplicate SceneManager model).
|
SSBO mode: load via SSBOEditor only (avoid duplicate SceneManager model).
|
||||||
Legacy mode: load via SceneManager.
|
Legacy mode: load via SceneManager.
|
||||||
"""
|
"""
|
||||||
|
if prefer_scene_manager:
|
||||||
|
if hasattr(self, 'scene_manager') and self.scene_manager:
|
||||||
|
return self.scene_manager.importModel(file_path)
|
||||||
|
return None
|
||||||
|
|
||||||
if self.use_ssbo_mouse_picking and getattr(self, 'ssbo_editor', None):
|
if self.use_ssbo_mouse_picking and getattr(self, 'ssbo_editor', None):
|
||||||
try:
|
try:
|
||||||
|
# Clear selection/gizmo first to avoid dangling references to soon-to-be removed nodes.
|
||||||
|
if hasattr(self, 'selection') and self.selection:
|
||||||
|
try:
|
||||||
|
self.selection.clearSelection()
|
||||||
|
except Exception:
|
||||||
|
try:
|
||||||
|
self.selection.updateSelection(None)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
# Remove legacy scene-manager models to avoid duplicate rendering
|
# Remove legacy scene-manager models to avoid duplicate rendering
|
||||||
if hasattr(self, 'scene_manager') and self.scene_manager and hasattr(self.scene_manager, 'models'):
|
if hasattr(self, 'scene_manager') and self.scene_manager and hasattr(self.scene_manager, 'models'):
|
||||||
for m in list(self.scene_manager.models):
|
for m in list(self.scene_manager.models):
|
||||||
|
|||||||
@ -340,6 +340,14 @@ class EditorPanels:
|
|||||||
|
|
||||||
with self.app.style_manager.begin_styled_window("场景树", self.app.showSceneTree, flags):
|
with self.app.style_manager.begin_styled_window("场景树", self.app.showSceneTree, flags):
|
||||||
self.app.showSceneTree = True # 确保窗口保持打开
|
self.app.showSceneTree = True # 确保窗口保持打开
|
||||||
|
window_pos = imgui.get_window_pos()
|
||||||
|
window_size = imgui.get_window_size()
|
||||||
|
self.app._scene_tree_window_rect = (
|
||||||
|
float(window_pos.x),
|
||||||
|
float(window_pos.y),
|
||||||
|
float(window_size.x),
|
||||||
|
float(window_size.y),
|
||||||
|
)
|
||||||
|
|
||||||
imgui.text("场景层级")
|
imgui.text("场景层级")
|
||||||
imgui.separator()
|
imgui.separator()
|
||||||
@ -465,6 +473,9 @@ class EditorPanels:
|
|||||||
# Clear LUI selection when a scene node is selected
|
# Clear LUI selection when a scene node is selected
|
||||||
if hasattr(self.app, 'lui_manager'):
|
if hasattr(self.app, 'lui_manager'):
|
||||||
self.app.lui_manager.selected_index = -1
|
self.app.lui_manager.selected_index = -1
|
||||||
|
|
||||||
|
if self.app.is_dragging and imgui.is_item_hovered():
|
||||||
|
self.app._drag_scene_tree_hover_node = node
|
||||||
|
|
||||||
# 右键菜单
|
# 右键菜单
|
||||||
if imgui.is_item_hovered() and imgui.is_mouse_clicked(1):
|
if imgui.is_item_hovered() and imgui.is_mouse_clicked(1):
|
||||||
@ -579,20 +590,31 @@ class EditorPanels:
|
|||||||
|
|
||||||
def _draw_resource_manager(self):
|
def _draw_resource_manager(self):
|
||||||
"""绘制资源管理器面板"""
|
"""绘制资源管理器面板"""
|
||||||
# 使用面板类型的窗口标志,支持docking
|
|
||||||
flags = self.app.style_manager.get_window_flags("panel")
|
flags = self.app.style_manager.get_window_flags("panel")
|
||||||
|
|
||||||
with self.app.style_manager.begin_styled_window("资源管理器", self.app.showResourceManager, flags):
|
with self.app.style_manager.begin_styled_window("资源管理器", self.app.showResourceManager, flags):
|
||||||
self.app.showResourceManager = True # 确保窗口保持打开
|
self.app.showResourceManager = True
|
||||||
|
|
||||||
# 获取资源管理器实例
|
|
||||||
rm = self.app.resource_manager
|
rm = self.app.resource_manager
|
||||||
|
|
||||||
# 工具栏
|
window_pos = imgui.get_window_pos()
|
||||||
|
window_size = imgui.get_window_size()
|
||||||
|
self.app._resource_manager_window_rect = (
|
||||||
|
float(window_pos.x),
|
||||||
|
float(window_pos.y),
|
||||||
|
float(window_size.x),
|
||||||
|
float(window_size.y),
|
||||||
|
)
|
||||||
|
self.app._resource_drop_targets.append((
|
||||||
|
float(window_pos.x),
|
||||||
|
float(window_pos.y),
|
||||||
|
float(window_size.x),
|
||||||
|
float(window_size.y),
|
||||||
|
str(rm.current_path),
|
||||||
|
))
|
||||||
|
|
||||||
imgui.text("文件浏览器")
|
imgui.text("文件浏览器")
|
||||||
imgui.separator()
|
imgui.separator()
|
||||||
|
|
||||||
# 导航按钮
|
|
||||||
if imgui.button("◀"):
|
if imgui.button("◀"):
|
||||||
rm.navigate_back()
|
rm.navigate_back()
|
||||||
imgui.same_line()
|
imgui.same_line()
|
||||||
@ -628,115 +650,89 @@ class EditorPanels:
|
|||||||
|
|
||||||
# 搜索框
|
# 搜索框
|
||||||
changed, rm.search_filter = imgui.input_text("搜索", rm.search_filter, 256)
|
changed, rm.search_filter = imgui.input_text("搜索", rm.search_filter, 256)
|
||||||
|
|
||||||
imgui.separator()
|
imgui.separator()
|
||||||
|
|
||||||
# 检查自动刷新
|
|
||||||
if rm.refresh_if_needed():
|
if rm.refresh_if_needed():
|
||||||
# 目录内容发生变化,可以在这里添加通知逻辑
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# 获取目录内容
|
|
||||||
dirs, files = rm.get_directory_contents(rm.current_path)
|
dirs, files = rm.get_directory_contents(rm.current_path)
|
||||||
|
|
||||||
# 显示目录
|
|
||||||
for dir_path in dirs:
|
for dir_path in dirs:
|
||||||
if not rm.should_show_file(dir_path):
|
if not rm.should_show_file(dir_path):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# 目录图标和名称
|
|
||||||
icon_name = rm.get_file_icon(dir_path.name, is_folder=True)
|
icon_name = rm.get_file_icon(dir_path.name, is_folder=True)
|
||||||
node_open = False
|
|
||||||
|
|
||||||
# 检查是否被选中
|
|
||||||
is_selected = dir_path in rm.selected_files
|
is_selected = dir_path in rm.selected_files
|
||||||
|
|
||||||
# 使用TreeNode来显示目录
|
|
||||||
if is_selected:
|
if is_selected:
|
||||||
imgui.push_style_color(imgui.Col_.header, (100/255, 150/255, 200/255, 1.0))
|
imgui.push_style_color(imgui.Col_.header, (100/255, 150/255, 200/255, 1.0))
|
||||||
|
|
||||||
# 尝试加载PNG图标
|
|
||||||
icon_texture = None
|
icon_texture = None
|
||||||
try:
|
try:
|
||||||
# 直接使用图标名称,load_icon会自动添加.png
|
|
||||||
icon_texture = self.app.style_manager.load_icon(f"file_types/{icon_name}")
|
icon_texture = self.app.style_manager.load_icon(f"file_types/{icon_name}")
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if icon_texture:
|
if icon_texture:
|
||||||
# 使用PNG图标
|
|
||||||
imgui.image(icon_texture, (16, 16))
|
imgui.image(icon_texture, (16, 16))
|
||||||
imgui.same_line()
|
imgui.same_line()
|
||||||
node_open = imgui.tree_node(f"{dir_path.name}")
|
node_open = imgui.tree_node(f"{dir_path.name}##dir_{dir_path}")
|
||||||
else:
|
else:
|
||||||
# 回退到文本标识符
|
node_open = imgui.tree_node(f"[{icon_name.upper()}]{dir_path.name}##dir_{dir_path}")
|
||||||
node_open = imgui.tree_node(f"[{icon_name.upper()}]{dir_path.name}")
|
|
||||||
|
|
||||||
if is_selected:
|
if is_selected:
|
||||||
imgui.pop_style_color()
|
imgui.pop_style_color()
|
||||||
|
|
||||||
# 处理选择
|
self._append_drop_target_from_last_item(dir_path)
|
||||||
|
self._start_resource_drag_if_needed(rm, dir_path)
|
||||||
|
|
||||||
if imgui.is_item_clicked():
|
if imgui.is_item_clicked():
|
||||||
if imgui.get_io().key_ctrl:
|
if imgui.get_io().key_ctrl:
|
||||||
# 多选模式
|
|
||||||
if is_selected:
|
if is_selected:
|
||||||
rm.selected_files.discard(dir_path)
|
rm.selected_files.discard(dir_path)
|
||||||
else:
|
else:
|
||||||
rm.selected_files.add(dir_path)
|
rm.selected_files.add(dir_path)
|
||||||
else:
|
else:
|
||||||
# 单选模式
|
|
||||||
rm.selected_files.clear()
|
rm.selected_files.clear()
|
||||||
rm.selected_files.add(dir_path)
|
rm.selected_files.add(dir_path)
|
||||||
rm.focused_file = dir_path
|
rm.focused_file = dir_path
|
||||||
|
|
||||||
# 双击导航
|
|
||||||
if imgui.is_item_hovered() and imgui.is_mouse_double_clicked(0):
|
if imgui.is_item_hovered() and imgui.is_mouse_double_clicked(0):
|
||||||
rm.navigate_to(dir_path)
|
rm.navigate_to(dir_path)
|
||||||
|
|
||||||
# 右键菜单
|
|
||||||
if imgui.is_item_hovered() and imgui.is_mouse_clicked(1):
|
if imgui.is_item_hovered() and imgui.is_mouse_clicked(1):
|
||||||
rm.show_context_menu = True
|
rm.show_context_menu = True
|
||||||
rm.context_menu_file = dir_path
|
rm.context_menu_file = dir_path
|
||||||
rm.context_menu_position = (imgui.get_mouse_pos().x, imgui.get_mouse_pos().y)
|
rm.context_menu_position = (imgui.get_mouse_pos().x, imgui.get_mouse_pos().y)
|
||||||
|
imgui.open_popup("resource_context_menu")
|
||||||
# 如果节点展开,显示子内容
|
|
||||||
if node_open:
|
if node_open:
|
||||||
# 获取子目录内容
|
|
||||||
subdirs, subfiles = rm.get_directory_contents(dir_path)
|
subdirs, subfiles = rm.get_directory_contents(dir_path)
|
||||||
|
|
||||||
# 显示子目录
|
|
||||||
for subdir in subdirs:
|
for subdir in subdirs:
|
||||||
if not rm.should_show_file(subdir):
|
if not rm.should_show_file(subdir):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# 初始化变量
|
|
||||||
subicon_name = "folder"
|
|
||||||
sub_is_selected = False
|
|
||||||
|
|
||||||
# 获取子目录图标名称
|
|
||||||
subicon_name = rm.get_file_icon(subdir.name, is_folder=True)
|
subicon_name = rm.get_file_icon(subdir.name, is_folder=True)
|
||||||
sub_is_selected = subdir in rm.selected_files
|
sub_is_selected = subdir in rm.selected_files
|
||||||
|
|
||||||
# 尝试加载PNG图标
|
|
||||||
subicon_texture = None
|
subicon_texture = None
|
||||||
try:
|
try:
|
||||||
subicon_texture = self.app.style_manager.load_icon(f"file_types/{subicon_name}")
|
subicon_texture = self.app.style_manager.load_icon(f"file_types/{subicon_name}")
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if subicon_texture:
|
if subicon_texture:
|
||||||
# 使用PNG图标
|
|
||||||
imgui.image(subicon_texture, (16, 16))
|
imgui.image(subicon_texture, (16, 16))
|
||||||
imgui.same_line()
|
imgui.same_line()
|
||||||
sub_node_open = imgui.tree_node(f" {subdir.name}")
|
sub_node_open = imgui.tree_node(f" {subdir.name}##dir_{subdir}")
|
||||||
else:
|
else:
|
||||||
# 回退到文本标识符
|
sub_node_open = imgui.tree_node(f" [{subicon_name.upper()}]{subdir.name}##dir_{subdir}")
|
||||||
sub_node_open = imgui.tree_node(f" [{subicon_name.upper()}]{subdir.name}")
|
|
||||||
|
self._append_drop_target_from_last_item(subdir)
|
||||||
if sub_is_selected:
|
self._start_resource_drag_if_needed(rm, subdir)
|
||||||
imgui.pop_style_color()
|
|
||||||
|
|
||||||
# 处理子目录的选择
|
|
||||||
if imgui.is_item_clicked():
|
if imgui.is_item_clicked():
|
||||||
if imgui.get_io().key_ctrl:
|
if imgui.get_io().key_ctrl:
|
||||||
if sub_is_selected:
|
if sub_is_selected:
|
||||||
@ -747,46 +743,43 @@ class EditorPanels:
|
|||||||
rm.selected_files.clear()
|
rm.selected_files.clear()
|
||||||
rm.selected_files.add(subdir)
|
rm.selected_files.add(subdir)
|
||||||
rm.focused_file = subdir
|
rm.focused_file = subdir
|
||||||
|
|
||||||
# 双击子目录导航
|
|
||||||
if imgui.is_item_hovered() and imgui.is_mouse_double_clicked(0):
|
if imgui.is_item_hovered() and imgui.is_mouse_double_clicked(0):
|
||||||
rm.navigate_to(subdir)
|
rm.navigate_to(subdir)
|
||||||
|
|
||||||
# 右键菜单
|
|
||||||
if imgui.is_item_hovered() and imgui.is_mouse_clicked(1):
|
if imgui.is_item_hovered() and imgui.is_mouse_clicked(1):
|
||||||
rm.show_context_menu = True
|
rm.show_context_menu = True
|
||||||
rm.context_menu_file = subdir
|
rm.context_menu_file = subdir
|
||||||
rm.context_menu_position = (imgui.get_mouse_pos().x, imgui.get_mouse_pos().y)
|
rm.context_menu_position = (imgui.get_mouse_pos().x, imgui.get_mouse_pos().y)
|
||||||
|
imgui.open_popup("resource_context_menu")
|
||||||
|
|
||||||
if sub_node_open:
|
if sub_node_open:
|
||||||
imgui.tree_pop()
|
imgui.tree_pop()
|
||||||
|
|
||||||
# 显示子文件
|
|
||||||
for subfile in subfiles:
|
for subfile in subfiles:
|
||||||
if not rm.should_show_file(subfile):
|
if not rm.should_show_file(subfile):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
subicon_name = rm.get_file_icon(subfile.name)
|
subicon_name = rm.get_file_icon(subfile.name)
|
||||||
sub_is_selected = subfile in rm.selected_files
|
sub_is_selected = subfile in rm.selected_files
|
||||||
|
|
||||||
# 尝试加载PNG图标
|
|
||||||
subicon_texture = None
|
subicon_texture = None
|
||||||
try:
|
try:
|
||||||
subicon_texture = self.app.style_manager.load_icon(f"file_types/{subicon_name}")
|
subicon_texture = self.app.style_manager.load_icon(f"file_types/{subicon_name}")
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if subicon_texture:
|
if subicon_texture:
|
||||||
# 使用PNG图标
|
|
||||||
imgui.image(subicon_texture, (16, 16))
|
imgui.image(subicon_texture, (16, 16))
|
||||||
imgui.same_line()
|
imgui.same_line()
|
||||||
selected = imgui.selectable(f" {subfile.name}", sub_is_selected)
|
selected = imgui.selectable(f" {subfile.name}##file_{subfile}", sub_is_selected)
|
||||||
else:
|
else:
|
||||||
# 回退到文本标识符
|
selected = imgui.selectable(f" [{subicon_name.upper()}] {subfile.name}##file_{subfile}", sub_is_selected)
|
||||||
selected = imgui.selectable(f" [{subicon_name.upper()}] {subfile.name}", sub_is_selected)
|
|
||||||
|
selected_clicked = selected[0] if isinstance(selected, tuple) else bool(selected)
|
||||||
# 处理子文件的选择
|
self._start_resource_drag_if_needed(rm, subfile)
|
||||||
if selected:
|
|
||||||
|
if selected_clicked:
|
||||||
if imgui.get_io().key_ctrl:
|
if imgui.get_io().key_ctrl:
|
||||||
if sub_is_selected:
|
if sub_is_selected:
|
||||||
rm.selected_files.discard(subfile)
|
rm.selected_files.discard(subfile)
|
||||||
@ -796,99 +789,142 @@ class EditorPanels:
|
|||||||
rm.selected_files.clear()
|
rm.selected_files.clear()
|
||||||
rm.selected_files.add(subfile)
|
rm.selected_files.add(subfile)
|
||||||
rm.focused_file = subfile
|
rm.focused_file = subfile
|
||||||
|
|
||||||
# 双击子文件操作
|
|
||||||
if imgui.is_item_hovered() and imgui.is_mouse_double_clicked(0):
|
if imgui.is_item_hovered() and imgui.is_mouse_double_clicked(0):
|
||||||
if subfile.suffix.lower() in ['.gltf', '.glb', '.fbx', '.bam', '.egg', '.obj']:
|
if self._is_model_file(subfile):
|
||||||
self.app._import_model_for_runtime(str(subfile))
|
self.app._import_model_for_runtime(str(subfile))
|
||||||
self.app.add_info_message(f"正在导入模型: {subfile.name}")
|
self.app.add_info_message(f"正在导入模型: {subfile.name}")
|
||||||
else:
|
else:
|
||||||
rm.open_file(subfile)
|
rm.open_file(subfile)
|
||||||
|
|
||||||
# 右键菜单
|
|
||||||
if imgui.is_item_hovered() and imgui.is_mouse_clicked(1):
|
if imgui.is_item_hovered() and imgui.is_mouse_clicked(1):
|
||||||
rm.show_context_menu = True
|
rm.show_context_menu = True
|
||||||
rm.context_menu_file = subfile
|
rm.context_menu_file = subfile
|
||||||
rm.context_menu_position = (imgui.get_mouse_pos().x, imgui.get_mouse_pos().y)
|
rm.context_menu_position = (imgui.get_mouse_pos().x, imgui.get_mouse_pos().y)
|
||||||
|
imgui.open_popup("resource_context_menu")
|
||||||
# 只有在节点展开时才调用tree_pop
|
|
||||||
if node_open:
|
imgui.tree_pop()
|
||||||
imgui.tree_pop()
|
|
||||||
|
for file_path in files:
|
||||||
|
if not rm.should_show_file(file_path):
|
||||||
|
continue
|
||||||
# 处理拖拽开始
|
|
||||||
if imgui.is_item_active() and imgui.is_item_hovered():
|
icon_name = rm.get_file_icon(file_path.name)
|
||||||
if imgui.is_mouse_dragging(0):
|
is_selected = file_path in rm.selected_files
|
||||||
# 开始拖拽
|
|
||||||
drag_files = list(rm.selected_files) if rm.selected_files else [file_path]
|
icon_texture = None
|
||||||
rm.start_drag(drag_files)
|
try:
|
||||||
self.app.is_dragging = True
|
icon_texture = self.app.style_manager.load_icon(f"file_types/{icon_name}")
|
||||||
self.app.show_drag_overlay = True
|
except:
|
||||||
|
pass
|
||||||
# 双击打开文件
|
|
||||||
|
if icon_texture:
|
||||||
|
imgui.image(icon_texture, (16, 16))
|
||||||
|
imgui.same_line()
|
||||||
|
selected = imgui.selectable(f"{file_path.name}##file_{file_path}", is_selected)
|
||||||
|
else:
|
||||||
|
selected = imgui.selectable(f"[{icon_name.upper()}] {file_path.name}##file_{file_path}", is_selected)
|
||||||
|
|
||||||
|
selected_clicked = selected[0] if isinstance(selected, tuple) else bool(selected)
|
||||||
|
self._start_resource_drag_if_needed(rm, file_path)
|
||||||
|
|
||||||
|
if selected_clicked:
|
||||||
|
if imgui.get_io().key_ctrl:
|
||||||
|
if is_selected:
|
||||||
|
rm.selected_files.discard(file_path)
|
||||||
|
else:
|
||||||
|
rm.selected_files.add(file_path)
|
||||||
|
else:
|
||||||
|
rm.selected_files.clear()
|
||||||
|
rm.selected_files.add(file_path)
|
||||||
|
rm.focused_file = file_path
|
||||||
|
|
||||||
if imgui.is_item_hovered() and imgui.is_mouse_double_clicked(0):
|
if imgui.is_item_hovered() and imgui.is_mouse_double_clicked(0):
|
||||||
# 检查是否是支持的3D模型格式
|
if self._is_model_file(file_path):
|
||||||
if file_path.suffix.lower() in ['.gltf', '.glb', '.fbx', '.bam', '.egg', '.obj']:
|
|
||||||
# 导入3D模型
|
|
||||||
self.app.add_info_message(f"正在导入模型: {file_path.name}")
|
self.app.add_info_message(f"正在导入模型: {file_path.name}")
|
||||||
self.app._import_model_for_runtime(str(file_path))
|
self.app._import_model_for_runtime(str(file_path))
|
||||||
else:
|
else:
|
||||||
# 使用系统默认程序打开
|
|
||||||
rm.open_file(file_path)
|
rm.open_file(file_path)
|
||||||
|
|
||||||
# 右键菜单
|
|
||||||
if imgui.is_item_hovered() and imgui.is_mouse_clicked(1):
|
if imgui.is_item_hovered() and imgui.is_mouse_clicked(1):
|
||||||
rm.show_context_menu = True
|
rm.show_context_menu = True
|
||||||
rm.context_menu_file = file_path
|
rm.context_menu_file = file_path
|
||||||
rm.context_menu_position = (imgui.get_mouse_pos().x, imgui.get_mouse_pos().y)
|
rm.context_menu_position = (imgui.get_mouse_pos().x, imgui.get_mouse_pos().y)
|
||||||
|
imgui.open_popup("resource_context_menu")
|
||||||
# 右键菜单
|
|
||||||
if rm.show_context_menu and rm.context_menu_file:
|
if rm.context_menu_file:
|
||||||
imgui.set_next_window_pos((rm.context_menu_position[0], rm.context_menu_position[1]))
|
imgui.set_next_window_pos((rm.context_menu_position[0], rm.context_menu_position[1]))
|
||||||
with imgui_ctx.begin_popup("context_menu", imgui.WindowFlags_.no_title_bar |
|
|
||||||
imgui.WindowFlags_.no_resize | imgui.WindowFlags_.always_auto_resize) as popup:
|
if imgui.begin_popup("resource_context_menu"):
|
||||||
if popup:
|
if rm.context_menu_file and rm.context_menu_file.is_dir():
|
||||||
if rm.context_menu_file.is_dir():
|
if imgui.menu_item("打开")[1]:
|
||||||
if imgui.menu_item("打开"):
|
rm.navigate_to(rm.context_menu_file)
|
||||||
rm.navigate_to(rm.context_menu_file)
|
imgui.separator()
|
||||||
imgui.separator()
|
if imgui.menu_item("重命名")[1]:
|
||||||
if imgui.menu_item("重命名"):
|
print(f"重命名文件夹: {rm.context_menu_file.name}")
|
||||||
print(f"重命名文件夹: {rm.context_menu_file.name}")
|
if imgui.menu_item("删除")[1]:
|
||||||
if imgui.menu_item("删除"):
|
print(f"删除文件夹: {rm.context_menu_file.name}")
|
||||||
print(f"删除文件夹: {rm.context_menu_file.name}")
|
elif rm.context_menu_file:
|
||||||
|
if imgui.menu_item("打开")[1]:
|
||||||
|
rm.open_file(rm.context_menu_file)
|
||||||
|
imgui.separator()
|
||||||
|
if imgui.menu_item("导入到场景")[1]:
|
||||||
|
if self._is_model_file(rm.context_menu_file):
|
||||||
|
self.app.add_info_message(f"正在导入模型: {rm.context_menu_file.name}")
|
||||||
|
self.app._import_model_for_runtime(str(rm.context_menu_file))
|
||||||
|
if imgui.menu_item("重命名")[1]:
|
||||||
|
print(f"重命名文件: {rm.context_menu_file.name}")
|
||||||
|
if imgui.menu_item("删除")[1]:
|
||||||
|
print(f"删除文件: {rm.context_menu_file.name}")
|
||||||
|
|
||||||
|
if rm.context_menu_file:
|
||||||
|
imgui.separator()
|
||||||
|
if imgui.menu_item("复制路径")[1]:
|
||||||
|
imgui.set_clipboard_text(str(rm.context_menu_file))
|
||||||
|
self.app.add_info_message("路径已复制到剪贴板")
|
||||||
|
if imgui.menu_item("在文件管理器中显示")[1]:
|
||||||
|
import platform
|
||||||
|
import subprocess
|
||||||
|
if platform.system() == "Windows":
|
||||||
|
subprocess.run(["explorer", "/select,", str(rm.context_menu_file)])
|
||||||
|
elif platform.system() == "Darwin":
|
||||||
|
subprocess.run(["open", "-R", str(rm.context_menu_file)])
|
||||||
else:
|
else:
|
||||||
if imgui.menu_item("打开"):
|
subprocess.run(["xdg-open", str(rm.context_menu_file.parent)])
|
||||||
rm.open_file(rm.context_menu_file)
|
|
||||||
imgui.separator()
|
if imgui.is_mouse_clicked(0) and not imgui.is_window_hovered():
|
||||||
if imgui.menu_item("导入到场景"):
|
rm.context_menu_file = None
|
||||||
if rm.context_menu_file.suffix.lower() in ['.gltf', '.glb', '.fbx', '.bam', '.egg', '.obj']:
|
rm.show_context_menu = False
|
||||||
self.app.add_info_message(f"正在导入模型: {rm.context_menu_file.name}")
|
imgui.close_current_popup()
|
||||||
self.app._import_model_for_runtime(str(rm.context_menu_file))
|
|
||||||
if imgui.menu_item("重命名"):
|
imgui.end_popup()
|
||||||
print(f"重命名文件: {rm.context_menu_file.name}")
|
|
||||||
if imgui.menu_item("删除"):
|
@staticmethod
|
||||||
print(f"删除文件: {rm.context_menu_file.name}")
|
def _is_model_file(path: Path) -> bool:
|
||||||
|
return path.suffix.lower() in ['.gltf', '.glb', '.fbx', '.bam', '.egg', '.obj']
|
||||||
imgui.separator()
|
|
||||||
if imgui.menu_item("复制路径"):
|
def _append_drop_target_from_last_item(self, target_path: Path):
|
||||||
imgui.set_clipboard_text(str(rm.context_menu_file))
|
item_min = imgui.get_item_rect_min()
|
||||||
self.app.add_info_message("路径已复制到剪贴板")
|
item_max = imgui.get_item_rect_max()
|
||||||
if imgui.menu_item("在文件管理器中显示"):
|
width = float(item_max.x - item_min.x)
|
||||||
import platform
|
height = float(item_max.y - item_min.y)
|
||||||
import subprocess
|
if width <= 0 or height <= 0:
|
||||||
if platform.system() == "Windows":
|
return
|
||||||
subprocess.run(["explorer", "/select,", str(rm.context_menu_file)])
|
self.app._resource_drop_targets.append((
|
||||||
elif platform.system() == "Darwin":
|
float(item_min.x),
|
||||||
subprocess.run(["open", "-R", str(rm.context_menu_file)])
|
float(item_min.y),
|
||||||
else:
|
width,
|
||||||
subprocess.run(["xdg-open", str(rm.context_menu_file.parent)])
|
height,
|
||||||
|
str(target_path),
|
||||||
# 如果点击其他地方,关闭菜单
|
))
|
||||||
if imgui.is_mouse_clicked(0) or imgui.is_mouse_clicked(1):
|
|
||||||
if not imgui.is_window_hovered():
|
def _start_resource_drag_if_needed(self, rm, fallback_path: Path):
|
||||||
rm.show_context_menu = False
|
if not imgui.is_item_active() or not imgui.is_mouse_dragging(0):
|
||||||
rm.context_menu_file = None
|
return
|
||||||
|
drag_files = list(rm.selected_files) if rm.selected_files else [fallback_path]
|
||||||
|
rm.start_drag(drag_files)
|
||||||
|
self.app.is_dragging = True
|
||||||
|
self.app.show_drag_overlay = True
|
||||||
|
|
||||||
|
|
||||||
def _draw_property_panel(self):
|
def _draw_property_panel(self):
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import os
|
import os
|
||||||
|
from pathlib import Path
|
||||||
from imgui_bundle import imgui, imgui_ctx
|
from imgui_bundle import imgui, imgui_ctx
|
||||||
|
|
||||||
|
|
||||||
@ -19,129 +20,173 @@ class InteractionPanels:
|
|||||||
|
|
||||||
|
|
||||||
def _draw_drag_drop_interface(self):
|
def _draw_drag_drop_interface(self):
|
||||||
"""绘制拖拽界面"""
|
"""处理资源管理器到场景/树的拖拽完成。"""
|
||||||
# 检查资源管理器的拖拽状态
|
|
||||||
if self.resource_manager.is_dragging():
|
if self.resource_manager.is_dragging():
|
||||||
self.is_dragging = True
|
self.is_dragging = True
|
||||||
self.dragged_files = self.resource_manager.get_dragged_files()
|
self.dragged_files = self.resource_manager.get_dragged_files()
|
||||||
self.show_drag_overlay = True
|
|
||||||
|
if self.is_dragging and self.dragged_files and imgui.is_mouse_released(0):
|
||||||
# 绘制拖拽覆盖层
|
self._handle_drag_drop_completion()
|
||||||
if self.show_drag_overlay:
|
|
||||||
self._draw_drag_overlay()
|
|
||||||
|
|
||||||
# 检查是否有拖拽的文件需要处理
|
|
||||||
if self.is_dragging and self.dragged_files:
|
|
||||||
# 显示拖拽状态
|
|
||||||
self._draw_drag_status()
|
|
||||||
|
|
||||||
# 检查是否释放鼠标(结束拖拽)
|
|
||||||
if imgui.is_mouse_released(0):
|
|
||||||
self._handle_drag_drop_completion()
|
|
||||||
|
|
||||||
|
|
||||||
def _handle_drag_drop_completion(self):
|
def _handle_drag_drop_completion(self):
|
||||||
"""处理拖拽完成"""
|
"""处理资源管理器内部拖拽落点。"""
|
||||||
# 检查是否在3D视图中释放
|
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()
|
mouse_pos = imgui.get_mouse_pos()
|
||||||
viewport = imgui.get_main_viewport()
|
drop_kind, target_dir, target_parent = self._resolve_drop_target(mouse_pos)
|
||||||
|
|
||||||
# 简单检查:如果不在任何ImGui窗口上,则认为是在3D视图中
|
if drop_kind == "resource_manager":
|
||||||
if not imgui.is_any_window_hovered():
|
moved, errors = self.resource_manager.move_files_to_directory(dragged_files, target_dir)
|
||||||
# 导入支持的3D模型文件
|
if moved:
|
||||||
imported_count = 0
|
label = self.resource_manager.get_relative_path(target_dir) if target_dir else "资源管理器"
|
||||||
for file_path in self.dragged_files:
|
self.add_success_message(f"已移动 {len(moved)} 个资源到 {label}")
|
||||||
if file_path.suffix.lower() in ['.gltf', '.glb', '.fbx', '.bam', '.egg', '.obj']:
|
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:
|
try:
|
||||||
self._import_model_for_runtime(str(file_path))
|
self.scene_manager.processMaterials(model_node)
|
||||||
self.add_success_message(f"成功导入模型: {file_path.name}")
|
except Exception:
|
||||||
imported_count += 1
|
pass
|
||||||
except Exception as e:
|
if drop_kind == "scene_tree" and self._is_valid_drop_parent(target_parent):
|
||||||
self.add_error_message(f"导入模型失败 {file_path.name}: {e}")
|
try:
|
||||||
|
model_node.wrtReparentTo(target_parent)
|
||||||
if imported_count > 0:
|
except Exception:
|
||||||
self.add_success_message(f"共导入 {imported_count} 个模型")
|
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.is_dragging = False
|
||||||
self.dragged_files.clear()
|
self.dragged_files.clear()
|
||||||
self.show_drag_overlay = False
|
self.show_drag_overlay = False
|
||||||
self.resource_manager.clear_drag()
|
self.resource_manager.clear_drag()
|
||||||
|
self._drag_scene_tree_hover_node = None
|
||||||
|
|
||||||
|
|
||||||
def _draw_drag_overlay(self):
|
def _draw_drag_overlay(self):
|
||||||
"""绘制拖拽覆盖层"""
|
"""已弃用:保留兼容接口,不再绘制遮罩。"""
|
||||||
viewport = imgui.get_main_viewport()
|
return
|
||||||
imgui.set_next_window_pos((0, 0))
|
|
||||||
imgui.set_next_window_size(viewport.work_size)
|
|
||||||
|
|
||||||
flags = (
|
|
||||||
imgui.WindowFlags_.no_title_bar |
|
|
||||||
imgui.WindowFlags_.no_resize |
|
|
||||||
imgui.WindowFlags_.no_move |
|
|
||||||
imgui.WindowFlags_.no_scrollbar |
|
|
||||||
imgui.WindowFlags_.no_saved_settings |
|
|
||||||
imgui.WindowFlags_.no_background |
|
|
||||||
imgui.WindowFlags_.no_focus_on_appearing
|
|
||||||
)
|
|
||||||
|
|
||||||
imgui.begin("##DragOverlay", True, flags)
|
|
||||||
|
|
||||||
# 绘制半透明背景
|
|
||||||
draw_list = imgui.get_window_draw_list()
|
|
||||||
draw_list.add_rect_filled(
|
|
||||||
(0, 0), viewport.work_size,
|
|
||||||
imgui.get_color_u32((0, 0, 0, 0.1))
|
|
||||||
)
|
|
||||||
|
|
||||||
# 绘制提示文本
|
|
||||||
text_size = imgui.calc_text_size("释放以导入文件")
|
|
||||||
text_pos = (
|
|
||||||
(viewport.work_size.x - text_size.x) / 2,
|
|
||||||
(viewport.work_size.y - text_size.y) / 2
|
|
||||||
)
|
|
||||||
|
|
||||||
draw_list.add_text(
|
|
||||||
text_pos,
|
|
||||||
imgui.get_color_u32((1, 1, 1, 1)),
|
|
||||||
"释放以导入文件"
|
|
||||||
)
|
|
||||||
|
|
||||||
imgui.end()
|
|
||||||
|
|
||||||
|
|
||||||
def _draw_drag_status(self):
|
def _draw_drag_status(self):
|
||||||
"""绘制拖拽状态"""
|
"""已弃用:保留兼容接口,不再绘制状态窗。"""
|
||||||
viewport = imgui.get_main_viewport()
|
return
|
||||||
|
|
||||||
# 在右下角显示拖拽状态
|
|
||||||
imgui.set_next_window_pos(
|
|
||||||
(viewport.work_size.x - 300, viewport.work_size.y - 150),
|
|
||||||
imgui.Cond_.first_use_ever
|
|
||||||
)
|
|
||||||
|
|
||||||
flags = (
|
|
||||||
imgui.WindowFlags_.no_title_bar |
|
|
||||||
imgui.WindowFlags_.no_resize |
|
|
||||||
imgui.WindowFlags_.no_move |
|
|
||||||
imgui.WindowFlags_.no_scrollbar |
|
|
||||||
imgui.WindowFlags_.no_saved_settings
|
|
||||||
)
|
|
||||||
|
|
||||||
with imgui_ctx.begin("拖拽状态", True, flags):
|
|
||||||
imgui.text("拖拽的文件:")
|
|
||||||
for file_path in self.dragged_files:
|
|
||||||
filename = os.path.basename(file_path)
|
|
||||||
imgui.text(f" • {filename}")
|
|
||||||
|
|
||||||
imgui.separator()
|
|
||||||
|
|
||||||
if imgui.button("导入所有文件"):
|
|
||||||
self.process_dragged_files()
|
|
||||||
|
|
||||||
imgui.same_line()
|
|
||||||
if imgui.button("取消"):
|
|
||||||
self.clear_dragged_files()
|
|
||||||
|
|
||||||
|
|
||||||
def _draw_context_menus(self):
|
def _draw_context_menus(self):
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user