diff --git a/.idea/EG.iml b/.idea/EG.iml
index c608c0d0..04720f01 100644
--- a/.idea/EG.iml
+++ b/.idea/EG.iml
@@ -5,7 +5,7 @@
-
+
diff --git a/Start_Run.py b/Start_Run.py
index 8e6a1214..81951385 100644
--- a/Start_Run.py
+++ b/Start_Run.py
@@ -5,6 +5,9 @@ import sys
project_root = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, project_root)
+# 设置工作目录为项目根目录
+os.chdir(project_root)
+
# 添加 RenderPipeline 到路径(注意路径名称应与实际目录一致)
render_pipeline_path = os.path.join(project_root, "RenderPipeline")
sys.path.insert(0, render_pipeline_path)
diff --git a/core/vr/config/vr_settings.json b/core/vr/config/vr_settings.json
index 902d31fb..556f68a2 100644
--- a/core/vr/config/vr_settings.json
+++ b/core/vr/config/vr_settings.json
@@ -11,5 +11,8 @@
"enable_ssr": false,
"shadow_quality": "medium",
"ao_quality": "low"
- }
+ },
+ "anti_aliasing": "4x",
+ "refresh_rate": "90Hz",
+ "async_reprojection": true
}
\ No newline at end of file
diff --git a/tools/open_source_rate.py b/tools/open_source_rate.py
new file mode 100644
index 00000000..cf389707
--- /dev/null
+++ b/tools/open_source_rate.py
@@ -0,0 +1,95 @@
+import os
+from pathlib import Path
+
+THIRD_PARTY_DIR_NAMES = {
+ 'node_modules', 'vendor', 'third_party', 'third-party', 'extern', 'external', 'deps',
+ 'Cesium', 'Cesium-1.132', 'dist', 'build'
+}
+
+CODE_EXTENSIONS = {
+ '.py', '.js', '.ts', '.tsx', '.jsx', '.css', '.scss', '.html', '.c', '.h', '.cpp', '.hpp',
+ '.cc', '.hh', '.m', '.mm', '.java', '.go', '.rs', '.cs', '.vue', '.svelte'
+}
+
+SKIP_DIR_NAMES = {
+ '.git', '.hg', '.svn', '__pycache__', '.mypy_cache', '.pytest_cache', '.idea', '.vscode',
+ '.venv', 'venv', 'env', '.tox', '.cache', 'Resources', 'icons', 'tex', 'terminal'
+}
+
+MAX_FILE_SIZE_BYTES = 2 * 1024 * 1024 # 2 MiB
+
+
+def is_third_party(path: Path) -> bool:
+ for part in path.parts:
+ # normalize case on Windows
+ name = part.lower()
+ if name in {n.lower() for n in THIRD_PARTY_DIR_NAMES}:
+ return True
+ return False
+
+
+def should_skip_dir(dirname: str) -> bool:
+ return dirname.lower() in {n.lower() for n in SKIP_DIR_NAMES}
+
+
+def is_code_file(path: Path) -> bool:
+ return path.suffix.lower() in CODE_EXTENSIONS
+
+
+def count_lines(path: Path) -> int:
+ try:
+ if path.stat().st_size > MAX_FILE_SIZE_BYTES:
+ return 0
+ # Try text read with universal newlines, fallback to binary
+ try:
+ with path.open('r', encoding='utf-8', errors='ignore') as f:
+ return sum(1 for _ in f)
+ except Exception:
+ with path.open('rb') as f:
+ return f.read().count(b'\n')
+ except Exception:
+ return 0
+
+
+def main(root: Path) -> None:
+ third_party_loc = 0
+ first_party_loc = 0
+ third_party_files = 0
+ first_party_files = 0
+
+ for dirpath, dirnames, filenames in os.walk(root):
+ # prune directories
+ dirnames[:] = [d for d in dirnames if not should_skip_dir(d)]
+
+ current = Path(dirpath)
+ current_is_third = is_third_party(current)
+
+ for fn in filenames:
+ p = current / fn
+ if not is_code_file(p):
+ continue
+ loc = count_lines(p)
+ if current_is_third:
+ third_party_loc += loc
+ third_party_files += 1
+ else:
+ first_party_loc += loc
+ first_party_files += 1
+
+ total_loc = first_party_loc + third_party_loc
+ open_source_rate = (third_party_loc / total_loc) * 100 if total_loc else 0.0
+
+ print('Open-Source Rate (by LOC): {:.2f}%'.format(open_source_rate))
+ print('\nBreakdown:')
+ print(' First-party LOC : {} ({} files)'.format(first_party_loc, first_party_files))
+ print(' Third-party LOC : {} ({} files)'.format(third_party_loc, third_party_files))
+ print(' Total LOC : {}'.format(total_loc))
+ print('\nNotes:')
+ print(' - Third-party directories are heuristically detected by common names: {}'.format(', '.join(sorted(THIRD_PARTY_DIR_NAMES))))
+ print(' - Static asset directories are skipped: {}'.format(', '.join(sorted(SKIP_DIR_NAMES))))
+ print(' - Counted code extensions: {}'.format(', '.join(sorted(CODE_EXTENSIONS))))
+
+
+if __name__ == '__main__':
+ main(Path('.').resolve())
+
diff --git a/ui/property_panel.py b/ui/property_panel.py
index 65a5e874..3411306f 100644
--- a/ui/property_panel.py
+++ b/ui/property_panel.py
@@ -9790,6 +9790,12 @@ except Exception as e:
collision_group.setLayout(collision_layout)
self._propertyLayout.addWidget(collision_group)
+ # 同步一次状态,确保主按钮与可见性按钮齐全
+ try:
+ self._updateCollisionPanelState(model)
+ except Exception:
+ pass
+
except Exception as e:
print(f"创建碰撞面板失败: {e}")
import traceback
@@ -10144,8 +10150,9 @@ except Exception as e:
collision_np.show()
print(f"显示碰撞:{model.getName()}")
- # 立即更新按钮状态
+ # 立即更新按钮状态,并同步更新面板(确保缺失主按钮时补建)
self._updateCollisionVisibilityButton(model)
+ self._updateCollisionPanelState(model)
except Exception as e:
print(f"切换碰撞可见性失败: {e}")
@@ -10353,26 +10360,18 @@ except Exception as e:
has_collision = self._hasCollision(model)
print(f"模型 {model.getName()} 是否有碰撞体: {has_collision}-------------------------------------------------")
- # 更新状态徽章(使用固定宽度)
- if has_collision:
- new_badge = self.createFixedStatusBadge("已启用", "green")
- else:
- new_badge = self.createFixedStatusBadge("未启用", "red")
-
- # 替换旧的徽章
- old_badge = self.collision_status_badge
- parent_layout = old_badge.parent().layout()
- if parent_layout:
- # 找到旧徽章在布局中的位置
- for i in range(parent_layout.count()):
- item = parent_layout.itemAt(i)
- if item and item.widget() == old_badge:
- # 移除旧徽章并添加新徽章
- parent_layout.removeWidget(old_badge)
- old_badge.deleteLater()
- parent_layout.addWidget(new_badge, 0, 0, 1, 1) # 状态徽章在第0行第1列
- self.collision_status_badge = new_badge
- break
+ # 更新状态徽章(直接修改现有控件,避免布局冲突)
+ if hasattr(self, 'collision_status_badge') and self.collision_status_badge:
+ if has_collision:
+ self.collision_status_badge.setText("已启用")
+ # 使用固定宽度的绿色样式,保持尺寸一致
+ self.collision_status_badge.setStyleSheet(self.badge_style_green_fixed)
+ else:
+ self.collision_status_badge.setText("未启用")
+ # 使用固定宽度的红色样式,保持尺寸一致
+ self.collision_status_badge.setStyleSheet(self.badge_style_red_fixed)
+ # 触发重绘,确保立即可见
+ self.collision_status_badge.update()
if has_collision:
# 有碰撞:显示移除按钮,下拉框变为只读并显示当前类型
@@ -10388,6 +10387,18 @@ except Exception as e:
except:
pass
self.collision_button.clicked.connect(lambda: self._removeCollisionAndUpdate(model))
+ else:
+ # 若不存在主按钮,则创建“移除碰撞”按钮并加入布局
+ try:
+ self.collision_button = self.createModernButton("移除碰撞")
+ self.collision_button.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
+ self.collision_button.setStyleSheet(self._collision_button_style())
+ self.collision_button.clicked.connect(lambda: self._removeCollisionAndUpdate(model))
+ if hasattr(self, 'collision_layout') and self.collision_layout is not None:
+ row = self.collision_layout.rowCount()
+ self.collision_layout.addWidget(self.collision_button, row, 0, 1, 4)
+ except Exception as _e:
+ print(f"创建移除碰撞按钮失败: {_e}")
# 获取并显示当前碰撞类型,设置为只读
current_shape = self._getCurrentCollisionShape(model)
@@ -10407,6 +10418,13 @@ except Exception as e:
self.collision_visibility_button.setVisible(True)
self._updateCollisionVisibilityButton(model)
+ # 确保按钮顺序:可见性按钮在上,主按钮在下
+ if hasattr(self, 'collision_layout'):
+ try:
+ self._repositionButtons(self.collision_layout.rowCount())
+ except Exception:
+ pass
+
print(f"碰撞面板状态更新:有碰撞 - {current_shape}")
else:
@@ -10422,6 +10440,18 @@ except Exception as e:
except:
pass
self.collision_button.clicked.connect(lambda: self._addCollisionAndUpdate(model))
+ else:
+ # 若不存在主按钮,则创建“添加碰撞”按钮并加入布局
+ try:
+ self.collision_button = self.createModernButton("添加碰撞")
+ self.collision_button.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
+ self.collision_button.setStyleSheet(self._collision_button_style())
+ self.collision_button.clicked.connect(lambda: self._addCollisionAndUpdate(model))
+ if hasattr(self, 'collision_layout') and self.collision_layout is not None:
+ row = self.collision_layout.rowCount()
+ self.collision_layout.addWidget(self.collision_button, row, 0, 1, 4)
+ except Exception as _e:
+ print(f"创建添加碰撞按钮失败: {_e}")
# 恢复为可编辑状态
self.collision_shape_combo.setEnabled(True)
@@ -10435,6 +10465,13 @@ except Exception as e:
if hasattr(self, 'collision_visibility_button'):
self.collision_visibility_button.setVisible(False)
+ # 确保按钮顺序:主按钮位于底部
+ if hasattr(self, 'collision_layout'):
+ try:
+ self._repositionButtons(self.collision_layout.rowCount())
+ except Exception:
+ pass
+
print(f"碰撞面板状态更新:无碰撞 - 可编辑")
except Exception as e:
@@ -11097,13 +11134,24 @@ except Exception as e:
layout = self.collision_layout
is_collision_visible = self._isCollisionVisible(model)
- # 找到合适的行位置
+ # 放在当前最后一行
current_row = layout.rowCount()
self.collision_visibility_button = QPushButton("隐藏碰撞" if is_collision_visible else "显示碰撞")
self.collision_visibility_button.setStyleSheet(self._collision_button_style())
self.collision_visibility_button.clicked.connect(lambda: self._toggleCollisionVisibility(model))
- layout.addWidget(self.collision_visibility_button, current_row - 1, 0, 1, 4)
+ layout.addWidget(self.collision_visibility_button, current_row, 0, 1, 4)
+
+ # 确保存在主按钮(有碰撞时应显示移除碰撞)
+ if self._hasCollision(model) and not hasattr(self, 'collision_button'):
+ try:
+ self.collision_button = self.createModernButton("移除碰撞")
+ self.collision_button.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
+ self.collision_button.setStyleSheet(self._collision_button_style())
+ self.collision_button.clicked.connect(lambda: self._removeCollisionAndUpdate(model))
+ layout.addWidget(self.collision_button, current_row + 1, 0, 1, 4)
+ except Exception as _e:
+ print(f"在可见性按钮后创建移除碰撞按钮失败: {_e}")
except Exception as e:
print(f"添加可见性按钮失败: {e}")
@@ -11114,22 +11162,39 @@ except Exception as e:
if hasattr(self, 'collision_layout'):
layout = self.collision_layout
- # 移动可见性按钮
- if hasattr(self, 'collision_visibility_button'):
- layout.addWidget(self.collision_visibility_button, new_row, 0, 1, 4)
- new_row += 1
+ def _remove_if_present(w):
+ try:
+ if not w:
+ return
+ # 从布局中移除该控件(如果已存在)
+ for i in range(layout.count()):
+ item = layout.itemAt(i)
+ if item and item.widget() is w:
+ layout.removeWidget(w)
+ break
+ except Exception:
+ pass
- # 移动主按钮
- if hasattr(self, 'collision_button'):
- layout.addWidget(self.collision_button, new_row, 0, 1, 4)
+ # 计算底部行
+ bottom_row = layout.rowCount()
+
+ # 按固定顺序重新放置:先可见性按钮,再主按钮
+ if hasattr(self, 'collision_visibility_button') and self.collision_visibility_button:
+ _remove_if_present(self.collision_visibility_button)
+ layout.addWidget(self.collision_visibility_button, bottom_row, 0, 1, 4)
+ bottom_row += 1
+
+ if hasattr(self, 'collision_button') and self.collision_button:
+ _remove_if_present(self.collision_button)
+ layout.addWidget(self.collision_button, bottom_row, 0, 1, 4)
except Exception as e:
print(f"重新定位按钮失败: {e}")
def _hideCollisionParameterControls(self):
- """隐藏碰撞参数控件(保留按钮)"""
+ """隐藏并移除碰撞参数控件与其所在的行(保留状态/形状/按钮行)"""
try:
- # 清理属性引用,但保留按钮
+ # 1) 先清理对象属性引用,避免残留信号
param_attrs = [
'collision_pos_x', 'collision_pos_y', 'collision_pos_z',
'collision_radius',
@@ -11137,47 +11202,72 @@ except Exception as e:
'collision_capsule_radius', 'collision_capsule_height',
'collision_normal_x', 'collision_normal_y', 'collision_normal_z'
]
-
- # 隐藏并删除参数控件
for attr in param_attrs:
if hasattr(self, attr):
widget = getattr(self, attr)
- if widget:
- widget.setVisible(False)
- widget.setParent(None)
- widget.deleteLater()
+ try:
+ if widget:
+ widget.setParent(None)
+ widget.deleteLater()
+ except Exception:
+ pass
delattr(self, attr)
- # 同时清理可能的标签控件
- if hasattr(self, 'collision_layout'):
+ # 2) 深度移除布局中第2行(索引>=2)之后的所有参数相关项,保留按钮
+ if hasattr(self, 'collision_layout') and self.collision_layout is not None:
layout = self.collision_layout
- # 收集需要移除的控件(不包括基本控件和按钮)
- widgets_to_remove = []
+ def _remove_layout_recursive(q_layout):
+ try:
+ while q_layout.count():
+ child = q_layout.takeAt(0)
+ if child.widget():
+ w = child.widget()
+ w.setParent(None)
+ w.deleteLater()
+ elif child.layout():
+ _remove_layout_recursive(child.layout())
+ except Exception:
+ pass
- for i in range(layout.rowCount()):
- if i >= 2: # 从第3行开始检查
- for j in range(layout.columnCount()):
- item = layout.itemAtPosition(i, j)
- if item:
- widget = item.widget()
- if widget and hasattr(widget, 'text'):
- # 检查是否是参数相关的标签
- text = widget.text()
- if any(keyword in text for keyword in
- ['位置偏移', 'X:', 'Y:', 'Z:', '半径:', '尺寸:', '宽度:', '长度:', '高度:',
- '法向量:', 'Nx:', 'Ny:', 'Nz:']):
- widgets_to_remove.append(widget)
+ row_count = layout.rowCount()
+ col_count = layout.columnCount()
+ # 从参数区域开始清理(行索引2及以后),但跳过我们要保留的按钮
+ for i in range(2, row_count):
+ for j in range(0, col_count):
+ item = layout.itemAtPosition(i, j)
+ if not item:
+ continue
+ # 若是按钮,且是保留的按钮,则跳过
+ w = item.widget()
+ if w is not None:
+ try:
+ if (hasattr(self, 'collision_button') and w == self.collision_button) or (hasattr(self, 'collision_visibility_button') and w == self.collision_visibility_button):
+ continue
+ except Exception:
+ pass
+ layout.removeWidget(w)
+ w.setParent(None)
+ w.deleteLater()
+ elif item.layout():
+ # 移除嵌套布局(包含标签与输入框)
+ nested = item.layout()
+ _remove_layout_recursive(nested)
+ layout.removeItem(nested)
- # 移除参数标签
- for widget in widgets_to_remove:
- widget.setVisible(False)
- widget.setParent(None)
- widget.deleteLater()
+ # 尝试把保留的按钮移动到参数区域第一行(行2)下方,保持整洁
+ try:
+ next_row = 2
+ if hasattr(self, 'collision_visibility_button'):
+ self.collision_visibility_button.setVisible(False)
+ if hasattr(self, 'collision_button') and self.collision_button is not None:
+ layout.addWidget(self.collision_button, next_row, 0, 1, 4)
+ except Exception:
+ pass
- print("隐藏碰撞参数控件完成(保留按钮)")
+ print("隐藏碰撞参数控件完成(仅保留状态、形状与按钮)")
except Exception as e:
print(f"隐藏碰撞参数控件失败: {e}")
import traceback
- traceback.print_exc()
\ No newline at end of file
+ traceback.print_exc()