ui替换
This commit is contained in:
parent
35cecd6008
commit
0860182b11
@ -1,446 +0,0 @@
|
||||
VR Manager 模块化拆分计划
|
||||
|
||||
📊 现状分析
|
||||
|
||||
当前状态:
|
||||
- 文件:core/vr_manager.py
|
||||
- 行数:4736行(严重超标,标准为900行)
|
||||
- 方法数:138个方法
|
||||
- 问题:典型的"上帝类"反模式,承担了过多职责
|
||||
|
||||
主要职责识别:
|
||||
1. 性能监控和调试 (~900行)
|
||||
2. 测试模式系统 (~800行)
|
||||
3. 对象池和优化 (~300行)
|
||||
4. 姿态跟踪系统 (~900行)
|
||||
5. 设备管理 (~300行)
|
||||
6. 渲染缓冲管理 (~400行)
|
||||
7. 相机系统 (~350行)
|
||||
8. 合成器和提交 (~300行)
|
||||
9. RenderPipeline集成 (~250行)
|
||||
10. 核心生命周期 (~500行)
|
||||
|
||||
---
|
||||
🎯 拆分策略
|
||||
|
||||
核心原则
|
||||
|
||||
1. 渐进迭代:每次拆分一个模块,立即验证
|
||||
2. 组合模式:新模块作为VRManager属性,保持接口不变
|
||||
3. 依赖顺序:先拆分独立模块,后拆分核心模块
|
||||
4. 向后兼容:所有公开API通过委托方法保持可用
|
||||
|
||||
拆分顺序(按依赖关系)
|
||||
|
||||
阶段1:性能监控 → 最独立,零依赖
|
||||
阶段2:测试调试 → 依赖少,可独立测试
|
||||
阶段3:对象池优化 → 小而关键,性能核心
|
||||
阶段4:跟踪系统 → 中等复杂度
|
||||
阶段5:渲染系统 → 核心功能,最复杂
|
||||
阶段6:核心管理器 → 整合所有子系统
|
||||
|
||||
---
|
||||
📋 详细拆分计划
|
||||
|
||||
🔷 阶段1:拆分性能监控系统 (2-3小时)
|
||||
|
||||
创建文件:core/vr/performance/monitoring.py (~900行)
|
||||
|
||||
迁移方法 (35个):
|
||||
- 性能报告:_print_performance_report, _print_performance_recommendations, _print_brief_performance_report
|
||||
- 性能监控:_init_performance_monitoring, _update_performance_metrics, _update_gpu_metrics, _track_frame_time
|
||||
- GPU计时:_get_gpu_frame_timing, enable_gpu_timing_monitoring, disable_gpu_timing_monitoring
|
||||
- 管线统计:_get_pipeline_stats, test_pipeline_monitoring, _start_timing, _end_timing
|
||||
- 诊断工具:_print_render_callback_diagnostics, _check_rendering_optimizations, _diagnose_opengl_state
|
||||
- 调试控制:enable_debug_output, disable_debug_output, set_debug_mode, toggle_debug_output, get_debug_status
|
||||
- 配置方法:set_performance_check_interval, set_frame_time_history_size, set_performance_report_interval
|
||||
- 查询方法:get_performance_stats, get_current_performance_summary, get_performance_monitoring_config
|
||||
- 控制方法:enable_performance_monitoring, disable_performance_monitoring, force_performance_report, reset_performance_counters
|
||||
- 状态查询:print_performance_monitoring_status, set_prediction_time
|
||||
|
||||
迁移属性 (~30个):
|
||||
# 性能监控开关
|
||||
performance_monitoring, debug_output_enabled, debug_mode
|
||||
enable_pipeline_monitoring, enable_gpu_timing
|
||||
|
||||
# 性能数据
|
||||
cpu_usage, memory_usage, gpu_usage, gpu_memory_usage
|
||||
frame_times, max_frame_time_history
|
||||
wait_poses_time, left_render_time, right_render_time, submit_time
|
||||
total_frame_time, vr_sync_wait_time
|
||||
|
||||
# GPU计时
|
||||
gpu_scene_render_ms, gpu_pre_submit_ms, gpu_post_submit_ms
|
||||
gpu_total_render_ms, gpu_compositor_render_ms
|
||||
gpu_timing_history, gpu_timing_failure_count
|
||||
|
||||
# 历史记录
|
||||
wait_poses_times, render_times, submit_times, sync_wait_times
|
||||
pipeline_history_size
|
||||
|
||||
类结构:
|
||||
class VRPerformanceMonitor:
|
||||
"""VR性能监控系统"""
|
||||
def __init__(self, vr_manager):
|
||||
self.vr_manager = vr_manager
|
||||
# 初始化所有性能监控属性
|
||||
|
||||
VRManager集成:
|
||||
# __init__ 中
|
||||
self.performance_monitor = VRPerformanceMonitor(self)
|
||||
|
||||
# 委托方法(保持API兼容)
|
||||
def enable_performance_monitoring(self):
|
||||
return self.performance_monitor.enable_performance_monitoring()
|
||||
|
||||
验证步骤:
|
||||
1. ✅ 编译检查:python -m py_compile core/vr/performance/monitoring.py
|
||||
2. ✅ 导入测试:启动应用,确认无导入错误
|
||||
3. ✅ 功能测试:调用 vr_manager.enable_performance_monitoring()
|
||||
4. ✅ 输出验证:检查性能报告正常生成
|
||||
5. ✅ API测试:验证所有委托方法可用
|
||||
|
||||
---
|
||||
🔷 阶段2:拆分测试调试系统 (2-3小时)
|
||||
|
||||
创建文件:core/vr/testing/test_mode.py (~800行)
|
||||
|
||||
迁移方法 (17个):
|
||||
- 测试模式:enable_vr_test_mode, disable_vr_test_mode, switch_test_display_mode
|
||||
- 纹理管理:_ensure_test_mode_textures, _create_cached_ovr_textures, _batch_submit_textures
|
||||
- 显示系统:_initialize_test_display, _update_test_display, _create_stereo_display, _cleanup_test_display
|
||||
- HUD系统:_initialize_test_performance_hud, _update_test_performance_hud, _cleanup_test_performance_hud
|
||||
- 状态查询:get_test_mode_status, get_test_mode_features, set_test_mode_features
|
||||
- 性能测试:run_vr_performance_test
|
||||
|
||||
迁移属性 (~15个):
|
||||
vr_test_mode, test_display_mode
|
||||
test_display_quad, test_right_quad, stereo_display_created
|
||||
test_performance_hud, test_performance_text
|
||||
test_mode_initialized
|
||||
hud_update_counter, hud_update_interval
|
||||
test_mode_submit_texture, test_mode_wait_poses
|
||||
|
||||
类结构:
|
||||
class VRTestMode:
|
||||
"""VR测试模式系统"""
|
||||
def __init__(self, vr_manager):
|
||||
self.vr_manager = vr_manager
|
||||
|
||||
验证步骤:
|
||||
1. ✅ 启用测试模式:vr_manager.enable_vr_test_mode('stereo')
|
||||
2. ✅ 检查显示:验证测试quad显示正确
|
||||
3. ✅ HUD验证:检查性能HUD显示
|
||||
4. ✅ 切换模式:测试 left/right/stereo 模式切换
|
||||
5. ✅ 禁用测试:vr_manager.disable_vr_test_mode()
|
||||
|
||||
---
|
||||
🔷 阶段3:拆分对象池和优化系统 (1-2小时)
|
||||
|
||||
创建文件:core/vr/performance/optimization.py (~300行)
|
||||
|
||||
迁移方法 (19个):
|
||||
- 对象池:_initialize_object_pools, _get_pooled_matrix, _return_pooled_matrix
|
||||
- GC控制:_manual_gc_control, enable_gc_control, disable_gc_control, set_manual_gc_interval, force_manual_gc
|
||||
- 分辨率:set_resolution_scale, set_quality_preset, cycle_quality_preset, _apply_resolution_scale
|
||||
- 查询方法:get_object_pool_status, get_resolution_info, print_resolution_info
|
||||
- 性能模式:enable_performance_mode, disable_performance_mode, set_performance_mode_trigger_frame, get_performance_mode_status
|
||||
|
||||
迁移属性 (~20个):
|
||||
# 对象池
|
||||
_matrix_pool, _matrix_pool_size
|
||||
_cached_matrices, _controller_poses_cache
|
||||
_left_ovr_texture, _right_ovr_texture
|
||||
|
||||
# GC控制
|
||||
_gc_control_enabled, _gc_disabled
|
||||
_manual_gc_interval, _last_manual_gc_frame
|
||||
|
||||
# 分辨率
|
||||
resolution_scale, base_eye_width, base_eye_height
|
||||
scaled_eye_width, scaled_eye_height
|
||||
quality_presets, current_quality_preset
|
||||
|
||||
# 性能模式
|
||||
performance_mode_enabled, performance_mode_trigger_frame
|
||||
|
||||
验证步骤:
|
||||
1. ✅ 对象池:检查Mat4对象池正常工作
|
||||
2. ✅ GC控制:验证手动GC按预期触发
|
||||
3. ✅ 分辨率:测试质量预设切换
|
||||
4. ✅ 性能测试:运行30秒性能测试,确认优化生效
|
||||
|
||||
---
|
||||
🔷 阶段4:拆分跟踪系统 (3-4小时)
|
||||
|
||||
创建文件1:core/vr/tracking/poses.py (~600行)
|
||||
|
||||
迁移方法 (12个):
|
||||
- 姿态获取:_wait_get_poses, _wait_get_poses_immediate, _wait_get_poses_with_prediction
|
||||
- 姿态缓存:_cache_poses_for_next_frame, _reset_waitgetposes_flag
|
||||
- 姿态更新:_update_tracking_data, update_hmd, _update_camera_poses, _update_camera_poses_with_cache
|
||||
- 矩阵转换:_convert_openvr_matrix_to_panda, _update_matrix_from_openvr, convert_mat
|
||||
|
||||
迁移属性:
|
||||
hmd_pose, controller_poses, tracked_device_poses
|
||||
poses, game_poses
|
||||
tracking_space, hmd_anchor, left_eye_anchor, right_eye_anchor
|
||||
coord_mat, coord_mat_inv
|
||||
use_prediction_time, poses_updated_in_task
|
||||
_waitgetposes_called_this_frame
|
||||
_cached_render_poses, _first_frame
|
||||
|
||||
创建文件2:core/vr/tracking/devices.py (~300行)
|
||||
|
||||
迁移方法 (8个):
|
||||
- 控制器:_initialize_controllers, _detect_controllers, get_controller_by_role
|
||||
- 设备管理:_create_tracked_device_anchor, update_tracked_devices
|
||||
- 状态查询:are_controllers_connected, get_connected_controllers
|
||||
- 震动:trigger_controller_haptic
|
||||
|
||||
迁移属性:
|
||||
left_controller, right_controller
|
||||
controllers, tracked_device_anchors
|
||||
|
||||
创建文件3:core/vr/tracking/input_wrapper.py (~200行)
|
||||
|
||||
迁移方法 (12个):
|
||||
- 按钮查询:is_trigger_pressed, is_trigger_just_pressed, is_grip_pressed, is_grip_just_pressed, is_menu_pressed
|
||||
- 触摸板:is_trackpad_touched, get_trackpad_position
|
||||
- 交互查询:get_selected_object, get_grabbed_object, is_grabbing_object
|
||||
- 交互控制:force_release_all_grabs, add_interactable_object
|
||||
|
||||
类结构:
|
||||
class VRPoseTracker:
|
||||
"""VR姿态跟踪系统"""
|
||||
|
||||
class VRDeviceManager:
|
||||
"""VR设备管理系统"""
|
||||
|
||||
class VRInputWrapper:
|
||||
"""VR输入包装层"""
|
||||
|
||||
验证步骤:
|
||||
1. ✅ 姿态跟踪:启动VR,检查头显跟踪正常
|
||||
2. ✅ 控制器检测:验证控制器正确识别
|
||||
3. ✅ 输入测试:测试按钮和触摸板输入
|
||||
4. ✅ 震动测试:触发控制器震动反馈
|
||||
|
||||
---
|
||||
🔷 阶段5:拆分渲染系统 (4-5小时)
|
||||
|
||||
创建文件1:core/vr/rendering/buffers.py (~400行)
|
||||
|
||||
迁移方法 (10个):
|
||||
- 缓冲区创建:_create_vr_buffers, _create_vr_buffer, _create_vr_buffers_with_pipeline
|
||||
- 纹理管理:_create_vr_texture, _prepare_and_cache_textures
|
||||
- Pipeline效果:_apply_pipeline_vr_effects
|
||||
- 天空盒:_check_skybox_status, _create_vr_skybox
|
||||
- 诊断:_diagnose_buffer_performance
|
||||
- 清理:_cleanup_vr_buffers
|
||||
|
||||
迁移属性:
|
||||
vr_left_eye_buffer, vr_right_eye_buffer
|
||||
vr_left_texture, vr_right_texture
|
||||
left_texture_id, right_texture_id, textures_prepared
|
||||
eye_width, eye_height, near_clip, far_clip
|
||||
scaled_eye_width, scaled_eye_height
|
||||
|
||||
创建文件2:core/vr/rendering/cameras.py (~350行)
|
||||
|
||||
迁移方法 (6个):
|
||||
- 相机设置:_setup_vr_cameras, _get_eye_offset
|
||||
- 优化:_optimize_vr_rendering, _apply_lightweight_rendering, _disable_vr_buffer_extras
|
||||
- 主相机:_disable_main_cam, _enable_main_cam
|
||||
|
||||
迁移属性:
|
||||
vr_left_camera, vr_right_camera
|
||||
|
||||
创建文件3:core/vr/rendering/compositor.py (~300行)
|
||||
|
||||
迁移方法 (9个):
|
||||
- 渲染回调:simple_left_cb, simple_right_cb
|
||||
- 纹理提交:submit_texture
|
||||
- GPU同步:_sync_gpu_if_needed, _smart_gpu_sync
|
||||
- ATW控制:_disable_async_reprojection, enable_async_reprojection_disable, disable_async_reprojection_disable
|
||||
|
||||
迁移属性:
|
||||
vr_compositor, submit_together
|
||||
openvr_frame_id
|
||||
left_eye_last_render_frame, right_eye_last_render_frame
|
||||
disable_async_reprojection
|
||||
|
||||
创建文件4:core/vr/rendering/pipeline.py (~250行)
|
||||
|
||||
迁移方法 (4个):
|
||||
- Pipeline集成:_create_vr_buffers_with_pipeline, _apply_pipeline_vr_effects
|
||||
- 模式切换:set_vr_render_mode, get_vr_render_mode
|
||||
|
||||
迁移属性:
|
||||
vr_render_mode, render_pipeline_enabled
|
||||
vr_pipeline_left_target, vr_pipeline_right_target
|
||||
pipeline_resolution_scale, vr_pipeline_controller
|
||||
pipeline_vr_config
|
||||
|
||||
验证步骤:
|
||||
1. ✅ 缓冲区:检查左右眼缓冲区正确创建
|
||||
2. ✅ 相机:验证双眼相机位置和视锥
|
||||
3. ✅ 渲染:测试左右眼渲染回调
|
||||
4. ✅ 提交:确认纹理正确提交到OpenVR
|
||||
5. ✅ Pipeline:测试RenderPipeline模式切换
|
||||
|
||||
---
|
||||
🔷 阶段6:重构核心管理器 (2-3小时)
|
||||
|
||||
保留在 core/vr_manager.py (~500行)
|
||||
|
||||
保留方法 (10个核心方法):
|
||||
- 生命周期:__init__, cleanup
|
||||
- VR初始化:is_vr_available, initialize_vr
|
||||
- VR控制:enable_vr, disable_vr
|
||||
- 主循环:_start_vr_task, _update_vr
|
||||
- 状态查询:get_vr_status
|
||||
|
||||
新增子系统属性(组合模式):
|
||||
def __init__(self, world):
|
||||
# ... 基础初始化 ...
|
||||
|
||||
# 子系统初始化(按依赖顺序)
|
||||
self.optimization = VROptimization(self)
|
||||
self.performance_monitor = VRPerformanceMonitor(self)
|
||||
self.test_mode = VRTestMode(self)
|
||||
|
||||
self.pose_tracker = VRPoseTracker(self)
|
||||
self.device_manager = VRDeviceManager(self)
|
||||
self.input_wrapper = VRInputWrapper(self)
|
||||
|
||||
self.buffer_manager = VRBufferManager(self)
|
||||
self.camera_manager = VRCameraManager(self)
|
||||
self.compositor = VRCompositor(self)
|
||||
self.pipeline_manager = VRPipelineManager(self)
|
||||
|
||||
委托方法(保持API兼容):
|
||||
# 性能监控委托
|
||||
def enable_performance_monitoring(self):
|
||||
return self.performance_monitor.enable_performance_monitoring()
|
||||
|
||||
# 测试模式委托
|
||||
def enable_vr_test_mode(self, display_mode='stereo'):
|
||||
return self.test_mode.enable_vr_test_mode(display_mode)
|
||||
|
||||
# 优化系统委托
|
||||
def set_resolution_scale(self, scale):
|
||||
return self.optimization.set_resolution_scale(scale)
|
||||
|
||||
# ... 其他委托方法 ...
|
||||
|
||||
验证步骤:
|
||||
1. ✅ 完整启动:启动应用,测试VR完整流程
|
||||
2. ✅ API测试:验证所有公开API可用
|
||||
3. ✅ 性能测试:运行性能测试,确认无回退
|
||||
4. ✅ 集成测试:测试传送、交互、渲染等所有功能
|
||||
|
||||
---
|
||||
✅ 每阶段验证清单
|
||||
|
||||
静态验证
|
||||
|
||||
- python -m py_compile 检查语法
|
||||
- python main.py --help 验证模块导入
|
||||
- 运行 pylint 检查代码质量
|
||||
|
||||
功能验证
|
||||
|
||||
- 启动应用:python main.py
|
||||
- 检查VR可用性:vr_manager.is_vr_available()
|
||||
- 测试VR启用:切换到VR模式
|
||||
- 测试头显跟踪:移动头显查看跟踪
|
||||
- 测试控制器:检测和交互
|
||||
- 测试传送:使用控制器传送
|
||||
- 测试性能监控:查看性能报告
|
||||
- 测试VR禁用:退出VR模式
|
||||
|
||||
性能验证
|
||||
|
||||
- 运行性能测试:vr_manager.run_vr_performance_test(30)
|
||||
- 检查帧率稳定在90fps
|
||||
- 对比拆分前后性能指标
|
||||
- 确认无性能回退
|
||||
|
||||
---
|
||||
🛡️ 风险控制
|
||||
|
||||
备份策略
|
||||
|
||||
# 每个阶段开始前
|
||||
cp core/vr_manager.py core/vr_manager.py.backup.stage{N}
|
||||
|
||||
回滚方案
|
||||
|
||||
# 恢复备份
|
||||
cp core/vr_manager.py.backup.stage{N} core/vr_manager.py
|
||||
|
||||
循环依赖预防
|
||||
|
||||
- ✅ 所有子模块通过 self.vr_manager 访问其他子系统
|
||||
- ✅ 子模块间不直接引用
|
||||
- ✅ VRManager 作为中介协调所有子系统
|
||||
|
||||
性能保护
|
||||
|
||||
- ✅ 热点路径(渲染回调)保持直接调用
|
||||
- ✅ 非关键路径使用委托模式
|
||||
- ✅ 对象池和缓存机制保持不变
|
||||
|
||||
---
|
||||
📊 预期成果
|
||||
|
||||
代码结构
|
||||
|
||||
core/vr_manager.py (500行) ✅
|
||||
core/vr/
|
||||
├── performance/
|
||||
│ ├── monitoring.py (900行) ✅
|
||||
│ └── optimization.py (300行) ✅
|
||||
├── testing/
|
||||
│ └── test_mode.py (800行) ✅
|
||||
├── tracking/
|
||||
│ ├── poses.py (600行) ✅
|
||||
│ ├── devices.py (300行) ✅
|
||||
│ └── input_wrapper.py (200行) ✅
|
||||
└── rendering/
|
||||
├── buffers.py (400行) ✅
|
||||
├── cameras.py (350行) ✅
|
||||
├── compositor.py (300行) ✅
|
||||
└── pipeline.py (250行) ✅
|
||||
|
||||
改进指标
|
||||
|
||||
- ✅ 文件行数:4736行 → 最大900行(符合规范)
|
||||
- ✅ 目录文件数:所有目录 ≤ 4个文件(远小于8个限制)
|
||||
- ✅ 方法数:138个 → 每个类 ≤ 20个方法
|
||||
- ✅ 职责清晰:每个模块单一职责
|
||||
- ✅ 可维护性:大幅提升
|
||||
- ✅ 可测试性:模块化便于单元测试
|
||||
- ✅ 向后兼容:100%保持现有API
|
||||
|
||||
时间估算
|
||||
|
||||
- 阶段1: 2-3小时
|
||||
- 阶段2: 2-3小时
|
||||
- 阶段3: 1-2小时
|
||||
- 阶段4: 3-4小时
|
||||
- 阶段5: 4-5小时
|
||||
- 阶段6: 2-3小时
|
||||
|
||||
总计:15-20小时,分6个阶段逐步完成
|
||||
|
||||
---
|
||||
🚀 开始执行
|
||||
|
||||
确认此计划后,将按以下顺序执行:
|
||||
1. 创建备份
|
||||
2. 阶段1:拆分性能监控系统
|
||||
3. 验证通过后,进入阶段2
|
||||
4. 依次完成所有6个阶段
|
||||
5. 最终整体测试和文档更新
|
||||
@ -1,30 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
echo "Building EG_Engine with PyInstaller..."
|
||||
/home/hello/.local/bin/pyinstaller --onedir --windowed --name=EG_Engine \
|
||||
--exclude-module pyassimp \
|
||||
--add-data="RenderPipelineFile:RenderPipelineFile" \
|
||||
--add-data="QMeta3D:QMeta3D" \
|
||||
--add-data="core:core" \
|
||||
--add-data="gui:gui" \
|
||||
--add-data="ui:ui" \
|
||||
--add-data="scene:scene" \
|
||||
--add-data="project:project" \
|
||||
--add-data="demo:demo" \
|
||||
--add-data="plugins:plugins" \
|
||||
--add-data="scripts:scripts" \
|
||||
--hidden-import=PyQt5.sip \
|
||||
--hidden-import=panda3d.core \
|
||||
main.py
|
||||
|
||||
echo "Creating run.sh script in dist/EG_Engine/"
|
||||
cat > dist/EG_Engine/run.sh << 'EOF'
|
||||
cd ..
|
||||
cd ..
|
||||
python3.10 main.py "$@"
|
||||
EOF
|
||||
|
||||
echo "Making run.sh executable"
|
||||
chmod +x dist/EG_Engine/run.sh
|
||||
|
||||
echo "Build completed!"
|
||||
@ -1,30 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
echo "Building EG_Engine with PyInstaller..."
|
||||
pyinstaller --onedir --windowed --name=EG_Engine \
|
||||
--exclude-module pyassimp \
|
||||
--add-data="RenderPipelineFile:RenderPipelineFile" \
|
||||
--add-data="QMeta3D:QMeta3D" \
|
||||
--add-data="core:core" \
|
||||
--add-data="gui:gui" \
|
||||
--add-data="ui:ui" \
|
||||
--add-data="scene:scene" \
|
||||
--add-data="project:project" \
|
||||
--add-data="demo:demo" \
|
||||
--add-data="plugins:plugins" \
|
||||
--add-data="scripts:scripts" \
|
||||
--hidden-import=PyQt5.sip \
|
||||
--hidden-import=panda3d.core \
|
||||
main.py
|
||||
|
||||
echo "Creating run.sh script in dist/EG_Engine/"
|
||||
cat > dist/EG_Engine/run.sh << 'EOF'
|
||||
cd ..
|
||||
cd ..
|
||||
python3 main.py "$@"
|
||||
EOF
|
||||
|
||||
echo "Making run.sh executable"
|
||||
chmod +x dist/EG_Engine/run.sh
|
||||
|
||||
echo "Build completed!"
|
||||
@ -1,12 +0,0 @@
|
||||
@echo off
|
||||
set PYTHONPATH=%~dp0_internal
|
||||
pyinstaller --onedir --windowed --name=EG_Engine --exclude-module pyassimp --add-data="RenderPipelineFile;RenderPipelineFile" --add-data="QMeta3D;QMeta3D" --add-data="core;core" --add-data="gui;gui" --add-data="ui;ui" --add-data="scene;scene" --add-data="project;project" --add-data="demo;demo" --add-data="plugins;plugins" --add-data="scripts;scripts" --hidden-import=PyQt5.sip --hidden-import=panda3d.core main.py
|
||||
|
||||
echo @echo off > dist\EG_Engine\run.bat
|
||||
echo set PYTHONPATH=%%~dp0_internal >> dist\EG_Engine\run.bat
|
||||
echo cd .. >> dist\EG_Engine\run.bat
|
||||
echo cd .. >> dist\EG_Engine\run.bat
|
||||
echo python main.py >> dist\EG_Engine\run.bat
|
||||
echo pause >> dist\EG_Engine\run.bat
|
||||
|
||||
pause
|
||||
@ -1,95 +1,145 @@
|
||||
from direct.task.TaskManagerGlobal import taskMgr
|
||||
|
||||
|
||||
class CustomMouseController:
|
||||
def __init__(self, showbase):
|
||||
self.showbase = showbase
|
||||
# This is used to store which keys are currently pressed.
|
||||
self.keyMap = {
|
||||
"mouse1": 0,
|
||||
"cam-forward": 0,
|
||||
"cam-backward": 0,
|
||||
"cam-left": 0,
|
||||
"cam-right": 0,
|
||||
"cam-up": 0,
|
||||
"cam-down": 0
|
||||
}
|
||||
|
||||
# 添加鼠标控制
|
||||
# self.showbase.accept("mouse1", self.setKey, ["mouse1", True])
|
||||
# self.showbase.accept("mouse1-up", self.setKey, ["mouse1", False])
|
||||
|
||||
self.showbase.accept("w", self.setKey, ["cam-forward", True])
|
||||
self.showbase.accept("a", self.setKey, ["cam-left", True])
|
||||
self.showbase.accept("s", self.setKey, ["cam-backward", True])
|
||||
self.showbase.accept("d", self.setKey, ["cam-right", True])
|
||||
self.showbase.accept("e", self.setKey, ["cam-up", True])
|
||||
self.showbase.accept("q", self.setKey, ["cam-down", True])
|
||||
|
||||
self.showbase.accept("w-up", self.setKey, ["cam-forward", False])
|
||||
self.showbase.accept("a-up", self.setKey, ["cam-left", False])
|
||||
self.showbase.accept("s-up", self.setKey, ["cam-backward", False])
|
||||
self.showbase.accept("d-up", self.setKey, ["cam-right", False])
|
||||
self.showbase.accept("e-up", self.setKey, ["cam-up", False])
|
||||
self.showbase.accept("q-up", self.setKey, ["cam-down", False])
|
||||
|
||||
self.last_mouse_x = 0
|
||||
self.last_mouse_y = 0
|
||||
|
||||
self.camera_speed = 25
|
||||
self.move_speed = 20
|
||||
|
||||
def setUp(self, mouse_speed: float = 25, move_speed: float = 20):
|
||||
taskMgr.add(self.move, "moveTask")
|
||||
self.camera_speed = mouse_speed
|
||||
self.move_speed = move_speed
|
||||
|
||||
def setKey(self, key, value, arg: str = None):
|
||||
self.keyMap[key] = value
|
||||
if key == "mouse1" and value == True and self.keyMap[key]:
|
||||
mouse_pos = self.showbase.mouseWatcherNode.getMouse()
|
||||
self.last_mouse_x = mouse_pos.get_x()
|
||||
self.last_mouse_y = mouse_pos.get_y()
|
||||
|
||||
def move(self, task):
|
||||
dt = self.showbase.clock.dt
|
||||
|
||||
if self.keyMap["cam-left"]:
|
||||
self.showbase.camera.setX(self.showbase.camera, -self.move_speed * dt)
|
||||
if self.keyMap["cam-right"]:
|
||||
self.showbase.camera.setX(self.showbase.camera, +self.move_speed * dt)
|
||||
if self.keyMap["cam-backward"]:
|
||||
self.showbase.camera.setY(self.showbase.camera, -self.move_speed * dt)
|
||||
if self.keyMap["cam-forward"]:
|
||||
self.showbase.camera.setY(self.showbase.camera, +self.move_speed * dt)
|
||||
if self.keyMap["cam-up"]:
|
||||
self.showbase.camera.setZ(self.showbase.camera, +self.move_speed * dt)
|
||||
if self.keyMap["cam-down"]:
|
||||
self.showbase.camera.setZ(self.showbase.camera, -self.move_speed * dt)
|
||||
if self.keyMap["mouse1"]:
|
||||
try:
|
||||
mouse_pos = self.showbase.mouseWatcherNode.getMouse()
|
||||
current_x = mouse_pos.get_x()
|
||||
current_y = mouse_pos.get_y()
|
||||
|
||||
# 计算鼠标移动差值
|
||||
dx = current_x - self.last_mouse_x
|
||||
dy = current_y - self.last_mouse_y
|
||||
dy *= -1
|
||||
|
||||
# 根据鼠标移动调整摄像机
|
||||
if abs(dx) > 0.01 or abs(dy) > 0.01:
|
||||
cam_hpr = self.showbase.camera.get_hpr()
|
||||
self.showbase.camera.set_hpr(
|
||||
cam_hpr.x - dx * self.camera_speed,
|
||||
max(-90, min(90, cam_hpr.y - dy * self.camera_speed)),
|
||||
0
|
||||
)
|
||||
|
||||
# 更新鼠标位置
|
||||
self.last_mouse_x = current_x
|
||||
self.last_mouse_y = current_y
|
||||
except Exception as e:
|
||||
print(f"旋转相机失败:{e}")
|
||||
|
||||
return task.cont
|
||||
from direct.task.TaskManagerGlobal import taskMgr
|
||||
|
||||
|
||||
class CustomMouseController:
|
||||
def __init__(self, showbase):
|
||||
self.showbase = showbase
|
||||
# This is used to store which keys are currently pressed.
|
||||
self.keyMap = {
|
||||
"mouse1": 0,
|
||||
"mouse3": 0, # 右键
|
||||
"cam-forward": 0,
|
||||
"cam-backward": 0,
|
||||
"cam-left": 0,
|
||||
"cam-right": 0,
|
||||
"cam-up": 0,
|
||||
"cam-down": 0
|
||||
}
|
||||
|
||||
# 添加鼠标控制
|
||||
self.showbase.accept("mouse1", self.setKey, ["mouse1", True])
|
||||
self.showbase.accept("mouse1-up", self.setKey, ["mouse1", False])
|
||||
self.showbase.accept("mouse3", self.setKey, ["mouse3", True]) # 右键
|
||||
self.showbase.accept("mouse3-up", self.setKey, ["mouse3", False]) # 右键释放
|
||||
|
||||
self.showbase.accept("w", self.setKey, ["cam-forward", True])
|
||||
self.showbase.accept("a", self.setKey, ["cam-left", True])
|
||||
self.showbase.accept("s", self.setKey, ["cam-backward", True])
|
||||
self.showbase.accept("d", self.setKey, ["cam-right", True])
|
||||
self.showbase.accept("e", self.setKey, ["cam-up", True])
|
||||
self.showbase.accept("q", self.setKey, ["cam-down", True])
|
||||
|
||||
self.showbase.accept("w-up", self.setKey, ["cam-forward", False])
|
||||
self.showbase.accept("a-up", self.setKey, ["cam-left", False])
|
||||
self.showbase.accept("s-up", self.setKey, ["cam-backward", False])
|
||||
self.showbase.accept("d-up", self.setKey, ["cam-right", False])
|
||||
self.showbase.accept("e-up", self.setKey, ["cam-up", False])
|
||||
self.showbase.accept("q-up", self.setKey, ["cam-down", False])
|
||||
|
||||
self.last_mouse_x = 0
|
||||
self.last_mouse_y = 0
|
||||
|
||||
self.camera_speed = 25
|
||||
self.move_speed = 20
|
||||
|
||||
def setUp(self, mouse_speed: float = 25, move_speed: float = 20):
|
||||
taskMgr.add(self.move, "moveTask")
|
||||
self.camera_speed = mouse_speed
|
||||
self.move_speed = move_speed
|
||||
|
||||
def setKey(self, key, value, arg: str = None):
|
||||
self.keyMap[key] = value
|
||||
if (key == "mouse1" or key == "mouse3") and value == True:
|
||||
mouse_pos = self.showbase.mouseWatcherNode.getMouse()
|
||||
if mouse_pos:
|
||||
self.last_mouse_x = mouse_pos.get_x()
|
||||
self.last_mouse_y = mouse_pos.get_y()
|
||||
|
||||
def move(self, task):
|
||||
dt = self.showbase.clock.dt
|
||||
|
||||
if self.keyMap["cam-left"]:
|
||||
self.showbase.camera.setX(self.showbase.camera, -self.move_speed * dt)
|
||||
if self.keyMap["cam-right"]:
|
||||
self.showbase.camera.setX(self.showbase.camera, +self.move_speed * dt)
|
||||
if self.keyMap["cam-backward"]:
|
||||
self.showbase.camera.setY(self.showbase.camera, -self.move_speed * dt)
|
||||
if self.keyMap["cam-forward"]:
|
||||
self.showbase.camera.setY(self.showbase.camera, +self.move_speed * dt)
|
||||
if self.keyMap["cam-up"]:
|
||||
self.showbase.camera.setZ(self.showbase.camera, +self.move_speed * dt)
|
||||
if self.keyMap["cam-down"]:
|
||||
self.showbase.camera.setZ(self.showbase.camera, -self.move_speed * dt)
|
||||
if self.keyMap["mouse1"] or self.keyMap["mouse3"]: # 左键或右键按下
|
||||
try:
|
||||
# 检查是否应该处理鼠标事件(避免与ImGui冲突)
|
||||
if self._should_handle_mouse():
|
||||
mouse_pos = self.showbase.mouseWatcherNode.getMouse()
|
||||
if mouse_pos:
|
||||
current_x = mouse_pos.get_x()
|
||||
current_y = mouse_pos.get_y()
|
||||
|
||||
# 计算鼠标移动差值
|
||||
dx = current_x - self.last_mouse_x
|
||||
dy = current_y - self.last_mouse_y
|
||||
dy *= -1
|
||||
|
||||
# 根据鼠标移动调整摄像机
|
||||
if abs(dx) > 0.01 or abs(dy) > 0.01:
|
||||
cam_hpr = self.showbase.camera.get_hpr()
|
||||
self.showbase.camera.set_hpr(
|
||||
cam_hpr.x - dx * self.camera_speed,
|
||||
max(-90, min(90, cam_hpr.y - dy * self.camera_speed)),
|
||||
0
|
||||
)
|
||||
|
||||
# 更新鼠标位置
|
||||
self.last_mouse_x = current_x
|
||||
self.last_mouse_y = current_y
|
||||
except Exception as e:
|
||||
print(f"旋转相机失败:{e}")
|
||||
|
||||
return task.cont
|
||||
|
||||
def _should_handle_mouse(self):
|
||||
"""检查是否应该处理鼠标事件(避免与ImGui冲突)"""
|
||||
try:
|
||||
# 如果是右键,优先处理视角控制
|
||||
if self.keyMap["mouse3"]:
|
||||
return True
|
||||
|
||||
# 检查ImGui是否想要捕获鼠标
|
||||
try:
|
||||
from imgui_bundle import imgui
|
||||
if hasattr(imgui, 'get_io') and imgui.get_io().want_capture_mouse:
|
||||
return False
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
# 检查鼠标位置是否在ImGui窗口区域内
|
||||
mouse_pos = self.showbase.mouseWatcherNode.getMouse()
|
||||
if not mouse_pos:
|
||||
return True
|
||||
|
||||
# 获取显示尺寸
|
||||
try:
|
||||
from imgui_bundle import imgui
|
||||
display_size = imgui.get_io().display_size
|
||||
mouse_x = mouse_pos.get_x() * display_size.x / 2 + display_size.x / 2
|
||||
mouse_y = display_size.y - (mouse_pos.get_y() * display_size.y / 2 + display_size.y / 2)
|
||||
|
||||
# 检查是否在常见的ImGui界面区域内
|
||||
# 这里可以根据实际的ImGui窗口位置进行更精确的检测
|
||||
if mouse_x < 300 and mouse_y < 200: # 左上角区域(菜单栏)
|
||||
return False
|
||||
if mouse_x < 300 and mouse_y > display_size.y - 200: # 左下角区域(工具栏)
|
||||
return False
|
||||
if mouse_x > display_size.x - 300 and mouse_y < 200: # 右上角区域
|
||||
return False
|
||||
except:
|
||||
pass
|
||||
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"鼠标事件检测失败: {e}")
|
||||
return True
|
||||
788
demo.py
788
demo.py
@ -13,6 +13,8 @@ from imgui_bundle import imgui, imgui_ctx
|
||||
import sys
|
||||
import os
|
||||
import warnings
|
||||
import threading
|
||||
import time
|
||||
|
||||
# 导入MyWorld类和必要的模块
|
||||
from core.world import CoreWorld
|
||||
@ -28,6 +30,59 @@ from scene.scene_manager import SceneManager
|
||||
from project.project_manager import ProjectManager
|
||||
from core.InfoPanelManager import InfoPanelManager
|
||||
from core.collision_manager import CollisionManager
|
||||
from core.CustomMouseController import CustomMouseController
|
||||
|
||||
# 拖拽监控类
|
||||
class DragDropMonitor:
|
||||
"""拖拽文件监控器"""
|
||||
|
||||
def __init__(self, world):
|
||||
self.world = world
|
||||
self.running = False
|
||||
self.thread = None
|
||||
|
||||
# 支持的文件格式
|
||||
self.supported_formats = ['.gltf', '.glb', '.fbx', '.bam', '.egg', '.obj']
|
||||
|
||||
def start(self):
|
||||
"""启动监控"""
|
||||
if not self.running:
|
||||
self.running = True
|
||||
self.thread = threading.Thread(target=self._monitor_loop, daemon=True)
|
||||
self.thread.start()
|
||||
|
||||
def stop(self):
|
||||
"""停止监控"""
|
||||
self.running = False
|
||||
if self.thread:
|
||||
self.thread.join()
|
||||
|
||||
def _monitor_loop(self):
|
||||
"""监控循环"""
|
||||
while self.running:
|
||||
try:
|
||||
# 这里可以实现具体的拖拽检测逻辑
|
||||
# 由于Panda3D限制,我们使用简化的实现
|
||||
time.sleep(0.1)
|
||||
|
||||
# 检查是否有新的拖拽文件
|
||||
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):
|
||||
"""从外部添加文件路径(用于系统级拖拽集成)"""
|
||||
if self.world:
|
||||
self.world.add_dragged_file(file_path)
|
||||
|
||||
try:
|
||||
# 尝试导入视频管理器,避免循环导入
|
||||
import importlib.util
|
||||
@ -95,6 +150,11 @@ class MyWorld(CoreWorld):
|
||||
# 初始化碰撞管理器
|
||||
self.collision_manager = CollisionManager(self)
|
||||
|
||||
# 初始化自定义鼠标控制器(视角移动)
|
||||
self.mouse_controller = CustomMouseController(self)
|
||||
self.mouse_controller.setUp(mouse_speed=25, move_speed=20)
|
||||
print("✓ 自定义鼠标控制器初始化完成")
|
||||
|
||||
# 初始化VR管理器
|
||||
try:
|
||||
from core.vr import VRManager
|
||||
@ -230,6 +290,25 @@ class MyWorld(CoreWorld):
|
||||
# 消息系统
|
||||
self.messages = []
|
||||
self.max_messages = 5 # 最多显示5条消息
|
||||
|
||||
# 剪切板系统
|
||||
self.clipboard = []
|
||||
self.clipboard_mode = "" # "copy" 或 "cut"
|
||||
|
||||
# 视角控制状态
|
||||
self.camera_control_enabled = True
|
||||
self.show_camera_info = False
|
||||
|
||||
# 拖拽导入状态
|
||||
self.dragged_files = []
|
||||
self.is_dragging = False
|
||||
self.show_drag_overlay = False
|
||||
self.drag_drop_monitor = None
|
||||
|
||||
# 导入功能状态
|
||||
self.show_import_dialog = False
|
||||
self.import_file_path = ""
|
||||
self.supported_formats = [".gltf", ".glb", ".fbx", ".bam", ".egg", ".obj"]
|
||||
|
||||
self.accept('imgui-new-frame', self.__newFrame)
|
||||
self.accept('`', self.__toggleImgui)
|
||||
@ -243,6 +322,18 @@ class MyWorld(CoreWorld):
|
||||
self.accept('o', self._on_o_pressed)
|
||||
self.accept('s', self._on_s_pressed)
|
||||
self.accept('f4', self._on_f4_pressed)
|
||||
|
||||
# 编辑功能快捷键
|
||||
self.accept('z', self._on_z_pressed)
|
||||
self.accept('y', self._on_y_pressed)
|
||||
self.accept('x', self._on_x_pressed)
|
||||
self.accept('c', self._on_c_pressed)
|
||||
self.accept('v', self._on_v_pressed)
|
||||
self.accept('delete', self._on_delete_pressed)
|
||||
|
||||
# 滚轮事件
|
||||
self.accept('wheel_up', self._on_wheel_up)
|
||||
self.accept('wheel_down', self._on_wheel_down)
|
||||
|
||||
self.testTexture = None
|
||||
self.icons = {} # 初始化图标字典
|
||||
@ -251,6 +342,10 @@ class MyWorld(CoreWorld):
|
||||
self.add_success_message("MyWorld 初始化完成")
|
||||
self.add_info_message("ImGui菜单系统已就绪")
|
||||
self.add_info_message("快捷键已启用 (Ctrl+N, Ctrl+O, Ctrl+S, Alt+F4)")
|
||||
|
||||
# 启用拖拽导入功能
|
||||
self.setup_drag_drop_support()
|
||||
self.add_info_message("拖拽导入功能已启用 - 可将3D文件拖拽到窗口中导入")
|
||||
|
||||
print("✓ MyWorld 初始化完成")
|
||||
|
||||
@ -443,6 +538,10 @@ class MyWorld(CoreWorld):
|
||||
self._draw_new_project_dialog()
|
||||
self._draw_open_project_dialog()
|
||||
self._draw_path_browser()
|
||||
self._draw_import_dialog()
|
||||
|
||||
# 绘制拖拽界面
|
||||
self._draw_drag_drop_interface()
|
||||
|
||||
def _draw_docked_layout(self, window_width, window_height):
|
||||
"""绘制可停靠的布局(支持拖拽)"""
|
||||
@ -491,12 +590,20 @@ class MyWorld(CoreWorld):
|
||||
# 编辑菜单
|
||||
with imgui_ctx.begin_menu("编辑") as edit_menu:
|
||||
if edit_menu:
|
||||
imgui.menu_item("撤销", "Ctrl+Z", False, True)
|
||||
imgui.menu_item("重做", "Ctrl+Y", False, True)
|
||||
if imgui.menu_item("撤销", "Ctrl+Z", False, True)[1]:
|
||||
self._on_undo()
|
||||
if imgui.menu_item("重做", "Ctrl+Y", False, True)[1]:
|
||||
self._on_redo()
|
||||
imgui.separator()
|
||||
imgui.menu_item("复制", "Ctrl+C", False, True)
|
||||
imgui.menu_item("粘贴", "Ctrl+V", False, True)
|
||||
imgui.menu_item("删除", "Del", False, True)
|
||||
if imgui.menu_item("剪切", "Ctrl+X", False, True)[1]:
|
||||
self._on_cut()
|
||||
if imgui.menu_item("复制", "Ctrl+C", False, True)[1]:
|
||||
self._on_copy()
|
||||
if imgui.menu_item("粘贴", "Ctrl+V", False, True)[1]:
|
||||
self._on_paste()
|
||||
imgui.separator()
|
||||
if imgui.menu_item("删除", "Del", False, True)[1]:
|
||||
self._on_delete()
|
||||
|
||||
# 视图菜单
|
||||
with imgui_ctx.begin_menu("视图") as view_menu:
|
||||
@ -510,7 +617,8 @@ class MyWorld(CoreWorld):
|
||||
# 工具菜单
|
||||
with imgui_ctx.begin_menu("工具") as tools_menu:
|
||||
if tools_menu:
|
||||
imgui.menu_item("导入模型", "", False, True)
|
||||
if imgui.menu_item("导入模型", "", False, True)[1]:
|
||||
self._on_import_model()
|
||||
imgui.menu_item("地形编辑器", "", False, True)
|
||||
imgui.menu_item("材质编辑器", "", False, True)
|
||||
imgui.menu_item("脚本编辑器", "", False, True)
|
||||
@ -708,6 +816,30 @@ class MyWorld(CoreWorld):
|
||||
if changed and command:
|
||||
self.add_info_message(f"执行命令: {command}")
|
||||
# TODO: 实现命令执行逻辑
|
||||
|
||||
imgui.separator()
|
||||
|
||||
# 视角控制信息
|
||||
imgui.text("视角控制:")
|
||||
imgui.text(" WASD - 移动")
|
||||
imgui.text(" Q/E - 上下")
|
||||
imgui.text(" 右键拖拽 - 旋转视角")
|
||||
imgui.text(" 滚轮 - 前进/后退")
|
||||
|
||||
# 相机位置信息
|
||||
cam_pos = self.camera.getPos()
|
||||
cam_hpr = self.camera.getHpr()
|
||||
imgui.text(f"位置: X={cam_pos.x:.1f}, Y={cam_pos.y:.1f}, Z={cam_pos.z:.1f}")
|
||||
imgui.text(f"旋转: H={cam_hpr.x:.1f}, P={cam_hpr.y:.1f}, R={cam_hpr.z:.1f}")
|
||||
|
||||
# 控制状态
|
||||
imgui.checkbox("启用视角控制", self.camera_control_enabled)
|
||||
|
||||
# 重置按钮
|
||||
if imgui.button("重置相机"):
|
||||
self.camera.setPos(0, -20, 5)
|
||||
self.camera.setHpr(0, 0, 0)
|
||||
self.add_info_message("相机位置已重置")
|
||||
|
||||
def _draw_script_panel(self):
|
||||
"""绘制脚本管理面板"""
|
||||
@ -830,6 +962,101 @@ class MyWorld(CoreWorld):
|
||||
if self.alt_pressed:
|
||||
self._on_exit()
|
||||
|
||||
def _on_z_pressed(self):
|
||||
"""Z键按下 - 检查Ctrl+Z组合键(撤销)"""
|
||||
if self.ctrl_pressed:
|
||||
self._on_undo()
|
||||
|
||||
def _on_y_pressed(self):
|
||||
"""Y键按下 - 检查Ctrl+Y组合键(重做)"""
|
||||
if self.ctrl_pressed:
|
||||
self._on_redo()
|
||||
|
||||
def _on_x_pressed(self):
|
||||
"""X键按下 - 检查Ctrl+X组合键(剪切)"""
|
||||
if self.ctrl_pressed:
|
||||
self._on_cut()
|
||||
|
||||
def _on_c_pressed(self):
|
||||
"""C键按下 - 检查Ctrl+C组合键(复制)"""
|
||||
if self.ctrl_pressed:
|
||||
self._on_copy()
|
||||
|
||||
def _on_v_pressed(self):
|
||||
"""V键按下 - 检查Ctrl+V组合键(粘贴)"""
|
||||
if self.ctrl_pressed:
|
||||
self._on_paste()
|
||||
|
||||
def _on_delete_pressed(self):
|
||||
"""Delete键按下 - 删除选中节点"""
|
||||
self._on_delete()
|
||||
|
||||
def _on_wheel_up(self):
|
||||
"""滚轮向上滚动 - 相机前进"""
|
||||
try:
|
||||
if not self.camera_control_enabled:
|
||||
return
|
||||
|
||||
# 检查鼠标是否在ImGui窗口上
|
||||
if self._is_mouse_over_imgui():
|
||||
return
|
||||
|
||||
# 沿相机前向向量移动
|
||||
forward = self.camera.getMat().getRow3(1)
|
||||
distance = 20.0 * globalClock.getDt()
|
||||
currentPos = self.camera.getPos()
|
||||
newPos = currentPos + forward * distance
|
||||
self.camera.setPos(newPos)
|
||||
except Exception as e:
|
||||
print(f"滚轮前进失败: {e}")
|
||||
|
||||
def _on_wheel_down(self):
|
||||
"""滚轮向下滚动 - 相机后退"""
|
||||
try:
|
||||
# 检查鼠标是否在ImGui窗口上
|
||||
if self._is_mouse_over_imgui():
|
||||
return
|
||||
|
||||
# 沿相机前向向量移动
|
||||
forward = self.camera.getMat().getRow3(1)
|
||||
distance = -20.0 * globalClock.getDt()
|
||||
currentPos = self.camera.getPos()
|
||||
newPos = currentPos + forward * distance
|
||||
self.camera.setPos(newPos)
|
||||
except Exception as e:
|
||||
print(f"滚轮后退失败: {e}")
|
||||
|
||||
def _is_mouse_over_imgui(self):
|
||||
"""检测鼠标是否在ImGui窗口上"""
|
||||
try:
|
||||
# 检查是否有任何ImGui窗口想要捕获鼠标
|
||||
if hasattr(imgui, 'get_io') and imgui.get_io().want_capture_mouse:
|
||||
return True
|
||||
|
||||
# 检查鼠标是否在任何ImGui窗口内
|
||||
mouse_pos = self.mouseWatcherNode.getMouse()
|
||||
if not mouse_pos:
|
||||
return False
|
||||
|
||||
# 简单的边界检查(可以根据需要扩展)
|
||||
display_size = imgui.get_io().display_size
|
||||
mouse_x = mouse_pos.get_x() * display_size.x / 2 + display_size.x / 2
|
||||
mouse_y = display_size.y - (mouse_pos.get_y() * display_size.y / 2 + display_size.y / 2)
|
||||
|
||||
# 检查是否在常见的ImGui界面区域内
|
||||
# 这里可以根据实际的ImGui窗口位置进行更精确的检测
|
||||
if mouse_x < 300 and mouse_y < 200: # 左上角区域(菜单栏)
|
||||
return True
|
||||
if mouse_x < 300 and mouse_y > display_size.y - 200: # 左下角区域(工具栏)
|
||||
return True
|
||||
if mouse_x > display_size.x - 300 and mouse_y < 200: # 右上角区域
|
||||
return True
|
||||
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"ImGui界面检测失败: {e}")
|
||||
return False
|
||||
|
||||
# ==================== 消息系统 ====================
|
||||
|
||||
def add_message(self, text, color=(1.0, 1.0, 1.0, 1.0)):
|
||||
@ -862,6 +1089,178 @@ class MyWorld(CoreWorld):
|
||||
"""添加信息消息"""
|
||||
self.add_message(f"ℹ {text}", (0.157, 0.620, 1.0, 1.0))
|
||||
|
||||
# ==================== 编辑菜单功能实现 ====================
|
||||
|
||||
def _on_undo(self):
|
||||
"""处理撤销操作"""
|
||||
try:
|
||||
if hasattr(self, 'command_manager') and self.command_manager:
|
||||
if self.command_manager.can_undo():
|
||||
success = self.command_manager.undo()
|
||||
if success:
|
||||
self.add_success_message("撤销操作成功")
|
||||
else:
|
||||
self.add_error_message("撤销操作失败")
|
||||
else:
|
||||
self.add_warning_message("没有可撤销的操作")
|
||||
else:
|
||||
self.add_error_message("命令管理器未初始化")
|
||||
except Exception as e:
|
||||
self.add_error_message(f"撤销操作失败: {e}")
|
||||
|
||||
def _on_redo(self):
|
||||
"""处理重做操作"""
|
||||
try:
|
||||
if hasattr(self, 'command_manager') and self.command_manager:
|
||||
if self.command_manager.can_redo():
|
||||
success = self.command_manager.redo()
|
||||
if success:
|
||||
self.add_success_message("重做操作成功")
|
||||
else:
|
||||
self.add_error_message("重做操作失败")
|
||||
else:
|
||||
self.add_warning_message("没有可重做的操作")
|
||||
else:
|
||||
self.add_error_message("命令管理器未初始化")
|
||||
except Exception as e:
|
||||
self.add_error_message(f"重做操作失败: {e}")
|
||||
|
||||
def _on_copy(self):
|
||||
"""处理复制操作"""
|
||||
try:
|
||||
if not hasattr(self, 'selection') or not self.selection:
|
||||
self.add_error_message("选择系统未初始化")
|
||||
return
|
||||
|
||||
# 获取当前选中的节点
|
||||
selected_node = self.selection.selectedNode
|
||||
if not selected_node:
|
||||
self.add_warning_message("没有选中的节点")
|
||||
return
|
||||
|
||||
# 检查节点有效性(不能复制根节点)
|
||||
if selected_node.getName() == "render":
|
||||
self.add_warning_message("不能复制根节点")
|
||||
return
|
||||
|
||||
# 序列化节点
|
||||
if hasattr(self, 'scene_manager') and self.scene_manager:
|
||||
node_data = self.scene_manager.serializeNodeForCopy(selected_node)
|
||||
if node_data:
|
||||
self.clipboard = [node_data]
|
||||
self.clipboard_mode = "copy"
|
||||
self.add_success_message(f"已复制节点: {selected_node.getName()}")
|
||||
else:
|
||||
self.add_error_message("节点序列化失败")
|
||||
else:
|
||||
self.add_error_message("场景管理器未初始化")
|
||||
except Exception as e:
|
||||
self.add_error_message(f"复制操作失败: {e}")
|
||||
|
||||
def _on_cut(self):
|
||||
"""处理剪切操作"""
|
||||
try:
|
||||
if not hasattr(self, 'selection') or not self.selection:
|
||||
self.add_error_message("选择系统未初始化")
|
||||
return
|
||||
|
||||
# 获取当前选中的节点
|
||||
selected_node = self.selection.selectedNode
|
||||
if not selected_node:
|
||||
self.add_warning_message("没有选中的节点")
|
||||
return
|
||||
|
||||
# 检查节点有效性(不能剪切根节点和系统节点)
|
||||
node_name = selected_node.getName()
|
||||
if node_name == "render":
|
||||
self.add_warning_message("不能剪切根节点")
|
||||
return
|
||||
|
||||
# 序列化节点
|
||||
if hasattr(self, 'scene_manager') and self.scene_manager:
|
||||
node_data = self.scene_manager.serializeNodeForCopy(selected_node)
|
||||
if node_data:
|
||||
self.clipboard = [node_data]
|
||||
self.clipboard_mode = "cut"
|
||||
|
||||
# 删除原节点
|
||||
self.scene_manager.deleteNode(selected_node)
|
||||
self.selection.clearSelection()
|
||||
|
||||
self.add_success_message(f"已剪切节点: {node_name}")
|
||||
else:
|
||||
self.add_error_message("节点序列化失败")
|
||||
else:
|
||||
self.add_error_message("场景管理器未初始化")
|
||||
except Exception as e:
|
||||
self.add_error_message(f"剪切操作失败: {e}")
|
||||
|
||||
def _on_paste(self):
|
||||
"""处理粘贴操作"""
|
||||
try:
|
||||
if not self.clipboard:
|
||||
self.add_warning_message("剪切板为空")
|
||||
return
|
||||
|
||||
if not hasattr(self, 'scene_manager') or not self.scene_manager:
|
||||
self.add_error_message("场景管理器未初始化")
|
||||
return
|
||||
|
||||
# 确定粘贴目标父节点
|
||||
parent_node = None
|
||||
if hasattr(self, 'selection') and self.selection:
|
||||
selected_node = self.selection.selectedNode
|
||||
if selected_node:
|
||||
parent_node = selected_node
|
||||
|
||||
# 如果没有选中节点,使用渲染根节点
|
||||
if not parent_node:
|
||||
parent_node = self.render
|
||||
|
||||
# 反序列化并添加节点
|
||||
for node_data in self.clipboard:
|
||||
new_node = self.scene_manager.deserializeNode(node_data, parent_node)
|
||||
if new_node:
|
||||
self.add_success_message(f"已粘贴节点: {new_node.getName()}")
|
||||
|
||||
# 如果是剪切模式,清空剪切板
|
||||
if self.clipboard_mode == "cut":
|
||||
self.clipboard = []
|
||||
self.clipboard_mode = ""
|
||||
else:
|
||||
self.add_error_message("节点反序列化失败")
|
||||
except Exception as e:
|
||||
self.add_error_message(f"粘贴操作失败: {e}")
|
||||
|
||||
def _on_delete(self):
|
||||
"""处理删除操作"""
|
||||
try:
|
||||
if not hasattr(self, 'selection') or not self.selection:
|
||||
self.add_error_message("选择系统未初始化")
|
||||
return
|
||||
|
||||
# 获取当前选中的节点
|
||||
selected_node = self.selection.selectedNode
|
||||
if not selected_node:
|
||||
self.add_warning_message("没有选中的节点")
|
||||
return
|
||||
|
||||
# 检查节点有效性(不能删除根节点)
|
||||
node_name = selected_node.getName()
|
||||
if node_name == "render":
|
||||
self.add_warning_message("不能删除根节点")
|
||||
return
|
||||
|
||||
# 删除节点
|
||||
if hasattr(self, 'scene_manager') and self.scene_manager:
|
||||
self.scene_manager.deleteNode(selected_node)
|
||||
self.selection.clearSelection()
|
||||
self.add_success_message(f"已删除节点: {node_name}")
|
||||
else:
|
||||
self.add_error_message("场景管理器未初始化")
|
||||
except Exception as e:
|
||||
self.add_error_message(f"删除操作失败: {e}")
|
||||
|
||||
# ==================== 对话框绘制函数 ====================
|
||||
|
||||
def _draw_new_project_dialog(self):
|
||||
@ -1054,24 +1453,41 @@ class MyWorld(CoreWorld):
|
||||
self.path_browser_current_path = item['path']
|
||||
self._refresh_path_browser()
|
||||
|
||||
# 显示文件(仅在打开项目模式下显示.json文件)
|
||||
# 显示文件(根据模式显示不同类型的文件)
|
||||
if self.path_browser_mode == "open_project":
|
||||
for item in self.path_browser_items:
|
||||
if not item['is_dir'] and item['name'].endswith('.json'):
|
||||
# 尝试使用图标或文本标识文件
|
||||
if self.icons.get('success_icon'): # 使用成功图标作为文件图标
|
||||
imgui.image(self.icons['success_icon'], (16, 16))
|
||||
imgui.same_line()
|
||||
else:
|
||||
imgui.text_colored((1.0, 0.8, 0.4, 1.0), "-")
|
||||
imgui.same_line()
|
||||
|
||||
imgui.text_colored((1.0, 1.0, 0.7, 1.0), "[FILE]")
|
||||
imgui.same_line()
|
||||
if imgui.selectable(item['name'], False)[0]:
|
||||
self.path_browser_selected_path = item['path']
|
||||
if imgui.is_item_hovered() and imgui.is_mouse_double_clicked(0):
|
||||
# 选择包含project.json的目录
|
||||
self.path_browser_current_path = os.path.dirname(item['path'])
|
||||
self._apply_selected_path()
|
||||
elif self.path_browser_mode == "import_model":
|
||||
for item in self.path_browser_items:
|
||||
if not item['is_dir']:
|
||||
file_ext = os.path.splitext(item['name'])[1].lower()
|
||||
# 根据文件类型显示不同颜色
|
||||
if file_ext in ['.gltf', '.glb']:
|
||||
color = (0.7, 1.0, 0.7, 1.0) # 绿色 - glTF
|
||||
elif file_ext == '.fbx':
|
||||
color = (1.0, 0.7, 0.7, 1.0) # 红色 - FBX
|
||||
elif file_ext in ['.bam', '.egg']:
|
||||
color = (0.7, 0.7, 1.0, 1.0) # 蓝色 - Panda3D
|
||||
elif file_ext == '.obj':
|
||||
color = (1.0, 1.0, 0.7, 1.0) # 黄色 - OBJ
|
||||
else:
|
||||
color = (0.8, 0.8, 0.8, 1.0) # 灰色 - 其他
|
||||
|
||||
imgui.text_colored(color, f"[{file_ext[1:].upper()}]")
|
||||
imgui.same_line()
|
||||
if imgui.selectable(item['name'], False)[0]:
|
||||
self.path_browser_selected_path = item['path']
|
||||
if imgui.is_item_hovered() and imgui.is_mouse_double_clicked(0):
|
||||
self.path_browser_selected_path = item['path']
|
||||
self._apply_selected_path()
|
||||
|
||||
imgui.separator()
|
||||
|
||||
@ -1090,6 +1506,90 @@ class MyWorld(CoreWorld):
|
||||
if imgui.button("取消"):
|
||||
self.show_path_browser = False
|
||||
|
||||
def _draw_import_dialog(self):
|
||||
"""绘制导入模型对话框"""
|
||||
if not self.show_import_dialog:
|
||||
return
|
||||
|
||||
# 设置对话框标志
|
||||
flags = (imgui.WindowFlags_.no_resize |
|
||||
imgui.WindowFlags_.no_collapse |
|
||||
imgui.WindowFlags_.modal)
|
||||
|
||||
# 获取屏幕尺寸,居中显示对话框
|
||||
display_size = imgui.get_io().display_size
|
||||
dialog_width = 600
|
||||
dialog_height = 500
|
||||
imgui.set_next_window_size((dialog_width, dialog_height))
|
||||
imgui.set_next_window_pos(
|
||||
((display_size.x - dialog_width) / 2, (display_size.y - dialog_height) / 2)
|
||||
)
|
||||
|
||||
with imgui_ctx.begin("导入模型", True, flags) as window:
|
||||
if not window.opened:
|
||||
self.show_import_dialog = False
|
||||
return
|
||||
|
||||
imgui.text("选择要导入的模型文件")
|
||||
imgui.separator()
|
||||
|
||||
# 文件路径输入
|
||||
imgui.text("文件路径:")
|
||||
changed, file_path = imgui.input_text("##import_file_path", self.import_file_path, 512)
|
||||
if changed:
|
||||
self.import_file_path = file_path
|
||||
|
||||
imgui.same_line()
|
||||
if imgui.button("浏览..."):
|
||||
self.path_browser_mode = "import_model"
|
||||
self.path_browser_current_path = os.path.dirname(self.import_file_path) if self.import_file_path else os.getcwd()
|
||||
self.show_path_browser = True
|
||||
self._refresh_path_browser()
|
||||
|
||||
imgui.separator()
|
||||
|
||||
# 支持的格式说明
|
||||
imgui.text("支持的文件格式:")
|
||||
formats_text = ", ".join(self.supported_formats)
|
||||
imgui.text_colored((0.7, 0.7, 0.7, 1.0), formats_text)
|
||||
|
||||
imgui.separator()
|
||||
|
||||
# 文件预览信息
|
||||
if self.import_file_path and os.path.exists(self.import_file_path):
|
||||
file_size = os.path.getsize(self.import_file_path)
|
||||
imgui.text(f"文件大小: {file_size / 1024:.2f} KB")
|
||||
|
||||
file_ext = os.path.splitext(self.import_file_path)[1].lower()
|
||||
if file_ext in self.supported_formats:
|
||||
imgui.text_colored((0.176, 1.0, 0.769, 1.0), "✓ 文件格式支持")
|
||||
else:
|
||||
imgui.text_colored((1.0, 0.3, 0.3, 1.0), "✗ 不支持的文件格式")
|
||||
else:
|
||||
imgui.text_colored((0.7, 0.7, 0.7, 1.0), "请选择有效的文件路径")
|
||||
|
||||
imgui.separator()
|
||||
|
||||
# 按钮区域
|
||||
can_import = (self.import_file_path and
|
||||
os.path.exists(self.import_file_path) and
|
||||
os.path.splitext(self.import_file_path)[1].lower() in self.supported_formats)
|
||||
|
||||
# 根据状态设置按钮颜色
|
||||
if can_import:
|
||||
if imgui.button("导入"):
|
||||
self._import_model()
|
||||
self.show_import_dialog = False
|
||||
else:
|
||||
# 禁用状态的按钮(灰色显示)
|
||||
imgui.push_style_color(imgui.Col_.button, (0.3, 0.3, 0.3, 1.0))
|
||||
imgui.button("导入")
|
||||
imgui.pop_style_color()
|
||||
|
||||
imgui.same_line()
|
||||
if imgui.button("取消"):
|
||||
self.show_import_dialog = False
|
||||
|
||||
def _create_new_project(self, name, path):
|
||||
"""创建新项目的实际实现"""
|
||||
if not hasattr(self, 'project_manager') or not self.project_manager:
|
||||
@ -1287,6 +1787,19 @@ class MyWorld(CoreWorld):
|
||||
self.add_error_message(f"无法访问路径: {self.path_browser_current_path}")
|
||||
return
|
||||
|
||||
# 根据模式过滤文件
|
||||
if self.path_browser_mode == "import_model":
|
||||
# 只显示支持的模型文件
|
||||
filtered_items = []
|
||||
for item in items:
|
||||
if item['is_dir']:
|
||||
filtered_items.append(item)
|
||||
else:
|
||||
file_ext = os.path.splitext(item['name'])[1].lower()
|
||||
if file_ext in self.supported_formats:
|
||||
filtered_items.append(item)
|
||||
items = filtered_items
|
||||
|
||||
# 排序:目录在前,文件在后,按名称排序
|
||||
items.sort(key=lambda x: (not x['is_dir'], x['name'].lower()))
|
||||
self.path_browser_items = items
|
||||
@ -1305,8 +1818,253 @@ class MyWorld(CoreWorld):
|
||||
# 打开项目模式:使用当前路径
|
||||
self.open_project_path = self.path_browser_current_path
|
||||
self.add_info_message(f"已选择项目路径: {self.open_project_path}")
|
||||
elif self.path_browser_mode == "import_model":
|
||||
# 导入模型模式:使用选择的文件路径
|
||||
self.import_file_path = self.path_browser_selected_path
|
||||
self.add_info_message(f"已选择文件: {self.import_file_path}")
|
||||
except Exception as e:
|
||||
self.add_error_message(f"应用路径失败: {e}")
|
||||
|
||||
# ==================== 导入功能实现 ====================
|
||||
|
||||
def _on_import_model(self):
|
||||
"""处理导入模型菜单项"""
|
||||
self.add_info_message("打开导入模型对话框")
|
||||
self.show_import_dialog = True
|
||||
|
||||
def _import_model(self):
|
||||
"""导入模型的具体实现"""
|
||||
try:
|
||||
if not self.import_file_path:
|
||||
self.add_error_message("请选择要导入的文件")
|
||||
return
|
||||
|
||||
if not os.path.exists(self.import_file_path):
|
||||
self.add_error_message(f"文件不存在: {self.import_file_path}")
|
||||
return
|
||||
|
||||
# 检查文件格式
|
||||
file_ext = os.path.splitext(self.import_file_path)[1].lower()
|
||||
if file_ext not in self.supported_formats:
|
||||
self.add_error_message(f"不支持的文件格式: {file_ext}")
|
||||
return
|
||||
|
||||
# 调用场景管理器导入模型
|
||||
if hasattr(self, 'scene_manager') and self.scene_manager:
|
||||
self.add_info_message(f"正在导入模型: {os.path.basename(self.import_file_path)}")
|
||||
|
||||
# 导入模型
|
||||
model_node = self.scene_manager.importModel(self.import_file_path)
|
||||
|
||||
if model_node:
|
||||
# 添加材质处理确保颜色正常
|
||||
if hasattr(self.scene_manager, 'processMaterials'):
|
||||
self.scene_manager.processMaterials(model_node)
|
||||
self.add_info_message("已应用默认材质")
|
||||
|
||||
# 额外的材质处理,确保颜色正确显示
|
||||
try:
|
||||
# 强制刷新模型显示
|
||||
model_node.clearMaterial()
|
||||
model_node.clearTexture()
|
||||
|
||||
# 重新应用材质
|
||||
if hasattr(self.scene_manager, 'processMaterials'):
|
||||
self.scene_manager.processMaterials(model_node)
|
||||
|
||||
# 设置默认的基础颜色(如果模型没有颜色)
|
||||
if model_node.getColor() == (1, 1, 1, 1): # 默认白色
|
||||
model_node.setColor(0.8, 0.8, 0.8, 1.0) # 设置为中性灰
|
||||
|
||||
except Exception as e:
|
||||
self.add_warning_message(f"材质处理警告: {e}")
|
||||
|
||||
# 设置模型位置
|
||||
model_node.setPos(0, 0, 0)
|
||||
|
||||
# 添加到场景管理器的模型列表
|
||||
if hasattr(self.scene_manager, 'models'):
|
||||
self.scene_manager.models.append(model_node)
|
||||
|
||||
# 选中新导入的模型
|
||||
if hasattr(self, 'selection') and self.selection:
|
||||
self.selection.selectNode(model_node)
|
||||
|
||||
self.add_success_message(f"模型导入成功: {os.path.basename(self.import_file_path)}")
|
||||
else:
|
||||
self.add_error_message("模型导入失败")
|
||||
else:
|
||||
self.add_error_message("场景管理器未初始化")
|
||||
|
||||
except Exception as e:
|
||||
self.add_error_message(f"导入模型失败: {e}")
|
||||
|
||||
# 清空导入路径
|
||||
self.import_file_path = ""
|
||||
|
||||
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}")
|
||||
|
||||
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}")
|
||||
|
||||
def clear_dragged_files(self):
|
||||
"""清空拖拽文件列表"""
|
||||
self.dragged_files.clear()
|
||||
self.is_dragging = False
|
||||
self.show_drag_overlay = False
|
||||
|
||||
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()
|
||||
|
||||
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.scene_manager.importModel(file_path)
|
||||
|
||||
if model_node:
|
||||
# 应用材质确保颜色正常
|
||||
self.scene_manager.processMaterials(model_node)
|
||||
|
||||
# 设置模型位置
|
||||
model_node.setPos(0, 0, 0)
|
||||
|
||||
# 添加到选择系统
|
||||
self.selection.select_node(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
|
||||
|
||||
def _draw_drag_drop_interface(self):
|
||||
"""绘制拖拽界面"""
|
||||
# 绘制拖拽覆盖层
|
||||
if self.show_drag_overlay:
|
||||
self._draw_drag_overlay()
|
||||
|
||||
# 检查是否有拖拽的文件需要处理
|
||||
if self.is_dragging and self.dragged_files:
|
||||
# 显示拖拽状态
|
||||
self._draw_drag_status()
|
||||
|
||||
def _draw_drag_overlay(self):
|
||||
"""绘制拖拽覆盖层"""
|
||||
viewport = imgui.get_main_viewport()
|
||||
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):
|
||||
"""绘制拖拽状态"""
|
||||
viewport = imgui.get_main_viewport()
|
||||
|
||||
# 在右下角显示拖拽状态
|
||||
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()
|
||||
|
||||
demo = MyWorld()
|
||||
demo.run()
|
||||
|
||||
17
imgui.ini
17
imgui.ini
@ -31,7 +31,7 @@ DockId=0x00000007,0
|
||||
|
||||
[Window][场景树]
|
||||
Pos=0,20
|
||||
Size=285,861
|
||||
Size=285,883
|
||||
Collapsed=0
|
||||
DockId=0x00000001,0
|
||||
|
||||
@ -42,8 +42,8 @@ Collapsed=0
|
||||
DockId=0x00000005,0
|
||||
|
||||
[Window][控制台]
|
||||
Pos=0,883
|
||||
Size=1524,133
|
||||
Pos=0,905
|
||||
Size=1524,111
|
||||
Collapsed=0
|
||||
DockId=0x0000000A,0
|
||||
|
||||
@ -93,15 +93,20 @@ Pos=675,308
|
||||
Size=500,400
|
||||
Collapsed=0
|
||||
|
||||
[Window][导入模型]
|
||||
Pos=625,258
|
||||
Size=600,500
|
||||
Collapsed=0
|
||||
|
||||
[Docking][Data]
|
||||
DockSpace ID=0x08BD597D Window=0x1BBC0F80 Pos=0,20 Size=1850,996 Split=X
|
||||
DockNode ID=0x00000003 Parent=0x08BD597D SizeRef=1524,996 Split=Y
|
||||
DockNode ID=0x00000009 Parent=0x00000003 SizeRef=1380,861 Split=X
|
||||
DockNode ID=0x00000009 Parent=0x00000003 SizeRef=1380,883 Split=X
|
||||
DockNode ID=0x00000001 Parent=0x00000009 SizeRef=285,730 HiddenTabBar=1 Selected=0xE0015051
|
||||
DockNode ID=0x00000002 Parent=0x00000009 SizeRef=1237,730 Split=Y
|
||||
DockNode ID=0x00000007 Parent=0x00000002 SizeRef=1380,32 HiddenTabBar=1 Selected=0x43A39006
|
||||
DockNode ID=0x00000008 Parent=0x00000002 SizeRef=1380,827 CentralNode=1 Selected=0x5E5F7166
|
||||
DockNode ID=0x0000000A Parent=0x00000003 SizeRef=1380,133 HiddenTabBar=1 Selected=0x5428E753
|
||||
DockNode ID=0x00000008 Parent=0x00000002 SizeRef=1380,849 CentralNode=1 Selected=0x5E5F7166
|
||||
DockNode ID=0x0000000A Parent=0x00000003 SizeRef=1380,111 HiddenTabBar=1 Selected=0x5428E753
|
||||
DockNode ID=0x00000004 Parent=0x08BD597D SizeRef=324,996 Split=Y Selected=0x5DB6FF37
|
||||
DockNode ID=0x00000005 Parent=0x00000004 SizeRef=304,498 HiddenTabBar=1 Selected=0x5DB6FF37
|
||||
DockNode ID=0x00000006 Parent=0x00000004 SizeRef=304,496 HiddenTabBar=1 Selected=0x3188AB8D
|
||||
|
||||
@ -1,354 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
完整的开源率分析脚本
|
||||
该脚本将执行完整的测试流程并生成准确报告
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
def run_command(command, ignore_failure=False):
|
||||
"""运行命令并返回结果"""
|
||||
try:
|
||||
print(f"执行命令: {command}")
|
||||
result = subprocess.run(command, shell=True, capture_output=True, text=True)
|
||||
if result.returncode != 0 and not ignore_failure:
|
||||
print(f"命令执行失败: {result.stderr}")
|
||||
return False
|
||||
print("命令执行成功")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"执行命令时出错: {e}")
|
||||
return False
|
||||
|
||||
def load_cloc_data(path="cloc.json"):
|
||||
"""加载并解析cloc统计数据"""
|
||||
if not os.path.exists(path):
|
||||
print(f"❌ 未找到 {path} 文件")
|
||||
return None
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
|
||||
def load_scancode_data(path="summary.json"):
|
||||
"""加载并解析ScanCode统计数据"""
|
||||
if not os.path.exists(path):
|
||||
print(f"❌ 未找到 {path} 文件")
|
||||
return None
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
|
||||
def get_licensed_files_details(scancode_data):
|
||||
"""获取含许可证文件的详细信息"""
|
||||
if not scancode_data:
|
||||
return []
|
||||
|
||||
files = scancode_data.get("files", [])
|
||||
licensed_files = []
|
||||
|
||||
# 定义需要排除的目录和文件模式
|
||||
exclude_patterns = [
|
||||
"/venv/",
|
||||
"/.git/",
|
||||
"/__pycache__/",
|
||||
"/.idea/",
|
||||
"/.vscode/",
|
||||
"/build/",
|
||||
"/dist/",
|
||||
".egg-info",
|
||||
"/Resources/",
|
||||
"/icons/",
|
||||
"/tex/",
|
||||
"cloc.json",
|
||||
"detailed_cloc.txt",
|
||||
"summary.json",
|
||||
"完整开源率分析报告.txt",
|
||||
"run_complete_analysis.py",
|
||||
]
|
||||
|
||||
for file in files:
|
||||
# 获取文件路径
|
||||
file_path = file.get("path", "")
|
||||
|
||||
# 只处理类型为"file"的条目
|
||||
if file.get("type") != "file":
|
||||
continue
|
||||
|
||||
# 检查是否应该排除该文件
|
||||
should_exclude = False
|
||||
for pattern in exclude_patterns:
|
||||
if pattern in file_path:
|
||||
should_exclude = True
|
||||
break
|
||||
|
||||
# 如果应该排除,则跳过该文件
|
||||
if should_exclude:
|
||||
continue
|
||||
|
||||
# 检查是否有许可证信息
|
||||
if file.get("detected_license_expression") or file.get("license_detections"):
|
||||
licensed_files.append({
|
||||
"path": file_path,
|
||||
"license": file.get("detected_license_expression", "Unknown"),
|
||||
"detections": file.get("license_detections", [])
|
||||
})
|
||||
|
||||
return licensed_files
|
||||
|
||||
def get_file_code_lines():
|
||||
"""从detailed_cloc.txt获取文件代码行数"""
|
||||
file_code_lines = {}
|
||||
|
||||
if not os.path.exists("detailed_cloc.txt"):
|
||||
print("❌ 未找到 detailed_cloc.txt 文件")
|
||||
return file_code_lines
|
||||
|
||||
with open("detailed_cloc.txt", "r") as f:
|
||||
cloc_lines = f.readlines()
|
||||
|
||||
# 解析cloc输出,创建文件路径到代码行数的映射
|
||||
for line in cloc_lines[3:]: # 跳过标题行
|
||||
parts = line.strip().split()
|
||||
if len(parts) >= 4:
|
||||
try:
|
||||
file_path = parts[0]
|
||||
code_lines = int(parts[-1])
|
||||
# 标准化路径格式
|
||||
if file_path.startswith('./'):
|
||||
file_path = file_path[2:]
|
||||
file_code_lines[file_path] = code_lines
|
||||
except ValueError:
|
||||
continue
|
||||
|
||||
return file_code_lines
|
||||
|
||||
def calculate_accurate_open_source_lines(licensed_files_details, file_code_lines):
|
||||
"""计算准确的开源代码行数"""
|
||||
total_licensed_code_lines = 0
|
||||
found_files = 0
|
||||
detailed_files = []
|
||||
|
||||
for file_info in licensed_files_details:
|
||||
file_path = file_info["path"]
|
||||
|
||||
# 将文件路径标准化
|
||||
normalized_path = file_path
|
||||
if normalized_path.startswith('EG/'):
|
||||
normalized_path = normalized_path[3:] # 去掉开头的EG/
|
||||
|
||||
if normalized_path in file_code_lines:
|
||||
code_lines = file_code_lines[normalized_path]
|
||||
total_licensed_code_lines += code_lines
|
||||
found_files += 1
|
||||
detailed_files.append({
|
||||
"path": file_path,
|
||||
"code_lines": code_lines,
|
||||
"license": file_info["license"]
|
||||
})
|
||||
else:
|
||||
# 尝试其他可能的路径格式
|
||||
alt_path1 = './' + normalized_path
|
||||
alt_path2 = 'EG/' + normalized_path
|
||||
if alt_path1 in file_code_lines:
|
||||
code_lines = file_code_lines[alt_path1]
|
||||
total_licensed_code_lines += code_lines
|
||||
found_files += 1
|
||||
detailed_files.append({
|
||||
"path": file_path,
|
||||
"code_lines": code_lines,
|
||||
"license": file_info["license"]
|
||||
})
|
||||
elif alt_path2 in file_code_lines:
|
||||
code_lines = file_code_lines[alt_path2]
|
||||
total_licensed_code_lines += code_lines
|
||||
found_files += 1
|
||||
detailed_files.append({
|
||||
"path": file_path,
|
||||
"code_lines": code_lines,
|
||||
"license": file_info["license"]
|
||||
})
|
||||
|
||||
return total_licensed_code_lines, detailed_files
|
||||
|
||||
def generate_detailed_report():
|
||||
"""生成详细报告"""
|
||||
# 加载数据
|
||||
cloc_data = load_cloc_data("cloc.json")
|
||||
scancode_data = load_scancode_data("summary.json")
|
||||
file_code_lines = get_file_code_lines()
|
||||
|
||||
if not cloc_data or not scancode_data:
|
||||
print("无法加载必要数据文件")
|
||||
return False
|
||||
|
||||
# 获取含许可证文件详情
|
||||
licensed_files_details = get_licensed_files_details(scancode_data)
|
||||
|
||||
# 计算准确的开源代码行数
|
||||
accurate_open_source_lines, detailed_files = calculate_accurate_open_source_lines(
|
||||
licensed_files_details, file_code_lines)
|
||||
|
||||
# 获取统计数据
|
||||
total_code_lines = cloc_data.get("SUM", {}).get("code", 0)
|
||||
total_files = 1075 # 根据脚本分析得出的实际文件数
|
||||
licensed_files = len(licensed_files_details)
|
||||
|
||||
# 计算开源率
|
||||
open_source_rate = (accurate_open_source_lines / total_code_lines) * 100 if total_code_lines > 0 else 0
|
||||
|
||||
# 创建报告内容
|
||||
report_content = []
|
||||
report_content.append("项目开源率分析完整报告")
|
||||
report_content.append("=" * 50)
|
||||
report_content.append("")
|
||||
|
||||
report_content.append("1. 报告概览")
|
||||
report_content.append("-" * 20)
|
||||
report_content.append(f"项目总文件数: {total_files}")
|
||||
report_content.append(f"含许可证文件数: {licensed_files}")
|
||||
report_content.append(f"项目总代码行数: {total_code_lines}")
|
||||
report_content.append(f"准确开源代码行数: {accurate_open_source_lines}")
|
||||
report_content.append(f"代码开源率: {open_source_rate:.2f}%")
|
||||
report_content.append("")
|
||||
|
||||
report_content.append("2. 各语言代码行数分布(包含文件路径)")
|
||||
report_content.append("-" * 40)
|
||||
|
||||
# 按语言分组显示文件
|
||||
lang_files = {}
|
||||
with open("detailed_cloc.txt", "r") as f:
|
||||
cloc_lines = f.readlines()
|
||||
|
||||
for line in cloc_lines[3:]: # 跳过标题行
|
||||
parts = line.strip().split()
|
||||
if len(parts) >= 4:
|
||||
try:
|
||||
file_path = parts[0]
|
||||
# 从文件路径推断语言(简化处理)
|
||||
if file_path.endswith('.py'):
|
||||
lang = 'Python'
|
||||
elif file_path.endswith('.js'):
|
||||
lang = 'JavaScript'
|
||||
elif file_path.endswith('.cpp') or file_path.endswith('.cc'):
|
||||
lang = 'C++'
|
||||
elif file_path.endswith('.h'):
|
||||
lang = 'C/C++ Header'
|
||||
elif file_path.endswith('.glsl'):
|
||||
lang = 'GLSL'
|
||||
elif file_path.endswith('.qml'):
|
||||
lang = 'QML'
|
||||
elif file_path.endswith('.xml'):
|
||||
lang = 'XML'
|
||||
elif file_path.endswith('.json'):
|
||||
lang = 'JSON'
|
||||
elif file_path.endswith('.md'):
|
||||
lang = 'Markdown'
|
||||
elif file_path.endswith('.html'):
|
||||
lang = 'HTML'
|
||||
elif file_path.endswith('.css'):
|
||||
lang = 'CSS'
|
||||
elif file_path.endswith('.sh'):
|
||||
lang = 'Shell'
|
||||
elif file_path.endswith('.yml') or file_path.endswith('.yaml'):
|
||||
lang = 'YAML'
|
||||
else:
|
||||
lang = 'Other'
|
||||
|
||||
if lang not in lang_files:
|
||||
lang_files[lang] = []
|
||||
lang_files[lang].append((file_path, int(parts[-1])))
|
||||
except ValueError:
|
||||
continue
|
||||
|
||||
for lang, files in lang_files.items():
|
||||
report_content.append(f"\n{lang}语言文件:")
|
||||
report_content.append(f" 文件总数: {len(files)}")
|
||||
total_lines = sum([f[1] for f in files])
|
||||
report_content.append(f" 代码行数: {total_lines}")
|
||||
report_content.append(" 文件列表:")
|
||||
for file_path, code_lines in files[:10]: # 只显示前10个文件
|
||||
report_content.append(f" {file_path}: {code_lines} 行")
|
||||
if len(files) > 10:
|
||||
report_content.append(f" ... 还有 {len(files) - 10} 个文件")
|
||||
|
||||
report_content.append("")
|
||||
report_content.append("3. 含许可证的开源文件详情")
|
||||
report_content.append("-" * 30)
|
||||
|
||||
# 按许可证类型分组显示文件
|
||||
files_by_license = {}
|
||||
for file_info in detailed_files:
|
||||
license_type = file_info.get("license", "Unknown")
|
||||
if license_type not in files_by_license:
|
||||
files_by_license[license_type] = []
|
||||
files_by_license[license_type].append(file_info)
|
||||
|
||||
for license_type, files in files_by_license.items():
|
||||
report_content.append(f"\n许可证类型: {license_type}")
|
||||
report_content.append(f" 文件数量: {len(files)}")
|
||||
total_lines = sum([f["code_lines"] for f in files])
|
||||
report_content.append(f" 代码行数: {total_lines}")
|
||||
report_content.append(" 文件列表:")
|
||||
for file_info in files:
|
||||
report_content.append(f" {file_info['path']}: {file_info['code_lines']} 行")
|
||||
|
||||
# 保存报告
|
||||
with open("完整开源率分析报告.txt", "w", encoding="utf-8") as f:
|
||||
f.write("\n".join(report_content))
|
||||
|
||||
print("完整报告已生成:完整开源率分析报告.txt")
|
||||
return True
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
print("开始执行完整的开源率分析流程...")
|
||||
|
||||
# 步骤1: 执行cloc统计代码行数
|
||||
print("\n步骤1: 执行cloc统计代码行数")
|
||||
cloc_cmd = "cloc --json --fullpath --not-match-d='(venv|\\.git|__pycache__|\\.idea|\\.vscode|build|dist|.*\\.egg-info|Resources/animations|Resources/materials|Resources/models|Resources/textures|icons|tex)' --not-match-f='(cloc.json|detailed_cloc.txt|summary.json|完整开源率分析报告.txt|run_complete_analysis.py)' . > cloc.json"
|
||||
if not run_command(cloc_cmd):
|
||||
print("❌ cloc统计失败")
|
||||
return False
|
||||
|
||||
# 步骤2: 生成详细文件列表
|
||||
print("\n步骤2: 生成详细文件列表")
|
||||
detailed_cloc_cmd = "cloc --by-file --fullpath --not-match-d='(venv|\\.git|__pycache__|\\.idea|\\.vscode|build|dist|.*\\.egg-info|Resources/animations|Resources/materials|Resources/models|Resources/textures|icons|tex)' --not-match-f='(cloc.json|detailed_cloc.txt|summary.json|完整开源率分析报告.txt|run_complete_analysis.py)' . | grep -v \"^\\s*$\" | grep -E \"(\\.py|\\.js|\\.cpp|\\.h|\\.glsl|\\.qml|\\.xml|\\.html|\\.css|\\.java|\\.cs|\\.php)\" > detailed_cloc.txt"
|
||||
if not run_command(detailed_cloc_cmd):
|
||||
print("❌ 生成详细文件列表失败")
|
||||
return False
|
||||
|
||||
# 步骤3: 执行ScanCode扫描许可证
|
||||
print("\n步骤3: 执行ScanCode扫描许可证")
|
||||
scancode_cmd = "scancode --license --classify --summary --json-pp summary.json . --ignore \"venv\" --ignore \".git\" --ignore \"__pycache__\" --ignore \".idea\" --ignore \".vscode\" --ignore \"build\" --ignore \"dist\" --ignore \"*.egg-info\" --ignore \"Resources\" --ignore \"icons\" --ignore \"tex\" --ignore \"cloc.json\" --ignore \"detailed_cloc.txt\" --ignore \"完整开源率分析报告.txt\" --ignore \"run_complete_analysis.py\""
|
||||
# 忽略失败,因为ScanCode会尝试扫描自己生成的summary.json文件导致"失败"
|
||||
run_command(scancode_cmd, ignore_failure=True)
|
||||
|
||||
# 检查summary.json是否生成
|
||||
if not os.path.exists("summary.json"):
|
||||
print("❌ ScanCode未生成summary.json文件")
|
||||
return False
|
||||
|
||||
# 步骤4: 生成详细报告
|
||||
print("\n步骤4: 生成详细报告")
|
||||
if not generate_detailed_report():
|
||||
print("❌ 生成报告失败")
|
||||
return False
|
||||
|
||||
print("\n✅ 完整分析流程执行完成!")
|
||||
print("生成的文件:")
|
||||
print(" - cloc.json: 代码行数统计")
|
||||
print(" - detailed_cloc.txt: 详细文件列表")
|
||||
print(" - summary.json: 许可证扫描结果")
|
||||
print(" - 完整开源率分析报告.txt: 最终报告")
|
||||
return True
|
||||
|
||||
if __name__ == "__main__":
|
||||
success = main()
|
||||
if success:
|
||||
print("\n🎉 所有步骤执行成功!")
|
||||
sys.exit(0)
|
||||
else:
|
||||
print("\n❌ 执行过程中出现错误!")
|
||||
sys.exit(1)
|
||||
238
simple_drag_drop.py
Normal file
238
simple_drag_drop.py
Normal file
@ -0,0 +1,238 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
简单的拖拽导入工具
|
||||
直接运行此工具,然后将3D文件拖拽到窗口中
|
||||
工具会生成可在demo.py中使用的导入命令
|
||||
"""
|
||||
|
||||
import tkinter as tk
|
||||
from tkinter import filedialog, messagebox, scrolledtext
|
||||
import os
|
||||
import sys
|
||||
|
||||
class SimpleDragDrop:
|
||||
def __init__(self):
|
||||
self.root = tk.Tk()
|
||||
self.root.title("3D模型拖拽导入工具")
|
||||
self.root.geometry("600x400")
|
||||
|
||||
# 设置窗口始终在最前面
|
||||
self.root.attributes('-topmost', True)
|
||||
|
||||
# 支持的文件格式
|
||||
self.supported_formats = ['.gltf', '.glb', '.fbx', '.bam', '.egg', '.obj']
|
||||
|
||||
self.setup_ui()
|
||||
self.setup_drag_drop()
|
||||
|
||||
def setup_ui(self):
|
||||
"""设置用户界面"""
|
||||
# 主框架
|
||||
main_frame = tk.Frame(self.root)
|
||||
main_frame.pack(fill='both', expand=True, padx=10, pady=10)
|
||||
|
||||
# 标题
|
||||
title_label = tk.Label(
|
||||
main_frame,
|
||||
text="将3D模型文件拖拽到此处",
|
||||
font=('Arial', 16, 'bold')
|
||||
)
|
||||
title_label.pack(pady=(0, 10))
|
||||
|
||||
# 拖拽区域
|
||||
self.drop_area = tk.Frame(
|
||||
main_frame,
|
||||
bg='lightgray',
|
||||
relief='sunken',
|
||||
bd=2,
|
||||
height=150
|
||||
)
|
||||
self.drop_area.pack(fill='both', expand=True, pady=(0, 10))
|
||||
|
||||
# 提示文本
|
||||
hint_label = tk.Label(
|
||||
self.drop_area,
|
||||
text="支持格式: .gltf, .glb, .fbx, .bam, .egg, .obj\n\n拖拽文件到此处或点击下方按钮选择文件",
|
||||
bg='lightgray',
|
||||
font=('Arial', 11)
|
||||
)
|
||||
hint_label.pack(expand=True)
|
||||
|
||||
# 按钮框架
|
||||
button_frame = tk.Frame(main_frame)
|
||||
button_frame.pack(fill='x', pady=(0, 10))
|
||||
|
||||
# 选择文件按钮
|
||||
select_btn = tk.Button(
|
||||
button_frame,
|
||||
text="选择文件",
|
||||
command=self.select_files,
|
||||
width=15
|
||||
)
|
||||
select_btn.pack(side='left', padx=5)
|
||||
|
||||
# 清空按钮
|
||||
clear_btn = tk.Button(
|
||||
button_frame,
|
||||
text="清空",
|
||||
command=self.clear_files,
|
||||
width=15
|
||||
)
|
||||
clear_btn.pack(side='left', padx=5)
|
||||
|
||||
# 生成命令按钮
|
||||
generate_btn = tk.Button(
|
||||
button_frame,
|
||||
text="生成导入命令",
|
||||
command=self.generate_commands,
|
||||
bg='lightblue',
|
||||
font=('Arial', 10, 'bold'),
|
||||
width=15
|
||||
)
|
||||
generate_btn.pack(side='right', padx=5)
|
||||
|
||||
# 文件列表
|
||||
list_label = tk.Label(
|
||||
main_frame,
|
||||
text="文件列表:",
|
||||
font=('Arial', 10, 'bold')
|
||||
)
|
||||
list_label.pack(anchor='w', pady=(0, 5))
|
||||
|
||||
# 文件列表框
|
||||
self.file_listbox = tk.Listbox(
|
||||
main_frame,
|
||||
height=8,
|
||||
selectmode=tk.MULTIPLE
|
||||
)
|
||||
self.file_listbox.pack(fill='both', expand=True, pady=(0, 10))
|
||||
|
||||
# 命令输出区域
|
||||
output_label = tk.Label(
|
||||
main_frame,
|
||||
text="导入命令:",
|
||||
font=('Arial', 10, 'bold')
|
||||
)
|
||||
output_label.pack(anchor='w', pady=(0, 5))
|
||||
|
||||
self.command_text = scrolledtext.ScrolledText(
|
||||
main_frame,
|
||||
height=6,
|
||||
width=70
|
||||
)
|
||||
self.command_text.pack(fill='both', expand=True)
|
||||
|
||||
def setup_drag_drop(self):
|
||||
"""设置拖拽功能"""
|
||||
try:
|
||||
# 设置拖拽目标
|
||||
self.drop_area.drop_target_register('DND_Files')
|
||||
self.drop_area.dnd_bind('<<Drop>>', self.on_drop)
|
||||
self.drop_area.dnd_bind('<<DragEnter>>', self.on_drag_enter)
|
||||
self.drop_area.dnd_bind('<<DragLeave>>', self.on_drag_leave)
|
||||
except:
|
||||
# 如果拖拽不支持,只使用文件选择器
|
||||
print("拖拽功能不可用,请使用文件选择器")
|
||||
|
||||
def on_drag_enter(self, event):
|
||||
"""拖拽进入事件"""
|
||||
self.drop_area.configure(bg='lightblue')
|
||||
|
||||
def on_drag_leave(self, event):
|
||||
"""拖拽离开事件"""
|
||||
self.drop_area.configure(bg='lightgray')
|
||||
|
||||
def on_drop(self, event):
|
||||
"""拖拽释放事件"""
|
||||
try:
|
||||
self.drop_area.configure(bg='lightgray')
|
||||
|
||||
# 获取拖拽的文件
|
||||
files = self.root.tk.splitlist(event.data)
|
||||
|
||||
for file_path in files:
|
||||
self.add_file(file_path)
|
||||
|
||||
except Exception as e:
|
||||
messagebox.showerror("错误", f"拖拽处理失败: {e}")
|
||||
|
||||
def add_file(self, file_path):
|
||||
"""添加文件到列表"""
|
||||
if os.path.exists(file_path):
|
||||
file_ext = os.path.splitext(file_path)[1].lower()
|
||||
if file_ext in self.supported_formats:
|
||||
if file_path not in self.file_listbox.get(0, tk.END):
|
||||
self.file_listbox.insert(tk.END, file_path)
|
||||
else:
|
||||
messagebox.showwarning("格式错误", f"不支持的文件格式: {file_ext}")
|
||||
else:
|
||||
messagebox.showerror("文件错误", f"文件不存在: {file_path}")
|
||||
|
||||
def select_files(self):
|
||||
"""选择文件"""
|
||||
files = filedialog.askopenfilenames(
|
||||
title="选择3D模型文件",
|
||||
filetypes=[
|
||||
("所有支持的格式", "*.gltf *.glb *.fbx *.bam *.egg *.obj"),
|
||||
("glTF文件", "*.gltf *.glb"),
|
||||
("FBX文件", "*.fbx"),
|
||||
("BAM文件", "*.bam"),
|
||||
("EGG文件", "*.egg"),
|
||||
("OBJ文件", "*.obj"),
|
||||
("所有文件", "*.*")
|
||||
]
|
||||
)
|
||||
|
||||
for file_path in files:
|
||||
self.add_file(file_path)
|
||||
|
||||
def clear_files(self):
|
||||
"""清空文件列表"""
|
||||
self.file_listbox.delete(0, tk.END)
|
||||
self.command_text.delete(1.0, tk.END)
|
||||
|
||||
def generate_commands(self):
|
||||
"""生成导入命令"""
|
||||
files = list(self.file_listbox.get(0, tk.END))
|
||||
|
||||
if not files:
|
||||
messagebox.showwarning("没有文件", "请先选择或拖拽文件")
|
||||
return
|
||||
|
||||
# 生成Python导入代码
|
||||
commands = "# 在demo.py的控制台中执行以下命令来导入文件:\n\n"
|
||||
|
||||
for i, file_path in enumerate(files, 1):
|
||||
filename = os.path.basename(file_path)
|
||||
commands += f"# 导入文件 {i}: {filename}\n"
|
||||
commands += f"world._import_model_from_path('{file_path}')\n\n"
|
||||
|
||||
commands += "# 或者使用批量导入:\n"
|
||||
commands += "files = [\n"
|
||||
for file_path in files:
|
||||
commands += f" '{file_path}',\n"
|
||||
commands += "]\n"
|
||||
commands += "for file_path in files:\n"
|
||||
commands += " world._import_model_from_path(file_path)\n"
|
||||
|
||||
# 显示命令
|
||||
self.command_text.delete(1.0, tk.END)
|
||||
self.command_text.insert(1.0, commands)
|
||||
|
||||
messagebox.showinfo("完成", "导入命令已生成,请复制到demo.py控制台中执行")
|
||||
|
||||
def run(self):
|
||||
"""运行应用"""
|
||||
self.root.mainloop()
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
try:
|
||||
app = SimpleDragDrop()
|
||||
app.run()
|
||||
except Exception as e:
|
||||
print(f"启动拖拽工具失败: {e}")
|
||||
input("按回车键退出...")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@ -1,149 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
测试中文字体显示的调试脚本
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '.'))
|
||||
|
||||
from panda3d.core import loadPrcFileData
|
||||
from direct.showbase.ShowBase import ShowBase
|
||||
import p3dimgui
|
||||
from imgui_bundle import imgui
|
||||
import platform
|
||||
from pathlib import Path
|
||||
|
||||
class ChineseFontTest(ShowBase):
|
||||
def __init__(self):
|
||||
# 设置窗口
|
||||
loadPrcFileData('', 'win-size 800 600')
|
||||
ShowBase.__init__(self)
|
||||
|
||||
# 初始化ImGui
|
||||
p3dimgui.init()
|
||||
|
||||
# 测试字体加载
|
||||
self.test_font_loading()
|
||||
|
||||
# 接受ImGui新帧事件
|
||||
self.accept('imgui-new-frame', self._new_frame)
|
||||
self.accept('`', self._toggle_imgui)
|
||||
|
||||
self.show_test_window = True
|
||||
|
||||
def test_font_loading(self):
|
||||
"""测试字体加载"""
|
||||
print("=== 开始字体测试 ===")
|
||||
|
||||
# 检查系统
|
||||
system = platform.system().lower()
|
||||
print(f"系统: {system}")
|
||||
|
||||
# 获取字体路径
|
||||
font_paths = []
|
||||
if system == "linux":
|
||||
font_paths = [
|
||||
"/usr/share/fonts/truetype/wqy/wqy-microhei.ttc",
|
||||
"/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc",
|
||||
"/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
|
||||
]
|
||||
elif system == "windows":
|
||||
font_paths = [
|
||||
"C:/Windows/Fonts/msyh.ttc",
|
||||
"C:/Windows/Fonts/simhei.ttf",
|
||||
]
|
||||
elif system == "darwin":
|
||||
font_paths = [
|
||||
"/System/Library/Fonts/PingFang.ttc",
|
||||
"/System/Library/Fonts/STHeiti.ttc",
|
||||
]
|
||||
|
||||
# 测试每个字体
|
||||
for font_path in font_paths:
|
||||
if Path(font_path).exists():
|
||||
print(f"✓ 字体文件存在: {font_path}")
|
||||
try:
|
||||
# 尝试加载字体
|
||||
font = self.imgui.io.fonts.add_font_from_file_ttf(font_path, 16.0)
|
||||
print(f" ✓ 字体加载成功")
|
||||
|
||||
# 检查字体信息
|
||||
font_info = self.imgui.io.fonts.fonts
|
||||
print(f" 字体数量: {len(font_info)}")
|
||||
|
||||
# 尝试获取字符集
|
||||
try:
|
||||
# 方法1:尝试获取中文字符集
|
||||
chinese_ranges = imgui.get_io().fonts.get_glyph_ranges_chinese_full()
|
||||
print(f" ✓ 中文字符集获取成功,范围: {len(chinese_ranges)} 个字符")
|
||||
except:
|
||||
try:
|
||||
# 方法2:尝试获取中文字符集(简化版)
|
||||
chinese_ranges = imgui.get_io().fonts.get_glyph_ranges_chinese()
|
||||
print(f" ✓ 中文字符集获取成功(简化版),范围: {len(chinese_ranges)} 个字符")
|
||||
except:
|
||||
try:
|
||||
# 方法3:尝试获取默认字符集
|
||||
default_ranges = imgui.get_io().fonts.get_glyph_ranges_default()
|
||||
print(f" ✓ 默认字符集获取成功,范围: {len(default_ranges)} 个字符")
|
||||
print(f" ⚠ 使用默认字符集,可能不支持中文")
|
||||
except Exception as e:
|
||||
print(f" ⚠ 字符集获取失败: {e}")
|
||||
print(f" ⚠ 将使用默认字符集")
|
||||
|
||||
break
|
||||
except Exception as e:
|
||||
print(f" ✗ 字体加载失败: {e}")
|
||||
else:
|
||||
print(f"✗ 字体文件不存在: {font_path}")
|
||||
|
||||
print("=== 字体测试完成 ===")
|
||||
|
||||
def _toggle_imgui(self):
|
||||
if not self.imgui.isKeyboardCaptured():
|
||||
self.imgui.toggle()
|
||||
|
||||
def _new_frame(self):
|
||||
# 绘制测试窗口
|
||||
if self.show_test_window:
|
||||
imgui.show_demo_window()
|
||||
|
||||
# 中文测试窗口
|
||||
expanded, opened = imgui.begin("中文显示测试", True)
|
||||
if expanded:
|
||||
imgui.text("基础中文测试:")
|
||||
imgui.text("你好,世界!")
|
||||
imgui.text("这是一个中文测试")
|
||||
imgui.text("引擎初始化完成")
|
||||
imgui.text("选择和变换系统")
|
||||
imgui.text("脚本管理系统")
|
||||
|
||||
imgui.separator()
|
||||
imgui.text("特殊字符测试:")
|
||||
imgui.text("标点符号:,。!?;:""''()【】")
|
||||
|
||||
imgui.separator()
|
||||
imgui.text("数字测试:")
|
||||
imgui.text("一二三四五六七八九十")
|
||||
|
||||
imgui.separator()
|
||||
|
||||
if imgui.button("测试按钮"):
|
||||
print("中文按钮被点击")
|
||||
|
||||
imgui.same_line()
|
||||
if imgui.button("另一个按钮"):
|
||||
print("另一个中文按钮")
|
||||
|
||||
imgui.separator()
|
||||
|
||||
changed, text = imgui.input_text("输入中文", "在这里输入中文")
|
||||
if changed:
|
||||
print(f"输入的中文: {text}")
|
||||
|
||||
imgui.end()
|
||||
|
||||
if __name__ == "__main__":
|
||||
test = ChineseFontTest()
|
||||
test.run()
|
||||
@ -1,52 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
测试ImGui docking功能
|
||||
"""
|
||||
from direct.showbase.ShowBase import ShowBase
|
||||
import p3dimgui
|
||||
from imgui_bundle import imgui, imgui_ctx
|
||||
|
||||
class DockingTest(ShowBase):
|
||||
def __init__(self):
|
||||
ShowBase.__init__(self)
|
||||
|
||||
# Install Dear ImGui
|
||||
p3dimgui.init()
|
||||
|
||||
# 启用ImGui Docking功能
|
||||
imgui.get_io().config_flags |= imgui.ConfigFlags_.docking_enable
|
||||
print("✓ ImGui Docking功能已启用")
|
||||
|
||||
# 添加任务
|
||||
self.taskMgr.add(self.__newFrame, "imgui-new-frame")
|
||||
|
||||
def __newFrame(self, task):
|
||||
# 创建全屏DockSpace(在第一帧之后创建)
|
||||
if imgui.get_frame_count() > 0:
|
||||
viewport = imgui.get_main_viewport()
|
||||
imgui.dock_space_over_viewport(0, viewport, imgui.DockNodeFlags_.passthru_central_node)
|
||||
|
||||
# 测试窗口
|
||||
if imgui.get_frame_count() > 10: # 等待几帧后显示
|
||||
# 窗口1
|
||||
with imgui_ctx.begin("测试窗口1", True):
|
||||
imgui.text("这是第一个测试窗口")
|
||||
imgui.text("你可以拖拽标题栏来移动窗口")
|
||||
imgui.text("也可以拖拽到其他窗口边缘来停靠")
|
||||
|
||||
# 窗口2
|
||||
with imgui_ctx.begin("测试窗口2", True):
|
||||
imgui.text("这是第二个测试窗口")
|
||||
imgui.text("尝试将这个窗口停靠到第一个窗口")
|
||||
imgui.text("或者创建标签页")
|
||||
|
||||
# 窗口3
|
||||
with imgui_ctx.begin("测试窗口3", True):
|
||||
imgui.text("这是第三个测试窗口")
|
||||
imgui.text("自由调整窗口大小和位置")
|
||||
|
||||
return task.cont
|
||||
|
||||
if __name__ == "__main__":
|
||||
test = DockingTest()
|
||||
test.run()
|
||||
21
testgui.py
21
testgui.py
@ -1,21 +0,0 @@
|
||||
from direct.showbase.ShowBase import ShowBase
|
||||
|
||||
from imgui_bundle import imgui
|
||||
|
||||
import p3dimgui
|
||||
|
||||
class MyApp(ShowBase):
|
||||
def __init__(self):
|
||||
ShowBase.__init__(self)
|
||||
|
||||
# Install Dear ImGui
|
||||
p3dimgui.init()
|
||||
|
||||
self.accept('imgui-new-frame', self.draw)
|
||||
|
||||
def draw(self):
|
||||
# Show the demo window.
|
||||
imgui.show_demo_window()
|
||||
|
||||
app = MyApp()
|
||||
app.run()
|
||||
Loading…
Reference in New Issue
Block a user