IMGui
This commit is contained in:
parent
26b8e0d93f
commit
b7abab4f93
3
.gitignore
vendored
3
.gitignore
vendored
@ -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
|
||||
|
||||
16
imgui.ini
16
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
|
||||
|
||||
@ -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"
|
||||
@ -152,6 +154,10 @@ class ObjectController:
|
||||
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, "
|
||||
f"{len(self.vertex_index)} unique IDs indexed")
|
||||
@ -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 = {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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):
|
||||
new_node = None
|
||||
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:
|
||||
new_node = source_node.copyTo(parent_node)
|
||||
created = source_node.copyTo(parent)
|
||||
if hasattr(self.scene_manager, "_generateUniqueName"):
|
||||
unique_name = self.scene_manager._generateUniqueName(source_node.getName(), parent_node)
|
||||
new_node.setName(unique_name)
|
||||
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.
|
||||
new_node.setPos(new_node.getX() + 0.2, new_node.getY() + 0.2, new_node.getZ())
|
||||
created.setPos(created.getX() + 0.2, created.getY() + 0.2, created.getZ())
|
||||
except Exception:
|
||||
new_node = None
|
||||
created = None
|
||||
|
||||
# Fallback: recreate from serialized data.
|
||||
if not new_node:
|
||||
if not created:
|
||||
if hasattr(self.scene_manager, "recreateNodeFromData"):
|
||||
new_node = self.scene_manager.recreateNodeFromData(node_data, parent_node)
|
||||
created = self.scene_manager.recreateNodeFromData(node_data, parent)
|
||||
else:
|
||||
new_node = self.scene_manager.deserializeNode(node_data, parent_node)
|
||||
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)
|
||||
|
||||
if new_node:
|
||||
created_any = True
|
||||
# Ensure pasted model can be picked by legacy collision fallback.
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user