From ffdad6f099772448c7edb03b844be80b74e089d2 Mon Sep 17 00:00:00 2001 From: ayuan9957 <107920784+ayuan9957@users.noreply.github.com> Date: Sat, 28 Feb 2026 17:42:09 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E4=BA=8C=E8=BD=AE=E6=8B=86?= =?UTF-8?q?=E5=88=86loadScene=E8=8A=82=E7=82=B9=E5=A4=84=E7=90=86=E6=B5=81?= =?UTF-8?q?=E7=A8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PROJECT_OPTIMIZATION_ANALYSIS.md | 47 +-- scene/scene_manager_io_mixin.py | 515 +++++++++++++------------------ 2 files changed, 247 insertions(+), 315 deletions(-) diff --git a/PROJECT_OPTIMIZATION_ANALYSIS.md b/PROJECT_OPTIMIZATION_ANALYSIS.md index ed7a7cfe..c1b86954 100644 --- a/PROJECT_OPTIMIZATION_ANALYSIS.md +++ b/PROJECT_OPTIMIZATION_ANALYSIS.md @@ -21,7 +21,7 @@ - 直接访问 `world.interface_manager`: `0` - 直接访问 `interface_manager.treeWidget`: `0` - `app.gui_manager` 仅剩注释引用(`ui/panels/editor_panels_left.py`) -- Task B 第一轮已落地(`scene_manager_io_mixin.loadScene`): +- Task B 第二轮已落地(`scene_manager_io_mixin.loadScene`): - 已抽出流程 helper: - `_preflight_load_scene` - `_cleanup_after_failed_load` @@ -30,7 +30,12 @@ - `_bootstrap_scene_tree_for_loaded_root` - `_load_scene_gui_metadata` - `_retry_load_scene` - - `loadScene` 行数:`556 -> 366` + - `_walk_loaded_scene` + - `_process_loaded_scene_node` + - `_restore_node_scripts` + - `_parse_vec3_string` + - `_parse_color_string` + - `loadScene` 行数:`556 -> 74` - 已清理重复异常分支:`_rebuildParentChildRelationships` 内重复 `except` 已移除 ## 1. 总体画像 @@ -64,16 +69,16 @@ 7. `TransformGizmo/rotate_gizmo.py` (`1587` 行) 8. `ui/panels/animation_tools.py` (`1579` 行) -### 2.2 长函数 Top(优先拆分) +### 2.2 长函数 Top(优先拆分,非 VR 当前状态) 1. `ui/LUI/lui_function_properties.py::_draw_component_properties` (`1441` 行) -2. `scene/scene_manager_io_mixin.py::loadScene` (`556` 行) -3. `ui/panels/animation_tools.py::_getActor` (`510` 行) -4. `main.py::__init__` (`375` 行) -5. `ui/LUI/lui_manager_interaction.py::_update_drag` (`348` 行) -6. `ui/panels/editor_panels_left.py::_draw_resource_manager` (`310` 行) -7. `scene/scene_manager_io_mixin.py::processNode` (`281` 行) -8. `core/selection.py::updateGizmoDrag` (`278` 行) +2. `ui/panels/animation_tools.py::_getActor` (`510` 行) +3. `main.py::__init__` (`375` 行) +4. `ui/LUI/lui_manager_interaction.py::_update_drag` (`348` 行) +5. `ui/panels/editor_panels_left.py::_draw_resource_manager` (`310` 行) +6. `core/selection.py::updateGizmoDrag` (`278` 行) +7. `ui/LUI/lui_manager_interaction.py::_handle_resize_drag` (`257` 行) +8. `scene/scene_manager_io_mixin.py::saveScene` (`222` 行) ### 2.3 异常处理密度高(可观测性风险) @@ -191,16 +196,14 @@ ## 4.1 本轮深入分析(非 VR,P1 准备) -### A) `scene/scene_manager_io_mixin.py::loadScene`(核心优先) +### A) `scene/scene_manager_io_mixin.py::loadScene`(核心优先,已完成) -- 函数规模: `556` 行 -- 近似圈复杂度: `114` +- 函数规模: `74` 行(重构前 `556` 行) +- 近似圈复杂度(重构前): `114` - 关键问题: - - 单函数同时承担 8 类职责(校验/清理/加载/树同步/节点递归处理/脚本恢复/材质恢复/重试)。 - - 内嵌 `processNode` 递归函数长达 `281` 行,可测试性差。 - - 调试输出密度高(`print` 与注释 `print` 很多),影响可读性和噪声控制。 - - `scene/scene_manager_io_mixin.py:1026` 与 `scene/scene_manager_io_mixin.py:1032` 存在重复 `except Exception` 分支(可合并)。 - - `scene/scene_manager_io_mixin.py:946` 的 GUI 重建入口目前仍注释,行为边界不清晰。 + - `processNode` 内联逻辑已外提为 `_walk_loaded_scene/_process_loaded_scene_node`。 + - 重复异常分支已清理。 + - 仍存在较多调试输出(`print` 与注释 `print`),后续可做日志分级。 - 建议拆分(保持外部 API `loadScene` 不变): - `_preflight_scene_file(filename) -> (ok, normalized_path, reason)` - `_cleanup_before_load(tree_widget, retry_count)` @@ -215,8 +218,8 @@ - 节点恢复行为(位置/材质/脚本/可见性)与当前一致。 - 失败重试逻辑保持语义一致。 - 当前状态(2026-02-28): - - 第一轮已完成(预检/清理/加载/树初始化/GUI元数据/重试)。 - - 剩余主要体积来自内嵌 `processNode`,下一轮应继续提取为独立方法。 + - 第二轮已完成(`loadScene` 已达验收目标:`74` 行)。 + - 下一步应转向 `main.py::__init__` 与 `ui/panels/animation_tools.py::_getActor`。 ### B) `main.py::__init__`(第二优先) @@ -255,8 +258,8 @@ ## 4.2 Task B 执行顺序建议(非 VR) -1. 先拆 `loadScene`(收益最大,且与 VR 无关)。 -2. 再整理 `main.__init__`(降低后续模块接入冲突)。 +1. `loadScene` 已完成两轮拆分(当前建议冻结并转入回归观察)。 +2. 下一步整理 `main.__init__`(降低后续模块接入冲突)。 3. 最后处理 `_getActor`(风险最高,建议独立提交并做手工回归)。 ## 5. 与现有文档关系 diff --git a/scene/scene_manager_io_mixin.py b/scene/scene_manager_io_mixin.py index 4b0572c0..e9c8af30 100644 --- a/scene/scene_manager_io_mixin.py +++ b/scene/scene_manager_io_mixin.py @@ -485,301 +485,9 @@ class SceneManagerIOMixin: scene.reparentTo(self.world.render) self._bootstrap_scene_tree_for_loaded_root(tree_widget, scene) - # 遍历场景中的所有模型节点 - # 用于存储处理后的灯光节点,避免重复处理 - processed_lights = [] - # 用于存储处理后的GUI元素,避免重复处理 - - #存储所有加载的节点,用于后续处理父子关系 - loaded_nodes = {} #name->nodePath映射 - - # 遍历场景中的所有节点 - def processNode(nodePath, depth=0): - indent = " " * depth - # print(f"{indent}处理节点: {nodePath.getName()} (类型: {type(nodePath.node()).__name__})") - - # 检查节点是否有效 - if nodePath.isEmpty(): - # print(f"{indent}[DEBUG] 节点为空,跳过处理") - return - - #存储节点以便后续处理父子关系 - loaded_nodes[nodePath.getName()] = nodePath - - if nodePath.getName().startswith('ground'): - print(f"{indent}跳过ground节点: {nodePath.getName()}") - return - - # 跳过render节点的递归 - if nodePath.getName() == "render" and depth > 0: - print(f"{indent}跳过重复的render节点") - return - - # 跳过光源节点 - if nodePath.getName() in ["alight", "dlight"]: - print(f"{indent}跳过光源节点: {nodePath.getName()}") - return - - # 跳过相机节点 - if nodePath.getName() in ["camera", "cam"]: - print(f"{indent}跳过相机节点: {nodePath.getName()}") - return - - # 跳过辅助节点 - if nodePath.getName().startswith(("gizmo", "selectionBox")): - print(f"{indent}跳过辅助节点: {nodePath.getName()}") - return - - if nodePath.getName() in ['SceneRoot'] or \ - any(keyword in nodePath.getName() for keyword in ["Skybox", "skybox"]): - print(f"{indent}跳过环境节点:{nodePath.getName()}") - return - - # 检查是否是用户创建的场景元素 - is_scene_element = ( - nodePath.hasTag("is_scene_element") or - nodePath.hasTag("is_model_root") or - nodePath.hasTag("light_type") or - nodePath.hasTag("gui_type") or # 检查gui_type标签 - nodePath.hasTag("is_gui_element") or - nodePath.hasTag("saved_gui_type") or - (nodePath.hasTag("element_type") and nodePath.getTag("element_type") == "info_panel") - ) - - # 特殊处理:检查节点名称是否包含GUI相关关键词 - is_potential_gui = any(keyword in nodePath.getName().lower() for keyword in - ["gui", "button", "label", "entry", "image", "video", "screen", "text"]) - - if is_scene_element or is_potential_gui: - print(f"{indent}找到场景元素节点: {nodePath.getName()}") - - # 如果是潜在的GUI元素但没有标签,添加基本标签 - if is_potential_gui and not (nodePath.hasTag("gui_type") or nodePath.hasTag("is_gui_element")): - print(f"{indent}为潜在GUI元素添加标签: {nodePath.getName()}") - nodePath.setTag("is_gui_element", "1") - nodePath.setTag("is_scene_element", "1") - # 尝试从名称推断类型 - name_lower = nodePath.getName().lower() - if "button" in name_lower: - nodePath.setTag("gui_type", "button") - elif "label" in name_lower: - nodePath.setTag("gui_type", "label") - elif "entry" in name_lower: - nodePath.setTag("gui_type", "entry") - elif "image" in name_lower: - nodePath.setTag("gui_type", "image") - elif "video" in name_lower or "screen" in name_lower: - nodePath.setTag("gui_type", "video_screen") - else: - nodePath.setTag("gui_type", "unknown") - - # 清除现有材质状态 - nodePath.clearMaterial() - nodePath.clearColor() - - # 恢复变换信息 - def parseVec3(vec_str): - """解析向量字符串为Vec3""" - try: - vec_str = vec_str.replace('LVecBase3f', '').replace('LPoint3f', '').strip('()') - x, y, z = map(float, vec_str.split(',')) - return Vec3(x, y, z) - except Exception as e: - print(f"解析向量失败: {vec_str}, 错误: {e}") - return Vec3(0, 0, 0) - - if nodePath.hasTag("transform_pos"): - pos = parseVec3(nodePath.getTag("transform_pos")) - nodePath.setPos(pos) - print(f"{indent}恢复位置: {pos}") - - if nodePath.hasTag("transform_hpr"): - hpr = parseVec3(nodePath.getTag("transform_hpr")) - nodePath.setHpr(hpr) - print(f"{indent}恢复旋转: {hpr}") - - if nodePath.hasTag("transform_scale"): - scale = parseVec3(nodePath.getTag("transform_scale")) - nodePath.setScale(scale) - print(f"{indent}恢复缩放: {scale}") - - # 恢复可见性状态 - user_visible = True - if nodePath.hasTag("user_visible"): - user_visible = nodePath.getTag("user_visible").lower() == "true" - - # 设置用户可见性标记 - nodePath.setPythonTag("user_visible", user_visible) - - # 应用可见性状态 - if hasattr(self.world, 'property_panel'): - self.world.property_panel._syncEffectiveVisibility(nodePath) - else: - # 如果没有属性面板,直接应用可见性 - if user_visible: - nodePath.show() - else: - nodePath.hide() - - if nodePath.hasTag("has_scripts") and nodePath.getTag("has_scripts") == "true": - if hasattr(self.world,'script_manager') and self.world.script_manager: - try: - import json - scripts_info = json.loads(nodePath.getTag("scripts_info")) - print(f"节点 {nodePath.getName()} 需要重新挂载 {len(scripts_info)} 个脚本") - - script_manager = self.world.script_manager - for script_info in scripts_info: - script_name = script_info["name"] - script_file = script_info.get("file","") - - print(f"尝试重新挂载脚本{script_name}from {script_file}") - - if script_name not in script_manager.loader.script_classes: - if script_file and os.path.exists(script_file): - print(f"从文件加载脚本:{script_file}") - loaded_class = script_manager.load_script_from_file(script_file) - if loaded_class is None: - print(f"从文件加载脚本失败{script_file}") - script_path = self._find_script_in_directory(script_name) - if script_path: - print(f"从目录找到脚本并加载{script_path}") - script_manager.load_script_from_file(script_path) - else: - script_path = self._find_script_in_directory(script_name) - if script_path: - print(f"从目录找到脚本并加载: {script_path}") - script_manager.load_script_from_file(script_path) - else: - print(f"找不到脚本文件: {script_name}") - if script_name in script_manager.loader.script_classes: - script_component = script_manager.add_script_to_object(nodePath,script_name) - if script_component: - print(f"成功为 {nodePath.getName()} 添加脚本: {script_name}") - else: - print(f"为 {nodePath.getName()} 添加脚本失败: {script_name}") - else: - print(f"脚本 {script_name} 不可用,跳过挂载") - except Exception as e: - print(f"重新挂载脚本失败: {e}") - import traceback - traceback.print_exc() - - - # 恢复材质属性 - def parseColor(color_str): - """解析颜色字符串为Vec4""" - try: - color_str = color_str.replace('LVecBase4f', '').strip('()') - r, g, b, a = map(float, color_str.split(',')) - return Vec4(r, g, b, a) - except: - return Vec4(1, 1, 1, 1) - - # 创建并恢复材质 - material = Material() - material_changed = False - - if nodePath.hasTag("material_ambient"): - material.setAmbient(parseColor(nodePath.getTag("material_ambient"))) - material_changed = True - - if nodePath.hasTag("material_diffuse"): - material.setDiffuse(parseColor(nodePath.getTag("material_diffuse"))) - material_changed = True - - if nodePath.hasTag("material_specular"): - material.setSpecular(parseColor(nodePath.getTag("material_specular"))) - material_changed = True - - if nodePath.hasTag("material_emission"): - material.setEmission(parseColor(nodePath.getTag("material_emission"))) - material_changed = True - - if nodePath.hasTag("material_shininess"): - material.setShininess(float(nodePath.getTag("material_shininess"))) - material_changed = True - - if nodePath.hasTag("material_basecolor"): - material.setBaseColor(parseColor(nodePath.getTag("material_basecolor"))) - material_changed = True - - if material_changed: - nodePath.setMaterial(material) - - # 恢复颜色属性 - if nodePath.hasTag("color"): - nodePath.setColor(parseColor(nodePath.getTag("color"))) - - # 处理特定类型的节点 - if nodePath.hasTag("light_type"): - light_type = nodePath.getTag("light_type") - print(f"{indent}检测到光源类型: {light_type}") - - # 检查是否已经处理过这个灯光 - if nodePath not in processed_lights: - # 重新创建RP光源对象 - if light_type == "spot_light": - self._recreateSpotLight(nodePath) - elif light_type == "point_light": - self._recreatePointLight(nodePath) - # 标记为已处理 - processed_lights.append(nodePath) - - elif nodePath.hasTag("element_type"): - element_type = nodePath.getTag("element_type") - if element_type == "cesium_tileset": - tileset_url = nodePath.getTag("saved_tileset_url") if nodePath.hasTag( - "saved_tileset_url") else "" - tileset_info = { - 'url': tileset_url, - 'node': nodePath, - 'position': nodePath.getPos(), - 'tiles': {} - } - - # 将节点重新挂载到render下(如果需要) - # 注意:GUI元素可能需要挂载到特定的父节点上 - if nodePath.hasTag("gui_type") or nodePath.hasTag("is_gui_element"): - # GUI元素通常应该挂载到aspect2d或特定的GUI父节点上 - # 这里我们先保持原挂载关系 - pass - else: - # 其他节点确保挂载到render下 - if nodePath.getParent() != self.world.render and not nodePath.getName() in ["render", - "aspect2d", - "render2d"]: - nodePath.wrtReparentTo(self.world.render) - - # 为模型节点设置碰撞检测 - if nodePath.hasTag("is_model_root"): - print(f"J{indent}处理模型节点{nodePath.getName()}") - - #self._validateAndFixAllTransforms(nodePath) - - self._fixModelStructure(nodePath) - - # 恢复模型动画信息 - self._restoreModelAnimationInfo(nodePath) - - # 检测并处理模型动画(类似property_panel.py中的逻辑) - self._processModelAnimations(nodePath) - - # if self.world.property_panel._hasCollision(nodePath): - # print(f"{indent}模型{nodePath.getName()}已有碰撞体,跳过碰撞体设置") - # else: - # print(f"{indent}为模型{nodePath.getName()}设置碰撞检测") - # self.setupCollision(nodePath) - self.models.append(nodePath) - - # 递归处理子节点 - for child in nodePath.getChildren(): - processNode(child, depth + 1) - print("\n开始处理场景节点...") - processNode(scene) + loaded_nodes = self._walk_loaded_scene(scene) - #处理父子关系 - 在所有节点加载完成后设置正确的父子关系 print("\n开始重建父子关系...") self._rebuildParentChildRelationships(loaded_nodes) @@ -810,6 +518,227 @@ class SceneManagerIOMixin: traceback.print_exc() return self._retry_load_scene(filename, retry_count, max_retries) + def _walk_loaded_scene(self, scene): + """遍历并恢复加载后的场景节点状态。""" + processed_lights = [] + loaded_nodes = {} + self._process_loaded_scene_node(scene, loaded_nodes, processed_lights, depth=0) + return loaded_nodes + + def _process_loaded_scene_node(self, nodePath, loaded_nodes, processed_lights, depth=0): + indent = " " * depth + if nodePath.isEmpty(): + return + + loaded_nodes[nodePath.getName()] = nodePath + + if nodePath.getName().startswith('ground'): + print(f"{indent}跳过ground节点: {nodePath.getName()}") + return + + if nodePath.getName() == "render" and depth > 0: + print(f"{indent}跳过重复的render节点") + return + + if nodePath.getName() in ["alight", "dlight"]: + print(f"{indent}跳过光源节点: {nodePath.getName()}") + return + + if nodePath.getName() in ["camera", "cam"]: + print(f"{indent}跳过相机节点: {nodePath.getName()}") + return + + if nodePath.getName().startswith(("gizmo", "selectionBox")): + print(f"{indent}跳过辅助节点: {nodePath.getName()}") + return + + if nodePath.getName() in ['SceneRoot'] or any(keyword in nodePath.getName() for keyword in ["Skybox", "skybox"]): + print(f"{indent}跳过环境节点:{nodePath.getName()}") + return + + is_scene_element = ( + nodePath.hasTag("is_scene_element") or + nodePath.hasTag("is_model_root") or + nodePath.hasTag("light_type") or + nodePath.hasTag("gui_type") or + nodePath.hasTag("is_gui_element") or + nodePath.hasTag("saved_gui_type") or + (nodePath.hasTag("element_type") and nodePath.getTag("element_type") == "info_panel") + ) + is_potential_gui = any( + keyword in nodePath.getName().lower() + for keyword in ["gui", "button", "label", "entry", "image", "video", "screen", "text"] + ) + + if is_scene_element or is_potential_gui: + print(f"{indent}找到场景元素节点: {nodePath.getName()}") + + if is_potential_gui and not (nodePath.hasTag("gui_type") or nodePath.hasTag("is_gui_element")): + print(f"{indent}为潜在GUI元素添加标签: {nodePath.getName()}") + nodePath.setTag("is_gui_element", "1") + nodePath.setTag("is_scene_element", "1") + name_lower = nodePath.getName().lower() + if "button" in name_lower: + nodePath.setTag("gui_type", "button") + elif "label" in name_lower: + nodePath.setTag("gui_type", "label") + elif "entry" in name_lower: + nodePath.setTag("gui_type", "entry") + elif "image" in name_lower: + nodePath.setTag("gui_type", "image") + elif "video" in name_lower or "screen" in name_lower: + nodePath.setTag("gui_type", "video_screen") + else: + nodePath.setTag("gui_type", "unknown") + + nodePath.clearMaterial() + nodePath.clearColor() + + if nodePath.hasTag("transform_pos"): + pos = self._parse_vec3_string(nodePath.getTag("transform_pos")) + nodePath.setPos(pos) + print(f"{indent}恢复位置: {pos}") + + if nodePath.hasTag("transform_hpr"): + hpr = self._parse_vec3_string(nodePath.getTag("transform_hpr")) + nodePath.setHpr(hpr) + print(f"{indent}恢复旋转: {hpr}") + + if nodePath.hasTag("transform_scale"): + scale = self._parse_vec3_string(nodePath.getTag("transform_scale")) + nodePath.setScale(scale) + print(f"{indent}恢复缩放: {scale}") + + user_visible = True + if nodePath.hasTag("user_visible"): + user_visible = nodePath.getTag("user_visible").lower() == "true" + + nodePath.setPythonTag("user_visible", user_visible) + if hasattr(self.world, 'property_panel'): + self.world.property_panel._syncEffectiveVisibility(nodePath) + else: + if user_visible: + nodePath.show() + else: + nodePath.hide() + + if nodePath.hasTag("has_scripts") and nodePath.getTag("has_scripts") == "true": + self._restore_node_scripts(nodePath) + + material = Material() + material_changed = False + if nodePath.hasTag("material_ambient"): + material.setAmbient(self._parse_color_string(nodePath.getTag("material_ambient"))) + material_changed = True + if nodePath.hasTag("material_diffuse"): + material.setDiffuse(self._parse_color_string(nodePath.getTag("material_diffuse"))) + material_changed = True + if nodePath.hasTag("material_specular"): + material.setSpecular(self._parse_color_string(nodePath.getTag("material_specular"))) + material_changed = True + if nodePath.hasTag("material_emission"): + material.setEmission(self._parse_color_string(nodePath.getTag("material_emission"))) + material_changed = True + if nodePath.hasTag("material_shininess"): + material.setShininess(float(nodePath.getTag("material_shininess"))) + material_changed = True + if nodePath.hasTag("material_basecolor"): + material.setBaseColor(self._parse_color_string(nodePath.getTag("material_basecolor"))) + material_changed = True + if material_changed: + nodePath.setMaterial(material) + + if nodePath.hasTag("color"): + nodePath.setColor(self._parse_color_string(nodePath.getTag("color"))) + + if nodePath.hasTag("light_type"): + light_type = nodePath.getTag("light_type") + print(f"{indent}检测到光源类型: {light_type}") + if nodePath not in processed_lights: + if light_type == "spot_light": + self._recreateSpotLight(nodePath) + elif light_type == "point_light": + self._recreatePointLight(nodePath) + processed_lights.append(nodePath) + + if not (nodePath.hasTag("gui_type") or nodePath.hasTag("is_gui_element")): + if nodePath.getParent() != self.world.render and nodePath.getName() not in ["render", "aspect2d", "render2d"]: + nodePath.wrtReparentTo(self.world.render) + + if nodePath.hasTag("is_model_root"): + print(f"J{indent}处理模型节点{nodePath.getName()}") + self._fixModelStructure(nodePath) + self._restoreModelAnimationInfo(nodePath) + self._processModelAnimations(nodePath) + self.models.append(nodePath) + + for child in nodePath.getChildren(): + self._process_loaded_scene_node(child, loaded_nodes, processed_lights, depth + 1) + + def _restore_node_scripts(self, nodePath): + """恢复节点挂载脚本。""" + if not hasattr(self.world, 'script_manager') or not self.world.script_manager: + return + try: + scripts_info = json.loads(nodePath.getTag("scripts_info")) + print(f"节点 {nodePath.getName()} 需要重新挂载 {len(scripts_info)} 个脚本") + + script_manager = self.world.script_manager + for script_info in scripts_info: + script_name = script_info["name"] + script_file = script_info.get("file", "") + print(f"尝试重新挂载脚本{script_name}from {script_file}") + + if script_name not in script_manager.loader.script_classes: + if script_file and os.path.exists(script_file): + print(f"从文件加载脚本:{script_file}") + loaded_class = script_manager.load_script_from_file(script_file) + if loaded_class is None: + print(f"从文件加载脚本失败{script_file}") + script_path = self._find_script_in_directory(script_name) + if script_path: + print(f"从目录找到脚本并加载{script_path}") + script_manager.load_script_from_file(script_path) + else: + script_path = self._find_script_in_directory(script_name) + if script_path: + print(f"从目录找到脚本并加载: {script_path}") + script_manager.load_script_from_file(script_path) + else: + print(f"找不到脚本文件: {script_name}") + + if script_name in script_manager.loader.script_classes: + script_component = script_manager.add_script_to_object(nodePath, script_name) + if script_component: + print(f"成功为 {nodePath.getName()} 添加脚本: {script_name}") + else: + print(f"为 {nodePath.getName()} 添加脚本失败: {script_name}") + else: + print(f"脚本 {script_name} 不可用,跳过挂载") + except Exception as e: + print(f"重新挂载脚本失败: {e}") + import traceback + traceback.print_exc() + + def _parse_vec3_string(self, vec_str): + """解析向量字符串为 Vec3。""" + try: + vec_str = vec_str.replace('LVecBase3f', '').replace('LPoint3f', '').strip('()') + x, y, z = map(float, vec_str.split(',')) + return Vec3(x, y, z) + except Exception as e: + print(f"解析向量失败: {vec_str}, 错误: {e}") + return Vec3(0, 0, 0) + + def _parse_color_string(self, color_str): + """解析颜色字符串为 Vec4。""" + try: + color_str = color_str.replace('LVecBase4f', '').strip('()') + r, g, b, a = map(float, color_str.split(',')) + return Vec4(r, g, b, a) + except Exception: + return Vec4(1, 1, 1, 1) + def _preflight_load_scene(self, filename, retry_count): """预检加载参数并在重试前做预清理。""" filename = os.path.normpath(filename)