From b7abab4f9322c33a081639f7842b689171c3657f Mon Sep 17 00:00:00 2001 From: Hector <145347438+hudomn@users.noreply.github.com> Date: Wed, 25 Feb 2026 14:27:43 +0800 Subject: [PATCH] IMGui --- .gitignore | 3 ++ imgui.ini | 16 ++++----- ssbo_component/ssbo_controller.py | 23 ++++++++++++ ssbo_component/ssbo_editor.py | 5 ++- ui/panels/app_actions.py | 60 ++++++++++++++++++++----------- ui/panels/editor_panels.py | 53 +++++++++++++++++++++------ 6 files changed, 121 insertions(+), 39 deletions(-) diff --git a/.gitignore b/.gitignore index 81a70abe..3b870ce6 100644 --- a/.gitignore +++ b/.gitignore @@ -55,3 +55,6 @@ Subjects/test_maintenance_config.json Resources/models/Women_1.glb Resources/models/Women_1.glb Resources/models/Women_1.glb +/venv/ +/engine/ +/panda3d_imgui-1.1.0-py3-none-any.whl diff --git a/imgui.ini b/imgui.ini index 35d46e5f..56503e61 100644 --- a/imgui.ini +++ b/imgui.ini @@ -24,14 +24,14 @@ Size=832,45 Collapsed=0 [Window][工具栏] -Pos=325,20 -Size=1228,32 +Pos=323,20 +Size=1230,32 Collapsed=0 DockId=0x0000000D,0 [Window][场景树] Pos=0,20 -Size=323,634 +Size=321,634 Collapsed=0 DockId=0x00000007,0 @@ -43,7 +43,7 @@ DockId=0x00000003,0 [Window][控制台] Pos=0,656 -Size=323,353 +Size=321,353 Collapsed=0 DockId=0x00000008,0 @@ -99,8 +99,8 @@ Size=600,500 Collapsed=0 [Window][资源管理器] -Pos=325,827 -Size=1228,182 +Pos=323,827 +Size=1230,182 Collapsed=0 DockId=0x00000006,0 @@ -198,10 +198,10 @@ Collapsed=0 [Docking][Data] DockSpace ID=0x08BD597D Window=0x1BBC0F80 Pos=0,20 Size=1920,989 Split=X DockNode ID=0x00000001 Parent=0x08BD597D SizeRef=1553,989 Split=X - DockNode ID=0x00000009 Parent=0x00000001 SizeRef=323,989 Split=Y Selected=0xE0015051 + DockNode ID=0x00000009 Parent=0x00000001 SizeRef=321,989 Split=Y Selected=0xE0015051 DockNode ID=0x00000007 Parent=0x00000009 SizeRef=271,634 Selected=0xE0015051 DockNode ID=0x00000008 Parent=0x00000009 SizeRef=271,353 Selected=0x5428E753 - DockNode ID=0x0000000A Parent=0x00000001 SizeRef=1228,989 Split=Y + DockNode ID=0x0000000A Parent=0x00000001 SizeRef=1230,989 Split=Y DockNode ID=0x0000000D Parent=0x0000000A SizeRef=1318,32 HiddenTabBar=1 Selected=0x43A39006 DockNode ID=0x0000000E Parent=0x0000000A SizeRef=1318,955 Split=Y DockNode ID=0x00000005 Parent=0x0000000E SizeRef=1341,771 CentralNode=1 diff --git a/ssbo_component/ssbo_controller.py b/ssbo_component/ssbo_controller.py index d6d97b4e..04ea04be 100644 --- a/ssbo_component/ssbo_controller.py +++ b/ssbo_component/ssbo_controller.py @@ -42,6 +42,7 @@ class ObjectController: self.virtual_tree_meta = None self.model = None + self.pick_model = None self.chunk_node = None # Single chunk node def bake_ids_and_collect(self, model): @@ -72,6 +73,7 @@ class ObjectController: self.local_transform_base_positions = {} self.virtual_tree = None self.virtual_tree_meta = None + self.pick_model = None global_id_counter = 0 chunk_key = model.get_name() or "default" @@ -151,6 +153,10 @@ class ObjectController: self._build_vertex_index(model) self._init_local_transform_state() self.build_virtual_hierarchy() + + # Keep ID colors only in picking clone to avoid affecting visible shading. + self.pick_model = model.copy_to(NodePath("ssbo_pick_root")) + self._set_uniform_vertex_color(model, 1.0, 1.0, 1.0, 1.0) t2 = time.time() print(f"[控制器] Vertex index built in {(t2-t1)*1000:.0f}ms, " @@ -160,6 +166,23 @@ class ObjectController: self.node_list.sort() return global_id_counter + def _set_uniform_vertex_color(self, root_np, r, g, b, a): + """ + Force vertex color to a uniform value on visible model to avoid + ID-encoding colors tinting the final render output. + """ + for gn_np in root_np.find_all_matches("**/+GeomNode"): + gnode = gn_np.node() + for gi in range(gnode.get_num_geoms()): + geom = gnode.modify_geom(gi) + vdata = geom.modify_vertex_data() + if not vdata.has_column("color"): + continue + writer = GeomVertexWriter(vdata, InternalName.make("color")) + for row in range(vdata.get_num_rows()): + writer.set_row(row) + writer.set_data4f(r, g, b, a) + def build_virtual_hierarchy(self): """Build a readonly virtual tree from node_list path keys.""" root = { diff --git a/ssbo_component/ssbo_editor.py b/ssbo_component/ssbo_editor.py index e82948f2..a19d6875 100644 --- a/ssbo_component/ssbo_editor.py +++ b/ssbo_component/ssbo_editor.py @@ -230,7 +230,10 @@ class SSBOEditor: pick_vert, pick_frag ) - self.pick_cam.set_scene(self.model) + pick_scene = getattr(self.controller, "pick_model", None) if self.controller else None + if pick_scene is None: + pick_scene = self.model + self.pick_cam.set_scene(pick_scene) initial_state = NodePath("initial") initial_state.set_shader(pick_shader, 100) # Remove global SSBO input, Chunks have their own inputs diff --git a/ui/panels/app_actions.py b/ui/panels/app_actions.py index aeb64a1d..751fb79a 100644 --- a/ui/panels/app_actions.py +++ b/ui/panels/app_actions.py @@ -608,28 +608,48 @@ class AppActions: created_any = False source_nodes = getattr(self, "clipboard_source_nodes", []) or [] for i, node_data in enumerate(self.clipboard): + def _paste_create_node(parent): + created = None + + # Copy mode: prefer direct NodePath clone to preserve visual geometry. + if self.clipboard_mode == "copy" and i < len(source_nodes): + source_node = source_nodes[i] + if source_node and not source_node.isEmpty(): + try: + created = source_node.copyTo(parent) + if hasattr(self.scene_manager, "_generateUniqueName"): + unique_name = self.scene_manager._generateUniqueName(source_node.getName(), parent) + created.setName(unique_name) + # Preserve model source tags so later cut/paste can rebuild real model. + for _tag in ("model_path", "saved_model_path", "original_path", "file", "element_type"): + if source_node.hasTag(_tag): + created.setTag(_tag, source_node.getTag(_tag)) + # Offset slightly so the new node can be seen immediately. + created.setPos(created.getX() + 0.2, created.getY() + 0.2, created.getZ()) + except Exception: + created = None + + # Fallback: recreate from serialized data. + if not created: + if hasattr(self.scene_manager, "recreateNodeFromData"): + created = self.scene_manager.recreateNodeFromData(node_data, parent) + else: + created = self.scene_manager.deserializeNode(node_data, parent) + return created + new_node = None + if hasattr(self, "command_manager") and self.command_manager: + try: + from core.Command_System import CreateNodeCommand + create_cmd = CreateNodeCommand(_paste_create_node, parent_node) + self.command_manager.execute_command(create_cmd) + new_node = create_cmd.created_node + except Exception as e: + print(f"[Paste] command create failed, fallback direct create: {e}") + new_node = _paste_create_node(parent_node) + else: + new_node = _paste_create_node(parent_node) - # Copy mode: prefer direct NodePath clone to preserve visual geometry. - if self.clipboard_mode == "copy" and i < len(source_nodes): - source_node = source_nodes[i] - if source_node and not source_node.isEmpty(): - try: - new_node = source_node.copyTo(parent_node) - if hasattr(self.scene_manager, "_generateUniqueName"): - unique_name = self.scene_manager._generateUniqueName(source_node.getName(), parent_node) - new_node.setName(unique_name) - # Offset slightly so the new node can be seen immediately. - new_node.setPos(new_node.getX() + 0.2, new_node.getY() + 0.2, new_node.getZ()) - except Exception: - new_node = None - - # Fallback: recreate from serialized data. - if not new_node: - if hasattr(self.scene_manager, "recreateNodeFromData"): - new_node = self.scene_manager.recreateNodeFromData(node_data, parent_node) - else: - new_node = self.scene_manager.deserializeNode(node_data, parent_node) if new_node: created_any = True # Ensure pasted model can be picked by legacy collision fallback. diff --git a/ui/panels/editor_panels.py b/ui/panels/editor_panels.py index 90be29ff..17a5c662 100644 --- a/ui/panels/editor_panels.py +++ b/ui/panels/editor_panels.py @@ -511,21 +511,54 @@ class EditorPanels: if not model or model != node or not controller: return False - node_list = getattr(controller, "node_list", None) or [] - if not node_list: + tree_root = controller.get_virtual_hierarchy() if hasattr(controller, "get_virtual_hierarchy") else None + if not tree_root or not tree_root.get("children"): imgui.text_disabled("(无可用子节点)") return True - selected_key = getattr(ssbo_editor, "selected_name", None) - for idx, key in enumerate(node_list): - display = controller.display_names.get(key, key.split("/")[-1]) - is_selected = (selected_key == key) - if imgui.selectable(f"{display}##ssbo_node_{idx}", is_selected)[0]: - ssbo_editor.select_node(key) - # 与旧系统保持一致:选择场景树项时清理LUI选择 + for name in sorted(tree_root["children"].keys()): + child = tree_root["children"][name] + self._draw_ssbo_virtual_tree_node(ssbo_editor, child, "ssbo_root") + return True + + def _draw_ssbo_virtual_tree_node(self, ssbo_editor, tree_node, unique_id_prefix, depth=0): + """Recursively draw virtual SSBO hierarchy in scene tree.""" + if not tree_node: + return + path = tree_node.get("path", "") + display = tree_node.get("display_name") or tree_node.get("name") or path + leaf_key = tree_node.get("leaf_key") + children = tree_node.get("children", {}) or {} + + label = f"{display}##{unique_id_prefix}_{path}" + + # Leaf: selectable to trigger SSBO selection. + if not children and leaf_key: + is_selected = (getattr(ssbo_editor, "selected_name", None) == leaf_key) + if imgui.selectable(label, is_selected)[0]: + ssbo_editor.select_node(leaf_key) if hasattr(self.app, "lui_manager"): self.app.lui_manager.selected_index = -1 - return True + return + + # Non-leaf: tree node only for hierarchy display. + opened = imgui.tree_node(label) + if opened: + # If this node is also a selectable leaf, render selectable entry first. + if leaf_key: + is_selected = (getattr(ssbo_editor, "selected_name", None) == leaf_key) + if imgui.selectable(f"[节点] {display}##leaf_{unique_id_prefix}_{path}", is_selected)[0]: + ssbo_editor.select_node(leaf_key) + if hasattr(self.app, "lui_manager"): + self.app.lui_manager.selected_index = -1 + for child_name in sorted(children.keys()): + self._draw_ssbo_virtual_tree_node( + ssbo_editor, + children[child_name], + unique_id_prefix, + depth + 1, + ) + imgui.tree_pop() def _show_node_context_menu(self, node, name, node_type): """显示节点右键菜单""" self.app._context_menu_node = True