From 8a80af9825d3d8dd269dea7143fa9e21f52f5b95 Mon Sep 17 00:00:00 2001 From: ayuan9957 <107920784+ayuan9957@users.noreply.github.com> Date: Fri, 13 Mar 2026 14:15:38 +0800 Subject: [PATCH] Fix light registration handling --- core/Command_System.py | 153 ++++++++++++++++++++++++----- core/terrain_manager.py | 46 ++++----- scene/scene_manager_light_mixin.py | 4 + ui/panels/dialog_panels.py | 78 ++++++++++----- 4 files changed, 208 insertions(+), 73 deletions(-) diff --git a/core/Command_System.py b/core/Command_System.py index 5e301764..9c0c5ede 100644 --- a/core/Command_System.py +++ b/core/Command_System.py @@ -8,10 +8,109 @@ def _is_valid_node(node) -> bool: return bool(node) and hasattr(node, "isEmpty") and (not node.isEmpty()) +def _is_light_node(node: NodePath) -> bool: + return bool(node) and hasattr(node, "hasTag") and node.hasTag("light_type") + + +def _is_terrain_node(node: NodePath) -> bool: + return bool(node) and hasattr(node, "hasTag") and node.hasTag("tree_item_type") and node.getTag("tree_item_type") == "TERRAIN_NODE" + + +def _set_light_registration(world, node: NodePath, registered: bool): + if not world or not _is_valid_node(node) or not _is_light_node(node): + return + + scene_manager = getattr(world, "scene_manager", None) + light_type = node.getTag("light_type") + light_lists = [] + if scene_manager: + if light_type == "spot_light" and hasattr(scene_manager, "Spotlight"): + light_lists.append(scene_manager.Spotlight) + elif light_type == "point_light" and hasattr(scene_manager, "Pointlight"): + light_lists.append(scene_manager.Pointlight) + + rp_light = node.getPythonTag("rp_light_object") if hasattr(node, "hasPythonTag") and node.hasPythonTag("rp_light_object") else None + current_registered = bool(node.getPythonTag("engine_light_registered")) if hasattr(node, "hasPythonTag") and node.hasPythonTag("engine_light_registered") else False + + if registered: + for light_list in light_lists: + if node not in light_list: + light_list.append(node) + if not current_registered: + try: + if rp_light is not None and getattr(world, "render_pipeline", None): + world.render_pipeline.add_light(rp_light) + elif hasattr(world, "render") and world.render: + world.render.setLight(node) + except Exception: + pass + try: + node.setPythonTag("engine_light_registered", True) + except Exception: + pass + return + + for light_list in light_lists: + try: + while node in light_list: + light_list.remove(node) + except Exception: + pass + if current_registered: + try: + if rp_light is not None and getattr(world, "render_pipeline", None): + world.render_pipeline.remove_light(rp_light) + elif hasattr(world, "render") and world.render: + world.render.clearLight(node) + except Exception: + pass + try: + node.setPythonTag("engine_light_registered", False) + except Exception: + pass + + +def _set_terrain_registration(world, node: NodePath, registered: bool): + if not world or not _is_valid_node(node) or not _is_terrain_node(node): + return + + terrain_manager = getattr(world, "terrain_manager", None) + if not terrain_manager or not hasattr(terrain_manager, "terrains"): + return + + terrain_info = None + if hasattr(node, "hasPythonTag") and node.hasPythonTag("terrain_info"): + terrain_info = node.getPythonTag("terrain_info") + else: + for info in getattr(terrain_manager, "terrains", []): + if info.get("node") == node: + terrain_info = info + break + if terrain_info is not None: + try: + node.setPythonTag("terrain_info", terrain_info) + except Exception: + pass + + if registered: + if terrain_info is not None: + terrain_info["node"] = node + if all(info.get("node") != node for info in terrain_manager.terrains): + terrain_manager.terrains.append(terrain_info) + return + + try: + terrain_manager.terrains = [info for info in terrain_manager.terrains if info.get("node") != node] + except Exception: + pass + + def _register_scene_node(world, node: NodePath): if not world or not _is_valid_node(node): return scene_manager = getattr(world, "scene_manager", None) + _set_light_registration(world, node, True) + _set_terrain_registration(world, node, True) if scene_manager and hasattr(scene_manager, "models") and node not in scene_manager.models: scene_manager.models.append(node) try: @@ -25,6 +124,8 @@ def _unregister_scene_node(world, node: NodePath): if not world or not node: return scene_manager = getattr(world, "scene_manager", None) + _set_light_registration(world, node, False) + _set_terrain_registration(world, node, False) if scene_manager and hasattr(scene_manager, "models"): try: while node in scene_manager.models: @@ -331,11 +432,11 @@ class DeleteNodeCommand(Command): if node.hasTag("tileset_url"): self.extra_data["tileset_url"] = node.getTag("tileset_url") - def execute(self): - """ - 执行删除操作 - """ - # 从world的相应列表中移除节点引用 + def execute(self): + """ + 执行删除操作 + """ + # 从world的相应列表中移除节点引用 if self.world and hasattr(self.world, 'scene_manager'): scene_manager = self.world.scene_manager if self.node_type == "LIGHT_NODE": @@ -353,19 +454,21 @@ class DeleteNodeCommand(Command): if self.node_type.startswith("GUI_") and hasattr(self.world, 'gui_elements') and self.node in self.world.gui_elements: self.world.gui_elements.remove(self.node) - elif self.node_type == "CESIUM_TILESET_NODE": - # 从tilesets列表中移除 - if hasattr(scene_manager, 'tilesets'): - tilesets_to_remove = [] - for i, tileset_info in enumerate(scene_manager.tilesets): + elif self.node_type == "CESIUM_TILESET_NODE": + # 从tilesets列表中移除 + if hasattr(scene_manager, 'tilesets'): + tilesets_to_remove = [] + for i, tileset_info in enumerate(scene_manager.tilesets): if tileset_info.get('node') == self.node: tilesets_to_remove.append(i) - for i in reversed(tilesets_to_remove): - del scene_manager.tilesets[i] - - # 从场景图中移除节点,使用 detachNode 而不是 removeNode 以便可以撤销 - if self.node and not self.node.isEmpty(): - self.node.detachNode() + for i in reversed(tilesets_to_remove): + del scene_manager.tilesets[i] + + _unregister_scene_node(self.world, self.node) + + # 从场景图中移除节点,使用 detachNode 而不是 removeNode 以便可以撤销 + if self.node and not self.node.isEmpty(): + self.node.detachNode() def undo(self): """ @@ -392,14 +495,16 @@ class DeleteNodeCommand(Command): if self.node_type.startswith("GUI_") and hasattr(self.world, 'gui_elements') and self.node not in self.world.gui_elements: self.world.gui_elements.append(self.node) - elif self.node_type == "CESIUM_TILESET_NODE": - # 简单恢复到 tilesets - if hasattr(scene_manager, 'tilesets'): - scene_manager.tilesets.append({'node': self.node, 'url': self.extra_data.get('tileset_url', '')}) - - print(f"✅ 成功撤销删除操作,节点 {self.node_name} 已恢复") - else: - print("❌ 无法撤销删除操作,节点引用已丢失") + elif self.node_type == "CESIUM_TILESET_NODE": + # 简单恢复到 tilesets + if hasattr(scene_manager, 'tilesets'): + scene_manager.tilesets.append({'node': self.node, 'url': self.extra_data.get('tileset_url', '')}) + + _register_scene_node(self.world, self.node) + + print(f"✅ 成功撤销删除操作,节点 {self.node_name} 已恢复") + else: + print("❌ 无法撤销删除操作,节点引用已丢失") except Exception as e: print(f"❌ 撤销删除操作时出错: {e}") diff --git a/core/terrain_manager.py b/core/terrain_manager.py index 1f5003e6..254ce53b 100644 --- a/core/terrain_manager.py +++ b/core/terrain_manager.py @@ -129,19 +129,20 @@ class TerrainManager: terrain_node.setPythonTag("selectable", True) # 保存地形信息(包括高度图的副本) - terrain_info = { - 'terrain': terrain, - 'node': terrain_node, - 'heightmap': heightmap_path, - 'heightfield': height_image, # 保存高度图副本 + terrain_info = { + 'terrain': terrain, + 'node': terrain_node, + 'heightmap': heightmap_path, + 'heightfield': height_image, # 保存高度图副本 'scale': scale, 'name': node_name - } - - self.terrains.append(terrain_info) - - parent_name = parent_item.text(0) if parent_item else "root" - print(f"✅ 为 {parent_name} 创建高度图地形: {terrain_name}") + } + + self.terrains.append(terrain_info) + terrain_node.setPythonTag("terrain_info", terrain_info) + + parent_name = parent_item.text(0) if parent_item else "root" + print(f"✅ 为 {parent_name} 创建高度图地形: {terrain_name}") # 在Qt树形控件中添加对应节点 qt_item = None @@ -278,19 +279,20 @@ class TerrainManager: terrain_node.setPythonTag("selectable", True) # 保存地形信息(包括高度图) - terrain_info = { - 'terrain': terrain, - 'node': terrain_node, - 'heightmap': None, - 'heightfield': height_image, # 保存高度图 + terrain_info = { + 'terrain': terrain, + 'node': terrain_node, + 'heightmap': None, + 'heightfield': height_image, # 保存高度图 'scale': (size[0], size[1], 50), 'name': node_name - } - - self.terrains.append(terrain_info) - - parent_name = parent_item.text(0) if parent_item else "root" - print(f"✅ 为 {parent_name} 创建平面地形: {terrain_name}") + } + + self.terrains.append(terrain_info) + terrain_node.setPythonTag("terrain_info", terrain_info) + + parent_name = parent_item.text(0) if parent_item else "root" + print(f"✅ 为 {parent_name} 创建平面地形: {terrain_name}") # 在Qt树形控件中添加对应节点 qt_item = None diff --git a/scene/scene_manager_light_mixin.py b/scene/scene_manager_light_mixin.py index 72cc8b70..b9b1a4e9 100644 --- a/scene/scene_manager_light_mixin.py +++ b/scene/scene_manager_light_mixin.py @@ -326,6 +326,7 @@ class SceneManagerLightMixin: spotlight_node.setTag("created_by_user", "1") spotlight_node.setTag("element_type", "spotlight") spotlight_node.setPythonTag("rp_light_object", spotlight) + spotlight_node.setPythonTag("engine_light_registered", True) self.Spotlight.append(spotlight_node) return spotlight_node else: @@ -348,6 +349,7 @@ class SceneManagerLightMixin: spotlight_node.setTag("tree_item_type", "LIGHT_NODE") spotlight_node.setTag("created_by_user", "1") spotlight_node.setTag("element_type", "spotlight") + spotlight_node.setPythonTag("engine_light_registered", True) # 设置聚光灯方向(向下照射) spotlight_node.lookAt(pos[0], pos[1], pos[2] - 5) # 向下看5个单位 @@ -414,6 +416,7 @@ class SceneManagerLightMixin: pointlight_node.setTag("created_by_user", "1") pointlight_node.setTag("element_type", "pointlight") pointlight_node.setPythonTag("rp_light_object", pointlight) + pointlight_node.setPythonTag("engine_light_registered", True) self.Pointlight.append(pointlight_node) return pointlight_node else: @@ -436,6 +439,7 @@ class SceneManagerLightMixin: pointlight_node.setTag("tree_item_type", "LIGHT_NODE") pointlight_node.setTag("created_by_user", "1") pointlight_node.setTag("element_type", "pointlight") + pointlight_node.setPythonTag("engine_light_registered", True) # 添加到光源列表 self.Pointlight.append(pointlight_node) diff --git a/ui/panels/dialog_panels.py b/ui/panels/dialog_panels.py index 39c67dd4..f32e52c3 100644 --- a/ui/panels/dialog_panels.py +++ b/ui/panels/dialog_panels.py @@ -19,6 +19,21 @@ class DialogPanels: else: setattr(self.app, name, value) + def _execute_scene_create_command(self, creator): + """Create a scene node through the shared undo/redo stack.""" + command_manager = getattr(self, "command_manager", None) + if not command_manager: + return creator() + + from core.Command_System import CreateNodeCommand + + command = CreateNodeCommand(lambda _parent: creator(), getattr(self, "render", None), world=self) + command_manager.execute_command(command) + if not command.created_node: + command_manager.pop_last_command() + return None + return command.created_node + def _draw_new_project_dialog(self): """绘制新建项目对话框""" @@ -554,17 +569,20 @@ class DialogPanels: if imgui.button("创建"): try: pos = tuple(self.dialog_params['spotlight_pos']) - - result = self.createSpotLight(pos) + + def create_spotlight(): + result = self.createSpotLight(pos) + if result: + light = result.node() + if hasattr(light, 'setColor'): + color = tuple(self.dialog_params['spotlight_color']) + light.setColor(color + (1.0,)) + if hasattr(light, 'setEnergy'): + light.setEnergy(self.dialog_params['spotlight_intensity']) + return result + + result = self._execute_scene_create_command(create_spotlight) if result: - # 设置颜色和强度 - light = result.node() - if hasattr(light, 'setColor'): - color = tuple(self.dialog_params['spotlight_color']) - light.setColor(color + (1.0,)) # 添加alpha通道 - if hasattr(light, 'setEnergy'): - light.setEnergy(self.dialog_params['spotlight_intensity']) - self.add_success_message("聚光灯创建成功") self.show_spot_light_dialog = False else: @@ -643,21 +661,23 @@ class DialogPanels: if imgui.button("创建"): try: pos = tuple(self.dialog_params['pointlight_pos']) - - result = self.createPointLight(pos) + + def create_pointlight(): + result = self.createPointLight(pos) + if result: + light = result.node() + if hasattr(light, 'setColor'): + color = tuple(self.dialog_params['pointlight_color']) + light.setColor(color + (1.0,)) + if hasattr(light, 'setEnergy'): + light.setEnergy(self.dialog_params['pointlight_intensity']) + if hasattr(light, 'setAttenuation'): + radius = self.dialog_params['pointlight_radius'] + light.setAttenuation((1.0, 0.5 / radius, 0.5 / (radius * radius))) + return result + + result = self._execute_scene_create_command(create_pointlight) if result: - # 设置颜色和强度 - light = result.node() - if hasattr(light, 'setColor'): - color = tuple(self.dialog_params['pointlight_color']) - light.setColor(color + (1.0,)) # 添加alpha通道 - if hasattr(light, 'setEnergy'): - light.setEnergy(self.dialog_params['pointlight_intensity']) - if hasattr(light, 'setAttenuation'): - # 设置衰减: (constant, linear, quadratic) - radius = self.dialog_params['pointlight_radius'] - light.setAttenuation((1.0, 0.5/radius, 0.5/(radius*radius))) - self.add_success_message("点光源创建成功") self.show_point_light_dialog = False else: @@ -728,8 +748,10 @@ class DialogPanels: # 转换为地形管理器期望的格式 size = (width, height) - - result = self.createFlatTerrain(size, resolution) + + result = self._execute_scene_create_command( + lambda: self.createFlatTerrain(size, resolution) + ) if result: self.add_success_message("平面地形创建成功") self.show_terrain_dialog = False @@ -1061,7 +1083,9 @@ class DialogPanels: try: # 使用默认缩放参数创建地形 scale = (1.0, 1.0, 10.0) # X, Y, Z缩放 - result = self.createTerrainFromHeightMap(self.heightmap_file_path, scale) + result = self._execute_scene_create_command( + lambda: self.createTerrainFromHeightMap(self.heightmap_file_path, scale) + ) if result: self.add_success_message("高度图地形创建成功") self.show_heightmap_browser = False