Merge these animation changes from hu_migrate_on_geng into your branch:

This commit is contained in:
ayuan9957 2026-03-23 08:25:30 +08:00
parent c14c2b8796
commit f17073160c
17 changed files with 278 additions and 110 deletions

View File

@ -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",

View File

@ -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"
}

View File

@ -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}")

View File

@ -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

View File

@ -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

View File

@ -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:

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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))

View File

@ -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()

View File

@ -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"):

View File

@ -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:

View File

@ -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("选中对象:")

View File

@ -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