Merge these animation changes from hu_migrate_on_geng into your branch:
This commit is contained in:
parent
c14c2b8796
commit
f17073160c
@ -10,7 +10,7 @@
|
||||
"importer": "model_importer",
|
||||
"import_settings": {},
|
||||
"dependency_guids": [],
|
||||
"updated_at": "2026-03-19 10:14:56",
|
||||
"updated_at": "2026-03-19 17:17:48",
|
||||
"imported_cache": {
|
||||
"root": "Library/Imported/90ece77e67b54ccda38d2de71cb4694d",
|
||||
"model_bam": "Library/Imported/90ece77e67b54ccda38d2de71cb4694d/model.bam",
|
||||
|
||||
@ -3,5 +3,5 @@
|
||||
"asset_path": "Assets/Models/box1.glb",
|
||||
"asset_type": "model",
|
||||
"source_hash": "fc694905c47a9b0005d77b701cc41852b56ef08c7406829a306e98f3ce158a64",
|
||||
"generated_at": "2026-03-19 10:14:56"
|
||||
"generated_at": "2026-03-19 17:17:48"
|
||||
}
|
||||
@ -2099,18 +2099,22 @@ class SelectionSystem:
|
||||
except Exception as e:
|
||||
print(f"同步 SSBO 选择状态失败: {e}")
|
||||
|
||||
self.selectedNode = nodePath
|
||||
# 添加兼容性属性
|
||||
self.selectedObject = nodePath
|
||||
effective_node = self._get_effective_selected_node()
|
||||
if effective_node is None:
|
||||
effective_node = nodePath
|
||||
|
||||
if self._is_valid_node(nodePath, require_attached=True):
|
||||
node_name = nodePath.getName()
|
||||
self.selectedNode = effective_node
|
||||
# 添加兼容性属性
|
||||
self.selectedObject = effective_node
|
||||
|
||||
if self._is_valid_node(effective_node, require_attached=True):
|
||||
node_name = effective_node.getName()
|
||||
#print(f"开始为节点 {node_name} 创建选择框和坐标轴...")
|
||||
|
||||
# 创建选择框
|
||||
#print("创建选择框...")
|
||||
if self.show_selection_box:
|
||||
self.createSelectionBox(nodePath)
|
||||
self.createSelectionBox(effective_node)
|
||||
else:
|
||||
self.clearSelectionBox()
|
||||
|
||||
@ -2124,12 +2128,12 @@ class SelectionSystem:
|
||||
|
||||
# 创建坐标轴
|
||||
#print("创建坐标轴...")
|
||||
self._updateSelectionOutline(nodePath)
|
||||
self.createGizmo(nodePath)
|
||||
if self._has_attached_transform_gizmo(nodePath):
|
||||
self._updateSelectionOutline(effective_node)
|
||||
self.createGizmo(effective_node)
|
||||
if self._has_attached_transform_gizmo(effective_node):
|
||||
gizmo_name = "Unknown"
|
||||
if self._has_new_transform_gizmo():
|
||||
gizmo_name = getattr(nodePath, "getName", lambda: "TransformGizmo")()
|
||||
gizmo_name = getattr(effective_node, "getName", lambda: "TransformGizmo")()
|
||||
elif self.gizmo and not self.gizmo.isEmpty():
|
||||
gizmo_name = self.gizmo.getName()
|
||||
#print(f"✓ 坐标轴创建成功: {gizmo_name}")
|
||||
|
||||
23
imgui.ini
23
imgui.ini
@ -31,19 +31,19 @@ DockId=0x0000000D,0
|
||||
|
||||
[Window][场景树]
|
||||
Pos=0,20
|
||||
Size=274,1084
|
||||
Size=274,1331
|
||||
Collapsed=0
|
||||
DockId=0x00000007,0
|
||||
|
||||
[Window][属性面板]
|
||||
Pos=1655,20
|
||||
Size=393,1084
|
||||
Pos=2167,20
|
||||
Size=393,1331
|
||||
Collapsed=0
|
||||
DockId=0x00000002,0
|
||||
|
||||
[Window][控制台]
|
||||
Pos=276,705
|
||||
Size=1377,399
|
||||
Pos=276,952
|
||||
Size=1889,399
|
||||
Collapsed=0
|
||||
DockId=0x00000006,1
|
||||
|
||||
@ -51,7 +51,6 @@ DockId=0x00000006,1
|
||||
Pos=1950,20
|
||||
Size=610,995
|
||||
Collapsed=0
|
||||
DockId=0x00000005,2
|
||||
|
||||
[Window][中文显示测试]
|
||||
Pos=60,60
|
||||
@ -60,7 +59,7 @@ Collapsed=0
|
||||
|
||||
[Window][WindowOverViewport_11111111]
|
||||
Pos=0,20
|
||||
Size=2048,1084
|
||||
Size=2560,1331
|
||||
Collapsed=0
|
||||
|
||||
[Window][测试窗口1]
|
||||
@ -99,8 +98,8 @@ Size=600,500
|
||||
Collapsed=0
|
||||
|
||||
[Window][资源管理器]
|
||||
Pos=276,705
|
||||
Size=1377,399
|
||||
Pos=276,952
|
||||
Size=1889,399
|
||||
Collapsed=0
|
||||
DockId=0x00000006,0
|
||||
|
||||
@ -150,7 +149,7 @@ Size=101,226
|
||||
Collapsed=0
|
||||
|
||||
[Window][LUI编辑器]
|
||||
Pos=1675,295
|
||||
Pos=1411,331
|
||||
Size=358,569
|
||||
Collapsed=0
|
||||
|
||||
@ -206,7 +205,7 @@ Collapsed=0
|
||||
DockId=0x00000005,2
|
||||
|
||||
[Window][项目另存为]
|
||||
Pos=1050,576
|
||||
Pos=794,432
|
||||
Size=460,240
|
||||
Collapsed=0
|
||||
|
||||
@ -216,7 +215,7 @@ Size=540,320
|
||||
Collapsed=0
|
||||
|
||||
[Docking][Data]
|
||||
DockSpace ID=0x08BD597D Window=0x1BBC0F80 Pos=0,20 Size=2048,1084 Split=X
|
||||
DockSpace ID=0x08BD597D Window=0x1BBC0F80 Pos=0,20 Size=2560,1331 Split=X
|
||||
DockNode ID=0x00000001 Parent=0x08BD597D SizeRef=1525,1012 Split=X
|
||||
DockNode ID=0x00000007 Parent=0x00000001 SizeRef=274,1084 Selected=0xE0015051
|
||||
DockNode ID=0x00000008 Parent=0x00000001 SizeRef=1249,1084 Split=Y
|
||||
|
||||
@ -841,6 +841,8 @@ class ProjectManager:
|
||||
|
||||
root_nodes = []
|
||||
seen = set()
|
||||
model_root_nodes = []
|
||||
auxiliary_root_nodes = []
|
||||
ssbo_editor = getattr(self.world, "ssbo_editor", None)
|
||||
source_model_root = getattr(ssbo_editor, "source_model_root", None) if ssbo_editor else None
|
||||
if source_model_root and not source_model_root.isEmpty():
|
||||
@ -867,7 +869,7 @@ class ProjectManager:
|
||||
if node_key in seen:
|
||||
continue
|
||||
seen.add(node_key)
|
||||
root_nodes.append(node)
|
||||
model_root_nodes.append(node)
|
||||
|
||||
for collection_name in ("Spotlight", "Pointlight"):
|
||||
for node in list(getattr(scene_manager, collection_name, []) or []):
|
||||
@ -877,7 +879,10 @@ class ProjectManager:
|
||||
if node_key in seen:
|
||||
continue
|
||||
seen.add(node_key)
|
||||
root_nodes.append(node)
|
||||
auxiliary_root_nodes.append(node)
|
||||
|
||||
root_nodes.extend(model_root_nodes)
|
||||
root_nodes.extend(auxiliary_root_nodes)
|
||||
return root_nodes
|
||||
|
||||
def _write_scene_description_from_live_scene(self, scene_entry, project_path):
|
||||
@ -916,42 +921,14 @@ class ProjectManager:
|
||||
return False
|
||||
|
||||
self._clearCurrentScene()
|
||||
if self._load_scene_description_via_ssbo(scene_description, project_path, asset_database):
|
||||
scene_manager = getattr(self.world, "scene_manager", None)
|
||||
scene_components = dict(scene_description.get("scene_components", {}) or {})
|
||||
camera_state = dict(scene_components.get("camera", {}) or scene_description.get("camera", {}) or {})
|
||||
camera = getattr(self.world, "camera", None) or getattr(self.world, "cam", None)
|
||||
if camera and not camera.isEmpty():
|
||||
try:
|
||||
position = list(camera_state.get("position", []) or [])
|
||||
rotation = list(camera_state.get("rotation", []) or [])
|
||||
if len(position) >= 3:
|
||||
camera.setPos(float(position[0]), float(position[1]), float(position[2]))
|
||||
if len(rotation) >= 3:
|
||||
camera.setHpr(float(rotation[0]), float(rotation[1]), float(rotation[2]))
|
||||
if "camera_control_enabled" in camera_state:
|
||||
self.world.camera_control_enabled = bool(camera_state.get("camera_control_enabled", True))
|
||||
except Exception:
|
||||
pass
|
||||
gui_snapshot = list((scene_components.get("gui", {}) or {}).get("elements", []) or scene_description.get("gui", []) or [])
|
||||
lui_snapshot = dict(scene_components.get("lui", {}) or scene_description.get("lui", {}) or {})
|
||||
scene_paths = self._scene_paths(scene_entry or {}, project_path=project_path) if scene_entry else {}
|
||||
self._write_scene_sidecars(scene_paths, gui_snapshot, lui_snapshot)
|
||||
|
||||
try:
|
||||
load_lui_fn = getattr(scene_manager, "_load_lui_snapshot_file", None) if scene_manager else None
|
||||
if callable(load_lui_fn) and scene_paths.get("scene_file"):
|
||||
temp_stub = os.path.splitext(scene_paths["scene_file"])[0] + ".bam"
|
||||
load_lui_fn(temp_stub)
|
||||
except Exception:
|
||||
pass
|
||||
return True
|
||||
ssbo_loaded = self._load_scene_description_via_ssbo(scene_description, project_path, asset_database)
|
||||
|
||||
scene_root, keep_nodes = self._build_scene_root_from_description(
|
||||
scene_description,
|
||||
project_path,
|
||||
asset_database,
|
||||
include_mode="all",
|
||||
skip_asset_nodes=ssbo_loaded,
|
||||
)
|
||||
|
||||
scene_manager = getattr(self.world, "scene_manager", None)
|
||||
@ -987,7 +964,8 @@ class ProjectManager:
|
||||
built_point_lights.append(child)
|
||||
|
||||
if scene_manager:
|
||||
scene_manager.models = built_model_nodes
|
||||
if not ssbo_loaded:
|
||||
scene_manager.models = built_model_nodes
|
||||
scene_manager.Spotlight = built_spot_lights
|
||||
scene_manager.Pointlight = built_point_lights
|
||||
update_tree_fn = getattr(scene_manager, "updateSceneTree", None)
|
||||
@ -1030,7 +1008,7 @@ class ProjectManager:
|
||||
load_lui_fn(temp_stub)
|
||||
except Exception:
|
||||
pass
|
||||
return True
|
||||
return bool(ssbo_loaded or built_nodes or built_spot_lights or built_point_lights or scene_components)
|
||||
|
||||
def _iter_top_level_scene_asset_nodes(self, scene_description):
|
||||
nodes = list(scene_description.get("nodes", []) or [])
|
||||
@ -1176,9 +1154,12 @@ class ProjectManager:
|
||||
if not loaded_any:
|
||||
return False
|
||||
|
||||
clear_runtime_fn = getattr(ssbo_editor, "_clear_runtime_state", None)
|
||||
rebuild_fn = getattr(ssbo_editor, "_rebuild_runtime_from_source_root", None)
|
||||
if callable(rebuild_fn):
|
||||
try:
|
||||
if callable(clear_runtime_fn):
|
||||
clear_runtime_fn(preserve_source_models=True)
|
||||
rebuild_fn(highlight_root_name=None)
|
||||
except Exception:
|
||||
pass
|
||||
@ -1451,7 +1432,26 @@ class ProjectManager:
|
||||
return True
|
||||
return False
|
||||
|
||||
def _build_scene_root_from_description(self, scene_description, project_path, asset_database, include_mode="all"):
|
||||
def _node_belongs_to_asset_hierarchy(self, node, node_lookup):
|
||||
if not isinstance(node, dict):
|
||||
return False
|
||||
|
||||
asset_guid = str(node.get("asset_guid", "") or (node.get("components", {}) or {}).get("model", {}).get("asset_guid", "") or "").strip()
|
||||
if asset_guid:
|
||||
return True
|
||||
|
||||
parent_id = node.get("parent_id")
|
||||
while parent_id:
|
||||
parent_node = node_lookup.get(parent_id)
|
||||
if not parent_node:
|
||||
break
|
||||
parent_asset_guid = str(parent_node.get("asset_guid", "") or (parent_node.get("components", {}) or {}).get("model", {}).get("asset_guid", "") or "").strip()
|
||||
if parent_asset_guid:
|
||||
return True
|
||||
parent_id = parent_node.get("parent_id")
|
||||
return False
|
||||
|
||||
def _build_scene_root_from_description(self, scene_description, project_path, asset_database, include_mode="all", skip_asset_nodes=False):
|
||||
nodes = list(scene_description.get("nodes", []) or [])
|
||||
node_lookup = {
|
||||
str(node.get("node_id", "") or ""): dict(node)
|
||||
@ -1462,6 +1462,9 @@ class ProjectManager:
|
||||
for node_id, node in node_lookup.items():
|
||||
if not self._should_keep_scene_description_node(node_id, node, node_lookup):
|
||||
continue
|
||||
if skip_asset_nodes:
|
||||
if self._node_belongs_to_asset_hierarchy(node, node_lookup):
|
||||
continue
|
||||
is_interactive = self._is_scene_description_node_interactive(node)
|
||||
if include_mode == "interactive" and not is_interactive:
|
||||
continue
|
||||
|
||||
@ -411,6 +411,31 @@ class SSBOEditor:
|
||||
if not self.controller or not self.model or not self.source_model_root:
|
||||
return
|
||||
|
||||
proxy = getattr(self, "_group_proxy", None)
|
||||
if self._node_is_valid(proxy):
|
||||
try:
|
||||
selection_key = str(proxy.getTag("ssbo_selection_key") or "").strip()
|
||||
except Exception:
|
||||
selection_key = ""
|
||||
if selection_key and selection_key != getattr(self.controller, "tree_root_key", None):
|
||||
source_group_node = self._resolve_source_node_by_tree_key(selection_key)
|
||||
if self._node_is_valid(source_group_node):
|
||||
try:
|
||||
group_mat = LMatrix4f(proxy.get_mat(self.base.render))
|
||||
except Exception:
|
||||
try:
|
||||
group_mat = LMatrix4f(proxy.getMat(self.base.render))
|
||||
except Exception:
|
||||
group_mat = None
|
||||
if group_mat is not None:
|
||||
try:
|
||||
source_group_node.set_mat(group_mat)
|
||||
except Exception:
|
||||
try:
|
||||
source_group_node.setMat(group_mat)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
grouped_entries = {}
|
||||
for gid, obj_np in self.controller.id_to_object_np.items():
|
||||
if not self._node_is_valid(obj_np):
|
||||
@ -2168,6 +2193,58 @@ class SSBOEditor:
|
||||
"is_group": len(self.selected_ids) > 1 and not self._is_root_selection(),
|
||||
}
|
||||
|
||||
def get_selection_key(self):
|
||||
if not self.controller or self.selected_name is None:
|
||||
return None
|
||||
return self.selected_name
|
||||
|
||||
def get_selection_source_node(self):
|
||||
if not self.controller or self.selected_name is None:
|
||||
return None
|
||||
source_node = self._resolve_source_node_by_tree_key(self.selected_name)
|
||||
if self._node_is_valid(source_node):
|
||||
return source_node
|
||||
return None
|
||||
|
||||
def is_source_tree_node(self, node):
|
||||
if not self._node_is_valid(node) or not self._node_is_valid(self.source_model_root):
|
||||
return False
|
||||
current = node
|
||||
while self._node_is_valid(current):
|
||||
if current == self.source_model_root:
|
||||
return True
|
||||
try:
|
||||
current = current.get_parent()
|
||||
except Exception:
|
||||
try:
|
||||
current = current.getParent()
|
||||
except Exception:
|
||||
return False
|
||||
return False
|
||||
|
||||
def refresh_runtime_from_source(self, preserve_selection=True):
|
||||
if not self._get_source_root_children():
|
||||
return False
|
||||
|
||||
selected_key = self.selected_name if preserve_selection else None
|
||||
try:
|
||||
self._clear_runtime_state(preserve_source_models=True)
|
||||
self._rebuild_runtime_from_source_root(highlight_root_name=None)
|
||||
if preserve_selection and selected_key and self.controller:
|
||||
try:
|
||||
if (
|
||||
selected_key == getattr(self.controller, "tree_root_key", None)
|
||||
or selected_key in getattr(self.controller, "tree_nodes", {})
|
||||
or selected_key in getattr(self.controller, "name_to_ids", {})
|
||||
):
|
||||
self.select_node(selected_key)
|
||||
except Exception:
|
||||
pass
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"[SSBOEditor] 从 source 刷新 runtime 失败: {e}")
|
||||
return False
|
||||
|
||||
def _sync_editor_selection_reference(self, node):
|
||||
selection = getattr(self.base, "selection", None)
|
||||
if not selection:
|
||||
|
||||
@ -750,7 +750,7 @@ class AppActions:
|
||||
return
|
||||
|
||||
# 获取当前选中的节点
|
||||
selected_node = self._resolve_cut_copy_node(self._get_selection_node())
|
||||
selected_node = self._resolve_cut_copy_node(self._get_selection_source_node())
|
||||
if not selected_node:
|
||||
self.add_warning_message("没有选中的节点")
|
||||
return
|
||||
@ -785,7 +785,7 @@ class AppActions:
|
||||
return
|
||||
|
||||
# 获取当前选中的节点
|
||||
selected_node = self._resolve_cut_copy_node(self._get_selection_node())
|
||||
selected_node = self._resolve_cut_copy_node(self._get_selection_source_node())
|
||||
if not selected_node:
|
||||
self.add_warning_message("没有选中的节点")
|
||||
return
|
||||
@ -833,7 +833,7 @@ class AppActions:
|
||||
# 确定粘贴目标父节点
|
||||
parent_node = None
|
||||
if hasattr(self, 'selection') and self.selection:
|
||||
selected_node = self._get_selection_node()
|
||||
selected_node = self._get_selection_source_node()
|
||||
if selected_node and not selected_node.isEmpty():
|
||||
# Paste as sibling by default (not as child of selected node),
|
||||
# which matches editor expectations and avoids "pasted but invisible".
|
||||
@ -970,7 +970,7 @@ class AppActions:
|
||||
return
|
||||
|
||||
# 获取当前选中的节点
|
||||
selected_node = self._get_selection_node()
|
||||
selected_node = self._get_selection_source_node()
|
||||
if not selected_node:
|
||||
self.add_warning_message("没有选中的节点")
|
||||
return
|
||||
|
||||
@ -78,8 +78,11 @@ class EditorPanelsLeftMixin:
|
||||
if getattr(self.app, "use_ssbo_mouse_picking", False):
|
||||
ssbo_editor = getattr(self.app, "ssbo_editor", None)
|
||||
ssbo_model = getattr(ssbo_editor, "model", None) if ssbo_editor else None
|
||||
source_model_root = getattr(ssbo_editor, "source_model_root", None) if ssbo_editor else None
|
||||
if ssbo_model and not ssbo_model.isEmpty() and ssbo_model.hasParent():
|
||||
return [ssbo_model]
|
||||
if source_model_root and not source_model_root.isEmpty():
|
||||
return []
|
||||
scene_manager = getattr(self.app, "scene_manager", None)
|
||||
if scene_manager and hasattr(scene_manager, "models"):
|
||||
return [m for m in scene_manager.models if m and not m.isEmpty()]
|
||||
@ -312,7 +315,24 @@ class EditorPanelsLeftMixin:
|
||||
|
||||
# 检查是否被选中
|
||||
is_selected = (self.app._get_selection_node() == node)
|
||||
|
||||
force_ssbo_root_key = None
|
||||
ssbo_editor_ref = None
|
||||
try:
|
||||
ssbo_editor = getattr(self.app, "ssbo_editor", None)
|
||||
controller = getattr(ssbo_editor, "controller", None) if ssbo_editor else None
|
||||
ssbo_model = getattr(ssbo_editor, "model", None) if ssbo_editor else None
|
||||
root_key = getattr(controller, "tree_root_key", None) if controller else None
|
||||
if (
|
||||
ssbo_editor and controller and ssbo_model and node == ssbo_model
|
||||
and root_key and root_key in controller.tree_nodes
|
||||
):
|
||||
force_ssbo_root_key = root_key
|
||||
ssbo_editor_ref = ssbo_editor
|
||||
is_selected = (getattr(ssbo_editor, "selected_name", None) == root_key)
|
||||
except Exception:
|
||||
force_ssbo_root_key = None
|
||||
ssbo_editor_ref = None
|
||||
|
||||
# 节点可见性
|
||||
is_visible = node.is_hidden() == False
|
||||
|
||||
@ -327,36 +347,19 @@ class EditorPanelsLeftMixin:
|
||||
|
||||
# 处理节点选择
|
||||
if imgui.is_item_clicked():
|
||||
# In SSBO mode, clicking model root should finally bind gizmo to
|
||||
# SSBO group proxy (not legacy model root). So run legacy
|
||||
# selection first, then force SSBO root selection at the end.
|
||||
force_ssbo_root_key = None
|
||||
ssbo_editor_ref = None
|
||||
try:
|
||||
ssbo_editor = getattr(self.app, "ssbo_editor", None)
|
||||
controller = getattr(ssbo_editor, "controller", None) if ssbo_editor else None
|
||||
ssbo_model = getattr(ssbo_editor, "model", None) if ssbo_editor else None
|
||||
root_key = getattr(controller, "tree_root_key", None) if controller else None
|
||||
if (
|
||||
ssbo_editor and controller and ssbo_model and node == ssbo_model
|
||||
and root_key and root_key in controller.tree_nodes
|
||||
):
|
||||
force_ssbo_root_key = root_key
|
||||
ssbo_editor_ref = ssbo_editor
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if hasattr(self.app, 'selection') and self.app.selection:
|
||||
self.app.selection.updateSelection(node)
|
||||
|
||||
if force_ssbo_root_key and ssbo_editor_ref:
|
||||
try:
|
||||
ssbo_editor_ref.select_node(force_ssbo_root_key)
|
||||
if hasattr(self.app, 'lui_manager'):
|
||||
self.app.lui_manager.selected_index = -1
|
||||
except Exception:
|
||||
pass
|
||||
# Clear LUI selection when a scene node is selected
|
||||
if hasattr(self.app, 'lui_manager'):
|
||||
self.app.lui_manager.selected_index = -1
|
||||
else:
|
||||
if hasattr(self.app, 'selection') and self.app.selection:
|
||||
self.app.selection.updateSelection(node)
|
||||
# Clear LUI selection when a scene node is selected
|
||||
if hasattr(self.app, 'lui_manager'):
|
||||
self.app.lui_manager.selected_index = -1
|
||||
|
||||
if self.app.is_dragging and imgui.is_item_hovered():
|
||||
self.app._drag_scene_tree_hover_node = node
|
||||
|
||||
@ -168,7 +168,7 @@ class EditorPanelsRightMixin(
|
||||
return
|
||||
|
||||
# --- Scene Node Properties ---
|
||||
selected_node = self.app._get_selection_node()
|
||||
selected_node = self.app._get_selection_source_node()
|
||||
|
||||
if selected_node and not selected_node.isEmpty():
|
||||
self._draw_node_properties(selected_node)
|
||||
|
||||
@ -19,6 +19,18 @@ class EditorPanelsRightMaterialMixin:
|
||||
pass
|
||||
return self.app._get_node_materials(node)
|
||||
|
||||
def _refresh_ssbo_runtime_for_material_node(self, node, preserve_selection=True):
|
||||
ssbo_editor = getattr(self.app, "ssbo_editor", None)
|
||||
if not ssbo_editor:
|
||||
return
|
||||
if not hasattr(ssbo_editor, "is_source_tree_node") or not ssbo_editor.is_source_tree_node(node):
|
||||
return
|
||||
if hasattr(ssbo_editor, "refresh_runtime_from_source"):
|
||||
try:
|
||||
ssbo_editor.refresh_runtime_from_source(preserve_selection=preserve_selection)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _begin_material_edit_session(self, node, session_key):
|
||||
self._ensure_node_materials_are_editable(node)
|
||||
sessions = self._ensure_material_edit_sessions()
|
||||
@ -52,6 +64,7 @@ class EditorPanelsRightMaterialMixin:
|
||||
return
|
||||
after_snapshot = self.app._capture_node_material_snapshot(node)
|
||||
self._record_material_snapshot_command(node, before_snapshot, after_snapshot)
|
||||
self._refresh_ssbo_runtime_for_material_node(node)
|
||||
|
||||
def _apply_material_change_with_history(self, node, apply_callback):
|
||||
self._ensure_node_materials_are_editable(node)
|
||||
@ -59,6 +72,7 @@ class EditorPanelsRightMaterialMixin:
|
||||
apply_callback()
|
||||
after_snapshot = self.app._capture_node_material_snapshot(node)
|
||||
self._record_material_snapshot_command(node, before_snapshot, after_snapshot)
|
||||
self._refresh_ssbo_runtime_for_material_node(node)
|
||||
|
||||
def _select_texture_for_material_with_history(self, node, material, texture_type):
|
||||
self._ensure_node_materials_are_editable(node)
|
||||
@ -68,6 +82,7 @@ class EditorPanelsRightMaterialMixin:
|
||||
return False
|
||||
after_snapshot = self.app._capture_node_material_snapshot(node)
|
||||
self._record_material_snapshot_command(node, before_snapshot, after_snapshot)
|
||||
self._refresh_ssbo_runtime_for_material_node(node)
|
||||
return True
|
||||
|
||||
def _draw_appearance_properties(self, node):
|
||||
@ -201,7 +216,7 @@ class EditorPanelsRightMaterialMixin:
|
||||
if imgui.is_item_activated():
|
||||
self._begin_material_edit_session(node, f"material_{i}_roughness")
|
||||
if changed:
|
||||
self._update_material_roughness(material, new_roughness)
|
||||
self._update_material_roughness(material, new_roughness, node)
|
||||
self._finish_material_edit_session(node, f"material_{i}_roughness")
|
||||
except:
|
||||
imgui.text_colored((0.7, 0.7, 0.7, 1.0), "粗糙度: 不可用")
|
||||
@ -213,7 +228,7 @@ class EditorPanelsRightMaterialMixin:
|
||||
if imgui.is_item_activated():
|
||||
self._begin_material_edit_session(node, f"material_{i}_metallic")
|
||||
if changed:
|
||||
self._update_material_metallic(material, new_metallic)
|
||||
self._update_material_metallic(material, new_metallic, node)
|
||||
self._finish_material_edit_session(node, f"material_{i}_metallic")
|
||||
except:
|
||||
imgui.text_colored((0.7, 0.7, 0.7, 1.0), "金属性: 不可用")
|
||||
@ -225,7 +240,7 @@ class EditorPanelsRightMaterialMixin:
|
||||
if imgui.is_item_activated():
|
||||
self._begin_material_edit_session(node, f"material_{i}_ior")
|
||||
if changed:
|
||||
self._update_material_ior(material, new_ior)
|
||||
self._update_material_ior(material, new_ior, node)
|
||||
self._finish_material_edit_session(node, f"material_{i}_ior")
|
||||
except:
|
||||
imgui.text_colored((0.7, 0.7, 0.7, 1.0), "折射率: 不可用")
|
||||
@ -241,7 +256,7 @@ class EditorPanelsRightMaterialMixin:
|
||||
self._apply_material_change_with_history(
|
||||
node,
|
||||
lambda selected_preset=preset_name: (
|
||||
self._apply_material_preset(material, selected_preset),
|
||||
self._apply_material_preset(material, selected_preset, node),
|
||||
self._apply_material_surface_state(node, material),
|
||||
self.app._refresh_pipeline_material_mode(node, material),
|
||||
),
|
||||
@ -278,6 +293,7 @@ class EditorPanelsRightMaterialMixin:
|
||||
imgui.separator()
|
||||
if imgui.button("应用材质"):
|
||||
self._apply_material_to_node(node)
|
||||
self._refresh_ssbo_runtime_for_material_node(node)
|
||||
imgui.same_line()
|
||||
if imgui.button("重置材质"):
|
||||
self._apply_material_change_with_history(node, lambda: self._reset_material(node))
|
||||
|
||||
@ -163,7 +163,7 @@ class EditorPanelsTopMixin:
|
||||
return os.path.basename(project_path) or "未命名项目"
|
||||
|
||||
def _get_toolbar_selection_name(self):
|
||||
selected_node = self.app._get_selection_node()
|
||||
selected_node = self.app._get_selection_source_node()
|
||||
if selected_node and not selected_node.isEmpty():
|
||||
return selected_node.getName() or "未命名对象"
|
||||
ssbo_summary = self.app._get_ssbo_selection_summary()
|
||||
|
||||
@ -42,6 +42,20 @@ class PanelDelegates:
|
||||
node = getattr(selection, "selectedNode", None) if selection else None
|
||||
return node if self._node_is_valid(node, require_attached=False) else None
|
||||
|
||||
def _get_selection_source_node(self):
|
||||
ssbo_editor = getattr(self, "ssbo_editor", None)
|
||||
if ssbo_editor and hasattr(ssbo_editor, "has_active_selection") and ssbo_editor.has_active_selection():
|
||||
source_node = getattr(ssbo_editor, "get_selection_source_node", lambda: None)()
|
||||
if source_node and self._node_is_valid(source_node, require_attached=False):
|
||||
return source_node
|
||||
return self._get_selection_node()
|
||||
|
||||
def _get_selection_key(self):
|
||||
ssbo_editor = getattr(self, "ssbo_editor", None)
|
||||
if ssbo_editor and hasattr(ssbo_editor, "has_active_selection") and ssbo_editor.has_active_selection():
|
||||
return getattr(ssbo_editor, "get_selection_key", lambda: None)()
|
||||
return None
|
||||
|
||||
def _get_ssbo_selection_summary(self):
|
||||
ssbo_editor = getattr(self, "ssbo_editor", None)
|
||||
if not ssbo_editor or not hasattr(ssbo_editor, "has_active_selection"):
|
||||
|
||||
@ -1132,6 +1132,11 @@ class PropertyHelpers:
|
||||
ssbo_editor.sync_scene_nodes_to_pick([node])
|
||||
except Exception:
|
||||
pass
|
||||
if ssbo_editor and hasattr(ssbo_editor, "is_source_tree_node") and ssbo_editor.is_source_tree_node(node):
|
||||
try:
|
||||
ssbo_editor.refresh_runtime_from_source(preserve_selection=True)
|
||||
except Exception:
|
||||
pass
|
||||
except Exception as e:
|
||||
print(f"应用材质快照失败: {e}")
|
||||
|
||||
@ -1180,6 +1185,43 @@ class PropertyHelpers:
|
||||
except Exception as e:
|
||||
print(f"同步Geom材质状态失败: {e}")
|
||||
|
||||
def _refresh_ssbo_runtime_for_material_node(self, node, preserve_selection=True):
|
||||
"""If editing a SSBO source node, rebuild the runtime proxy immediately."""
|
||||
try:
|
||||
ssbo_editor = getattr(self, "ssbo_editor", None)
|
||||
if not ssbo_editor or not node or node.isEmpty():
|
||||
return
|
||||
if not hasattr(ssbo_editor, "is_source_tree_node") or not ssbo_editor.is_source_tree_node(node):
|
||||
return
|
||||
if hasattr(ssbo_editor, "refresh_runtime_from_source"):
|
||||
ssbo_editor.refresh_runtime_from_source(preserve_selection=preserve_selection)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _sync_material_node_runtime(self, node, material=None, refresh_ssbo_runtime=True):
|
||||
"""Push source material edits into visible render state."""
|
||||
try:
|
||||
if not node or node.isEmpty():
|
||||
return
|
||||
|
||||
materials = [material] if material is not None else self._get_node_materials(node)
|
||||
for current_material in materials:
|
||||
if current_material is None:
|
||||
continue
|
||||
self._apply_material_to_geom_states(node, current_material)
|
||||
self._apply_material_surface_state(node, current_material)
|
||||
refresh_pipeline = getattr(self, "_refresh_pipeline_material_mode", None)
|
||||
if callable(refresh_pipeline):
|
||||
try:
|
||||
refresh_pipeline(node, current_material)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if refresh_ssbo_runtime:
|
||||
self._refresh_ssbo_runtime_for_material_node(node)
|
||||
except Exception as e:
|
||||
print(f"同步材质显示状态失败: {e}")
|
||||
|
||||
def _get_node_materials(self, node):
|
||||
"""Return the editable materials currently used by a node."""
|
||||
if not node or node.isEmpty():
|
||||
@ -1955,34 +1997,40 @@ class PropertyHelpers:
|
||||
print(f"更新材质基础颜色失败: {e}")
|
||||
|
||||
|
||||
def _update_material_roughness(self, material, value):
|
||||
def _update_material_roughness(self, material, value, node=None):
|
||||
"""更新材质粗糙度"""
|
||||
try:
|
||||
if hasattr(material, 'set_roughness'):
|
||||
material.set_roughness(value)
|
||||
if node is not None:
|
||||
self._sync_material_node_runtime(node, material)
|
||||
except Exception as e:
|
||||
print(f"更新材质粗糙度失败: {e}")
|
||||
|
||||
|
||||
def _update_material_metallic(self, material, value):
|
||||
def _update_material_metallic(self, material, value, node=None):
|
||||
"""更新材质金属性"""
|
||||
try:
|
||||
if hasattr(material, 'set_metallic'):
|
||||
material.set_metallic(value)
|
||||
if node is not None:
|
||||
self._sync_material_node_runtime(node, material)
|
||||
except Exception as e:
|
||||
print(f"更新材质金属性失败: {e}")
|
||||
|
||||
|
||||
def _update_material_ior(self, material, value):
|
||||
def _update_material_ior(self, material, value, node=None):
|
||||
"""更新材质折射率"""
|
||||
try:
|
||||
if hasattr(material, 'set_refractive_index'):
|
||||
material.set_refractive_index(value)
|
||||
if node is not None:
|
||||
self._sync_material_node_runtime(node, material)
|
||||
except Exception as e:
|
||||
print(f"更新材质折射率失败: {e}")
|
||||
|
||||
|
||||
def _apply_material_preset(self, material, preset_name):
|
||||
def _apply_material_preset(self, material, preset_name, node=None):
|
||||
"""应用材质预设"""
|
||||
try:
|
||||
from panda3d.core import Vec4, Material
|
||||
@ -2050,6 +2098,8 @@ class PropertyHelpers:
|
||||
material.set_emission(Vec4(surface_type, 0.0, opacity, 0.0))
|
||||
|
||||
print(f"已应用材质预设: {preset_name}")
|
||||
if node is not None:
|
||||
self._sync_material_node_runtime(node, material)
|
||||
except Exception as e:
|
||||
print(f"应用材质预设失败: {e}")
|
||||
|
||||
@ -2059,7 +2109,7 @@ class PropertyHelpers:
|
||||
try:
|
||||
material = self._ensure_material_for_node(node)
|
||||
if material:
|
||||
self._apply_material_surface_state(node, material)
|
||||
self._sync_material_node_runtime(node, material)
|
||||
print("材质已应用到节点")
|
||||
except Exception as e:
|
||||
print(f"应用材质失败: {e}")
|
||||
@ -2099,7 +2149,9 @@ class PropertyHelpers:
|
||||
material.set_refractive_index(1.5)
|
||||
if hasattr(material, 'set_emission'):
|
||||
material.set_emission(Vec4(0.0, 0.0, 1.0, 0.0))
|
||||
self._apply_material_surface_state(node, material)
|
||||
self._sync_material_node_runtime(node, material, refresh_ssbo_runtime=False)
|
||||
|
||||
self._refresh_ssbo_runtime_for_material_node(node)
|
||||
|
||||
print(f"已重置材质")
|
||||
except Exception as e:
|
||||
@ -2480,6 +2532,8 @@ class PropertyHelpers:
|
||||
if texture_path:
|
||||
stable_texture_path = os.path.normpath(os.path.abspath(texture_path))
|
||||
node.setTag(f"material_texture_{texture_type}", stable_texture_path)
|
||||
|
||||
self._sync_material_node_runtime(node, material)
|
||||
|
||||
print(f"已应用{texture_type}纹理到槽位 p3d_Texture{slot}: {texture_path}")
|
||||
return True
|
||||
@ -2512,6 +2566,12 @@ class PropertyHelpers:
|
||||
if node.hasTag("material_effect_parallax_enabled"):
|
||||
node.clearTag("material_effect_parallax_enabled")
|
||||
|
||||
for material in self._get_node_materials(node):
|
||||
if material is None:
|
||||
continue
|
||||
self._sync_material_node_runtime(node, material, refresh_ssbo_runtime=False)
|
||||
self._refresh_ssbo_runtime_for_material_node(node)
|
||||
|
||||
print("已清除所有纹理")
|
||||
return True
|
||||
except Exception as e:
|
||||
|
||||
@ -261,7 +261,7 @@ class ScriptPanels:
|
||||
"""绘制脚本挂载组"""
|
||||
if imgui.collapsing_header("脚本挂载"):
|
||||
# 显示当前选中对象
|
||||
selected_node = self._get_selection_node()
|
||||
selected_node = self._get_selection_source_node()
|
||||
|
||||
if selected_node and not selected_node.isEmpty():
|
||||
imgui.text("选中对象:")
|
||||
|
||||
@ -1,8 +0,0 @@
|
||||
{
|
||||
"name": "新项目_副本",
|
||||
"path": "D:\\IMGUI\\EG\\新项目_副本",
|
||||
"last_modified": "2026-03-13 16:55:57",
|
||||
"scene_file": "scenes\\scene.bam",
|
||||
"created_at": "2026-03-13 16:55:57",
|
||||
"version": "1.0"
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 1.4 MiB |
Loading…
Reference in New Issue
Block a user