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
|
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
|
Collapsed=0
|
||||||
|
|
||||||
[Window][工具栏]
|
[Window][工具栏]
|
||||||
Pos=325,20
|
Pos=323,20
|
||||||
Size=1228,32
|
Size=1230,32
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x0000000D,0
|
DockId=0x0000000D,0
|
||||||
|
|
||||||
[Window][场景树]
|
[Window][场景树]
|
||||||
Pos=0,20
|
Pos=0,20
|
||||||
Size=323,634
|
Size=321,634
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000007,0
|
DockId=0x00000007,0
|
||||||
|
|
||||||
@ -43,7 +43,7 @@ DockId=0x00000003,0
|
|||||||
|
|
||||||
[Window][控制台]
|
[Window][控制台]
|
||||||
Pos=0,656
|
Pos=0,656
|
||||||
Size=323,353
|
Size=321,353
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000008,0
|
DockId=0x00000008,0
|
||||||
|
|
||||||
@ -99,8 +99,8 @@ Size=600,500
|
|||||||
Collapsed=0
|
Collapsed=0
|
||||||
|
|
||||||
[Window][资源管理器]
|
[Window][资源管理器]
|
||||||
Pos=325,827
|
Pos=323,827
|
||||||
Size=1228,182
|
Size=1230,182
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000006,0
|
DockId=0x00000006,0
|
||||||
|
|
||||||
@ -198,10 +198,10 @@ Collapsed=0
|
|||||||
[Docking][Data]
|
[Docking][Data]
|
||||||
DockSpace ID=0x08BD597D Window=0x1BBC0F80 Pos=0,20 Size=1920,989 Split=X
|
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=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=0x00000007 Parent=0x00000009 SizeRef=271,634 Selected=0xE0015051
|
||||||
DockNode ID=0x00000008 Parent=0x00000009 SizeRef=271,353 Selected=0x5428E753
|
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=0x0000000D Parent=0x0000000A SizeRef=1318,32 HiddenTabBar=1 Selected=0x43A39006
|
||||||
DockNode ID=0x0000000E Parent=0x0000000A SizeRef=1318,955 Split=Y
|
DockNode ID=0x0000000E Parent=0x0000000A SizeRef=1318,955 Split=Y
|
||||||
DockNode ID=0x00000005 Parent=0x0000000E SizeRef=1341,771 CentralNode=1
|
DockNode ID=0x00000005 Parent=0x0000000E SizeRef=1341,771 CentralNode=1
|
||||||
|
|||||||
@ -42,6 +42,7 @@ class ObjectController:
|
|||||||
self.virtual_tree_meta = None
|
self.virtual_tree_meta = None
|
||||||
|
|
||||||
self.model = None
|
self.model = None
|
||||||
|
self.pick_model = None
|
||||||
self.chunk_node = None # Single chunk node
|
self.chunk_node = None # Single chunk node
|
||||||
|
|
||||||
def bake_ids_and_collect(self, model):
|
def bake_ids_and_collect(self, model):
|
||||||
@ -72,6 +73,7 @@ class ObjectController:
|
|||||||
self.local_transform_base_positions = {}
|
self.local_transform_base_positions = {}
|
||||||
self.virtual_tree = None
|
self.virtual_tree = None
|
||||||
self.virtual_tree_meta = None
|
self.virtual_tree_meta = None
|
||||||
|
self.pick_model = None
|
||||||
|
|
||||||
global_id_counter = 0
|
global_id_counter = 0
|
||||||
chunk_key = model.get_name() or "default"
|
chunk_key = model.get_name() or "default"
|
||||||
@ -151,6 +153,10 @@ class ObjectController:
|
|||||||
self._build_vertex_index(model)
|
self._build_vertex_index(model)
|
||||||
self._init_local_transform_state()
|
self._init_local_transform_state()
|
||||||
self.build_virtual_hierarchy()
|
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()
|
t2 = time.time()
|
||||||
print(f"[控制器] Vertex index built in {(t2-t1)*1000:.0f}ms, "
|
print(f"[控制器] Vertex index built in {(t2-t1)*1000:.0f}ms, "
|
||||||
@ -160,6 +166,23 @@ class ObjectController:
|
|||||||
self.node_list.sort()
|
self.node_list.sort()
|
||||||
return global_id_counter
|
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):
|
def build_virtual_hierarchy(self):
|
||||||
"""Build a readonly virtual tree from node_list path keys."""
|
"""Build a readonly virtual tree from node_list path keys."""
|
||||||
root = {
|
root = {
|
||||||
|
|||||||
@ -230,7 +230,10 @@ class SSBOEditor:
|
|||||||
pick_vert,
|
pick_vert,
|
||||||
pick_frag
|
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 = NodePath("initial")
|
||||||
initial_state.set_shader(pick_shader, 100)
|
initial_state.set_shader(pick_shader, 100)
|
||||||
# Remove global SSBO input, Chunks have their own inputs
|
# Remove global SSBO input, Chunks have their own inputs
|
||||||
|
|||||||
@ -608,28 +608,48 @@ class AppActions:
|
|||||||
created_any = False
|
created_any = False
|
||||||
source_nodes = getattr(self, "clipboard_source_nodes", []) or []
|
source_nodes = getattr(self, "clipboard_source_nodes", []) or []
|
||||||
for i, node_data in enumerate(self.clipboard):
|
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
|
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:
|
if new_node:
|
||||||
created_any = True
|
created_any = True
|
||||||
# Ensure pasted model can be picked by legacy collision fallback.
|
# 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:
|
if not model or model != node or not controller:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
node_list = getattr(controller, "node_list", None) or []
|
tree_root = controller.get_virtual_hierarchy() if hasattr(controller, "get_virtual_hierarchy") else None
|
||||||
if not node_list:
|
if not tree_root or not tree_root.get("children"):
|
||||||
imgui.text_disabled("(无可用子节点)")
|
imgui.text_disabled("(无可用子节点)")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
selected_key = getattr(ssbo_editor, "selected_name", None)
|
for name in sorted(tree_root["children"].keys()):
|
||||||
for idx, key in enumerate(node_list):
|
child = tree_root["children"][name]
|
||||||
display = controller.display_names.get(key, key.split("/")[-1])
|
self._draw_ssbo_virtual_tree_node(ssbo_editor, child, "ssbo_root")
|
||||||
is_selected = (selected_key == key)
|
return True
|
||||||
if imgui.selectable(f"{display}##ssbo_node_{idx}", is_selected)[0]:
|
|
||||||
ssbo_editor.select_node(key)
|
def _draw_ssbo_virtual_tree_node(self, ssbo_editor, tree_node, unique_id_prefix, depth=0):
|
||||||
# 与旧系统保持一致:选择场景树项时清理LUI选择
|
"""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"):
|
if hasattr(self.app, "lui_manager"):
|
||||||
self.app.lui_manager.selected_index = -1
|
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):
|
def _show_node_context_menu(self, node, name, node_type):
|
||||||
"""显示节点右键菜单"""
|
"""显示节点右键菜单"""
|
||||||
self.app._context_menu_node = True
|
self.app._context_menu_node = True
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user