Refine MetaCore scene workflow and SSBO loading
This commit is contained in:
parent
b9053b6a28
commit
c14c2b8796
18
.gitignore
vendored
18
.gitignore
vendored
@ -1,7 +1,17 @@
|
||||
__pycache__/
|
||||
*.pyc
|
||||
.idea/
|
||||
【模板】模型及算法与功能对应清单.docx
|
||||
__pycache__/
|
||||
*.pyc
|
||||
imgui.ini
|
||||
build/
|
||||
Builds/
|
||||
Library/
|
||||
**/Library/
|
||||
**/Builds/
|
||||
**/Scenes/*_gui.json
|
||||
**/Scenes/*_lui.json
|
||||
**/scenes/*_gui.json
|
||||
**/scenes/*_lui.json
|
||||
.idea/
|
||||
【模板】模型及算法与功能对应清单.docx
|
||||
【模板】GY知识-产品模块架构及功能清单和主要应用场景说明.docx
|
||||
1.json
|
||||
2.json
|
||||
|
||||
@ -64,10 +64,12 @@ class CustomMouseController:
|
||||
self.keyMap[key] = value
|
||||
# 只在右键按下时记录鼠标位置
|
||||
if key == "mouse3" and value == True:
|
||||
mouse_pos = self.showbase.mouseWatcherNode.getMouse()
|
||||
if mouse_pos:
|
||||
self.last_mouse_x = mouse_pos.get_x()
|
||||
self.last_mouse_y = mouse_pos.get_y()
|
||||
watcher = getattr(self.showbase, "mouseWatcherNode", None)
|
||||
if watcher and watcher.hasMouse():
|
||||
mouse_pos = watcher.getMouse()
|
||||
if mouse_pos:
|
||||
self.last_mouse_x = mouse_pos.get_x()
|
||||
self.last_mouse_y = mouse_pos.get_y()
|
||||
|
||||
def move(self, task):
|
||||
dt = self.showbase.clock.dt
|
||||
@ -98,7 +100,10 @@ class CustomMouseController:
|
||||
try:
|
||||
# 检查是否应该处理鼠标事件(避免与ImGui冲突)
|
||||
if self._should_handle_mouse():
|
||||
mouse_pos = self.showbase.mouseWatcherNode.getMouse()
|
||||
watcher = getattr(self.showbase, "mouseWatcherNode", None)
|
||||
if not watcher or not watcher.hasMouse():
|
||||
return task.cont
|
||||
mouse_pos = watcher.getMouse()
|
||||
if mouse_pos:
|
||||
current_x = mouse_pos.get_x()
|
||||
current_y = mouse_pos.get_y()
|
||||
@ -160,7 +165,10 @@ class CustomMouseController:
|
||||
pass
|
||||
|
||||
# 检查鼠标位置是否在ImGui窗口区域内
|
||||
mouse_pos = self.showbase.mouseWatcherNode.getMouse()
|
||||
watcher = getattr(self.showbase, "mouseWatcherNode", None)
|
||||
if not watcher or not watcher.hasMouse():
|
||||
return True
|
||||
mouse_pos = watcher.getMouse()
|
||||
if not mouse_pos:
|
||||
return True
|
||||
|
||||
@ -185,4 +193,4 @@ class CustomMouseController:
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"鼠标事件检测失败: {e}")
|
||||
return True
|
||||
return True
|
||||
|
||||
@ -810,20 +810,13 @@ class ProjectManager:
|
||||
def _cleanup_legacy_scene_artifacts(self, scene_entry, project_path):
|
||||
scene_paths = self._scene_paths(scene_entry, project_path=project_path)
|
||||
scene_file = scene_paths.get("scene_file", "")
|
||||
scene_root, _ = os.path.splitext(scene_file)
|
||||
legacy_candidates = [
|
||||
os.path.join(project_path, "scenes", "scene.bam"),
|
||||
os.path.join(project_path, "scenes", "scene_gui.json"),
|
||||
os.path.join(project_path, "scenes", "scene_lui.json"),
|
||||
]
|
||||
if scene_root:
|
||||
legacy_candidates.extend(
|
||||
[
|
||||
f"{scene_root}.bam",
|
||||
f"{scene_root}_gui.json",
|
||||
f"{scene_root}_lui.json",
|
||||
]
|
||||
)
|
||||
if scene_file:
|
||||
legacy_candidates.append(f"{os.path.splitext(scene_file)[0]}.bam")
|
||||
|
||||
removed_files = []
|
||||
for legacy_path in legacy_candidates:
|
||||
@ -923,6 +916,37 @@ 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
|
||||
|
||||
scene_root, keep_nodes = self._build_scene_root_from_description(
|
||||
scene_description,
|
||||
project_path,
|
||||
@ -966,6 +990,12 @@ class ProjectManager:
|
||||
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)
|
||||
if callable(update_tree_fn):
|
||||
try:
|
||||
update_tree_fn()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
if not scene_root.isEmpty():
|
||||
@ -974,6 +1004,20 @@ class ProjectManager:
|
||||
pass
|
||||
|
||||
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 {}
|
||||
@ -981,13 +1025,179 @@ class ProjectManager:
|
||||
|
||||
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_lui_file"):
|
||||
temp_stub = os.path.splitext(scene_paths["scene_lui_file"])[0] + ".bam"
|
||||
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
|
||||
|
||||
def _iter_top_level_scene_asset_nodes(self, scene_description):
|
||||
nodes = list(scene_description.get("nodes", []) or [])
|
||||
node_lookup = {
|
||||
str(node.get("node_id", "") or ""): dict(node)
|
||||
for node in nodes
|
||||
if node.get("node_id") is not None
|
||||
}
|
||||
|
||||
top_level_nodes = []
|
||||
for node_id, node in node_lookup.items():
|
||||
asset_guid = str(node.get("asset_guid", "") or (node.get("components", {}) or {}).get("model", {}).get("asset_guid", "") or "").strip()
|
||||
if not asset_guid:
|
||||
continue
|
||||
parent_id = node.get("parent_id")
|
||||
has_asset_parent = False
|
||||
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:
|
||||
has_asset_parent = True
|
||||
break
|
||||
parent_id = parent_node.get("parent_id")
|
||||
if not has_asset_parent:
|
||||
top_level_nodes.append(node)
|
||||
return top_level_nodes, node_lookup
|
||||
|
||||
def _apply_scene_description_state_to_node(self, target_np, node, project_path):
|
||||
if not target_np or target_np.isEmpty() or not isinstance(node, dict):
|
||||
return
|
||||
|
||||
node_name = str(node.get("name", "") or target_np.getName() or "Node")
|
||||
target_np.setName(node_name)
|
||||
|
||||
transform = dict(node.get("transform", {}) or {})
|
||||
position = list(transform.get("position", [0, 0, 0]) or [0, 0, 0])
|
||||
rotation = list(transform.get("rotation", [0, 0, 0]) or [0, 0, 0])
|
||||
scale = list(transform.get("scale", [1, 1, 1]) or [1, 1, 1])
|
||||
if len(position) >= 3:
|
||||
target_np.setPos(float(position[0]), float(position[1]), float(position[2]))
|
||||
if len(rotation) >= 3:
|
||||
target_np.setHpr(float(rotation[0]), float(rotation[1]), float(rotation[2]))
|
||||
if len(scale) >= 3:
|
||||
target_np.setScale(float(scale[0]), float(scale[1]), float(scale[2]))
|
||||
|
||||
visibility = dict(node.get("visibility", {}) or {})
|
||||
user_visible = bool(visibility.get("user_visible", True))
|
||||
target_np.setPythonTag("user_visible", user_visible)
|
||||
target_np.setTag("user_visible", "true" if user_visible else "false")
|
||||
if user_visible:
|
||||
target_np.show()
|
||||
else:
|
||||
target_np.hide()
|
||||
|
||||
for tag_name, tag_value in dict(node.get("tags", {}) or {}).items():
|
||||
if tag_value is None:
|
||||
continue
|
||||
target_np.setTag(str(tag_name), str(tag_value))
|
||||
|
||||
components = dict(node.get("components", {}) or {})
|
||||
model_component = dict(components.get("model", {}) or {})
|
||||
scripts_component = dict(components.get("scripts", {}) or {})
|
||||
metadata_component = dict(components.get("metadata", {}) or {})
|
||||
|
||||
asset_guid = str(model_component.get("asset_guid", "") or node.get("asset_guid", "") or "").strip()
|
||||
asset_path = str(model_component.get("asset_path", "") or node.get("asset_path", "") or "").strip()
|
||||
imported_node_key = str(model_component.get("imported_node_key", "") or node.get("imported_node_key", "") or "").strip()
|
||||
if asset_guid:
|
||||
target_np.setTag("asset_guid", asset_guid)
|
||||
if asset_path:
|
||||
asset_abs_path = os.path.join(project_path, asset_path.replace("/", os.sep))
|
||||
target_np.setTag("asset_path", asset_path)
|
||||
target_np.setTag("model_path", asset_abs_path)
|
||||
target_np.setTag("saved_model_path", asset_abs_path)
|
||||
target_np.setTag("original_path", asset_abs_path)
|
||||
target_np.setTag("file", os.path.basename(asset_abs_path))
|
||||
if imported_node_key:
|
||||
target_np.setTag("imported_node_key", imported_node_key)
|
||||
target_np.setTag("is_model_root", "1")
|
||||
target_np.setTag("is_scene_element", "1")
|
||||
|
||||
runtime_interactive = bool(metadata_component.get("runtime_interactive", node.get("runtime_interactive", False)))
|
||||
if runtime_interactive:
|
||||
target_np.setTag("runtime_interactive", "true")
|
||||
|
||||
scripts = list(scripts_component.get("entries", []) or node.get("scripts", []) or [])
|
||||
if scripts:
|
||||
target_np.setTag("has_scripts", "true")
|
||||
target_np.setTag("scripts_info", json.dumps(scripts, ensure_ascii=False))
|
||||
|
||||
def _load_scene_description_via_ssbo(self, scene_description, project_path, asset_database):
|
||||
if not getattr(self.world, "use_ssbo_mouse_picking", False):
|
||||
return False
|
||||
|
||||
ssbo_editor = getattr(self.world, "ssbo_editor", None)
|
||||
if not ssbo_editor:
|
||||
return False
|
||||
|
||||
top_level_asset_nodes, node_lookup = self._iter_top_level_scene_asset_nodes(scene_description)
|
||||
if not top_level_asset_nodes:
|
||||
return False
|
||||
|
||||
loaded_any = False
|
||||
for node in top_level_asset_nodes:
|
||||
components = dict(node.get("components", {}) or {})
|
||||
model_component = dict(components.get("model", {}) or {})
|
||||
asset_guid = str(model_component.get("asset_guid", "") or node.get("asset_guid", "") or "").strip()
|
||||
if not asset_guid:
|
||||
continue
|
||||
|
||||
asset_record = asset_database.get_asset(asset_guid)
|
||||
if not asset_record:
|
||||
continue
|
||||
|
||||
asset_rel = str(asset_record.get("asset_path", "") or model_component.get("asset_path", "") or node.get("asset_path", "") or "").strip()
|
||||
if not asset_rel:
|
||||
continue
|
||||
|
||||
asset_abs = os.path.join(project_path, asset_rel.replace("/", os.sep))
|
||||
if not os.path.exists(asset_abs):
|
||||
continue
|
||||
|
||||
try:
|
||||
imported_root = ssbo_editor.load_model(
|
||||
asset_abs,
|
||||
keep_source_model=False,
|
||||
append=loaded_any,
|
||||
scene_package_import=False,
|
||||
)
|
||||
except Exception as e:
|
||||
print(f"⚠️ SSBO 恢复场景模型失败 {asset_abs}: {e}")
|
||||
continue
|
||||
|
||||
target_root = imported_root if imported_root and not imported_root.isEmpty() else None
|
||||
if target_root is None:
|
||||
continue
|
||||
|
||||
self._apply_scene_description_state_to_node(target_root, node, project_path)
|
||||
loaded_any = True
|
||||
|
||||
if not loaded_any:
|
||||
return False
|
||||
|
||||
rebuild_fn = getattr(ssbo_editor, "_rebuild_runtime_from_source_root", None)
|
||||
if callable(rebuild_fn):
|
||||
try:
|
||||
rebuild_fn(highlight_root_name=None)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
scene_manager = getattr(self.world, "scene_manager", None)
|
||||
runtime_model = getattr(ssbo_editor, "model", None)
|
||||
if scene_manager:
|
||||
scene_manager.models = [runtime_model] if runtime_model and not runtime_model.isEmpty() else []
|
||||
scene_manager.Spotlight = []
|
||||
scene_manager.Pointlight = []
|
||||
update_tree_fn = getattr(scene_manager, "updateSceneTree", None)
|
||||
if callable(update_tree_fn):
|
||||
try:
|
||||
update_tree_fn()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def _should_keep_scene_description_node(self, node_id, node, node_lookup):
|
||||
if not node_id:
|
||||
@ -1972,9 +2182,95 @@ class ProjectManager:
|
||||
def _clearCurrentScene(self):
|
||||
"""清空当前场景"""
|
||||
try:
|
||||
if hasattr(self.world, 'scene_manager') and self.world.scene_manager:
|
||||
self.world.scene_manager.clearScene()
|
||||
print("✓ 当前场景已清空")
|
||||
selection = getattr(self.world, "selection", None)
|
||||
if selection and hasattr(selection, "clearSelection"):
|
||||
try:
|
||||
selection.clearSelection()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
script_manager = getattr(self.world, "script_manager", None)
|
||||
if script_manager and hasattr(script_manager, "reset_scene_state"):
|
||||
try:
|
||||
script_manager.reset_scene_state()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
ssbo_editor = getattr(self.world, "ssbo_editor", None)
|
||||
if ssbo_editor and hasattr(ssbo_editor, "reset_scene_state"):
|
||||
try:
|
||||
ssbo_editor.reset_scene_state()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
scene_manager = getattr(self.world, "scene_manager", None)
|
||||
if scene_manager:
|
||||
tree_widget = scene_manager._get_tree_widget() if hasattr(scene_manager, "_get_tree_widget") else None
|
||||
|
||||
for model in list(getattr(scene_manager, "models", []) or []):
|
||||
try:
|
||||
if tree_widget:
|
||||
tree_widget.delete_item(model)
|
||||
elif model and not model.isEmpty():
|
||||
model.removeNode()
|
||||
except Exception:
|
||||
pass
|
||||
scene_manager.models = []
|
||||
|
||||
for collection_name in ("Spotlight", "Pointlight"):
|
||||
collection = list(getattr(scene_manager, collection_name, []) or [])
|
||||
for light_node in collection:
|
||||
try:
|
||||
if tree_widget:
|
||||
tree_widget.delete_item(light_node)
|
||||
elif light_node and not light_node.isEmpty():
|
||||
light_node.removeNode()
|
||||
except Exception:
|
||||
pass
|
||||
setattr(scene_manager, collection_name, [])
|
||||
|
||||
terrain_manager = getattr(self.world, "terrain_manager", None)
|
||||
terrains = list(getattr(terrain_manager, "terrains", []) or []) if terrain_manager else []
|
||||
for terrain in terrains:
|
||||
try:
|
||||
if tree_widget:
|
||||
tree_widget.delete_item(terrain)
|
||||
elif terrain and not terrain.isEmpty():
|
||||
terrain.removeNode()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
cleanup_aux_fn = getattr(scene_manager, "_cleanupAuxiliaryNodes", None)
|
||||
if callable(cleanup_aux_fn):
|
||||
try:
|
||||
cleanup_aux_fn()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
cleanup_render_fn = getattr(scene_manager, "_cleanup_untracked_render_children", None)
|
||||
if callable(cleanup_render_fn):
|
||||
try:
|
||||
cleanup_render_fn()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
update_tree_fn = getattr(scene_manager, "updateSceneTree", None)
|
||||
if callable(update_tree_fn):
|
||||
try:
|
||||
update_tree_fn()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
lui_manager = getattr(self.world, "lui_manager", None)
|
||||
if lui_manager:
|
||||
clear_lui_fn = getattr(lui_manager, "_clear_lui_structure_runtime", None)
|
||||
if callable(clear_lui_fn):
|
||||
try:
|
||||
clear_lui_fn()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
print("✓ 当前场景已清空")
|
||||
except Exception as e:
|
||||
print(f"清空场景失败: {str(e)}")
|
||||
|
||||
|
||||
@ -214,6 +214,13 @@ def _collect_material_override_component(node):
|
||||
|
||||
|
||||
def _resolve_imported_node_key(node, fallback_key: str) -> str:
|
||||
if node.hasTag("is_model_root"):
|
||||
for tag_name in ("imported_node_key", "source_model_node_key", "ssbo_tree_key", "tree_item_key"):
|
||||
if node.hasTag(tag_name):
|
||||
tag_value = str(node.getTag(tag_name) or "").strip()
|
||||
if tag_value:
|
||||
return tag_value
|
||||
return ""
|
||||
for tag_name in ("imported_node_key", "source_model_node_key", "ssbo_tree_key", "tree_item_key"):
|
||||
if node.hasTag(tag_name):
|
||||
tag_value = str(node.getTag(tag_name) or "").strip()
|
||||
@ -222,6 +229,22 @@ def _resolve_imported_node_key(node, fallback_key: str) -> str:
|
||||
return fallback_key
|
||||
|
||||
|
||||
def _should_serialize_child_nodes(node, asset_guid: str, scripts: list[dict], runtime_interactive: bool, light_component: dict, material_component: dict) -> bool:
|
||||
if not node:
|
||||
return False
|
||||
if not asset_guid:
|
||||
return True
|
||||
if scripts:
|
||||
return True
|
||||
if runtime_interactive:
|
||||
return True
|
||||
if light_component:
|
||||
return True
|
||||
if material_component:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def _build_scene_components(camera_state: dict, gui_elements: list, lui_snapshot: dict) -> dict:
|
||||
return {
|
||||
"camera": dict(camera_state or {}),
|
||||
@ -304,6 +327,9 @@ def _build_scene_description_payload(
|
||||
script_info["project_relative_path"] = asset_path_for_script
|
||||
script_info["file"] = asset_path_for_script
|
||||
|
||||
light_component = _collect_light_component(child)
|
||||
material_component = _collect_material_override_component(child)
|
||||
|
||||
node_entries.append(
|
||||
{
|
||||
"node_id": node_id,
|
||||
@ -330,8 +356,8 @@ def _build_scene_description_payload(
|
||||
"components": {
|
||||
"model": _collect_model_component(child, asset_guid, asset_path, imported_node_key),
|
||||
"scripts": _collect_script_component(scripts),
|
||||
"light": _collect_light_component(child),
|
||||
"material_overrides": _collect_material_override_component(child),
|
||||
"light": light_component,
|
||||
"material_overrides": material_component,
|
||||
"metadata": _collect_node_metadata_component(child, runtime_interactive),
|
||||
},
|
||||
"tags": {
|
||||
@ -340,11 +366,19 @@ def _build_scene_description_payload(
|
||||
},
|
||||
}
|
||||
)
|
||||
try:
|
||||
child_nodes = list(child.getChildren())
|
||||
except Exception:
|
||||
child_nodes = []
|
||||
walk_nodes(child_nodes, node_id)
|
||||
if _should_serialize_child_nodes(
|
||||
child,
|
||||
asset_guid,
|
||||
scripts,
|
||||
runtime_interactive,
|
||||
light_component,
|
||||
material_component,
|
||||
):
|
||||
try:
|
||||
child_nodes = list(child.getChildren())
|
||||
except Exception:
|
||||
child_nodes = []
|
||||
walk_nodes(child_nodes, node_id)
|
||||
|
||||
walk_nodes(root_nodes)
|
||||
|
||||
|
||||
@ -807,7 +807,7 @@ class ObjectController:
|
||||
for chunk_id in target_chunks:
|
||||
self._set_chunk_dynamic(chunk_id, True)
|
||||
|
||||
def bake_ids_and_collect(self, model):
|
||||
def bake_ids_and_collect_hybrid(self, model):
|
||||
"""
|
||||
构建混合架构:
|
||||
1) 把每个 geom 拆成可独立编辑的动态对象
|
||||
|
||||
@ -972,7 +972,15 @@ class SSBOEditor:
|
||||
working_root = root.copy_to(working_holder)
|
||||
|
||||
self.controller = ObjectController()
|
||||
count = self.controller.bake_ids_and_collect(working_root)
|
||||
try:
|
||||
geom_count = len(list(working_root.find_all_matches("**/+GeomNode")))
|
||||
except Exception:
|
||||
geom_count = 0
|
||||
use_hybrid_mode = geom_count > 0 and geom_count <= 256
|
||||
if use_hybrid_mode:
|
||||
count = self.controller.bake_ids_and_collect_hybrid(working_root)
|
||||
else:
|
||||
count = self.controller.bake_ids_and_collect(working_root)
|
||||
self.model = self.controller.model
|
||||
self.model.reparent_to(self.base.render)
|
||||
|
||||
@ -1000,7 +1008,8 @@ class SSBOEditor:
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
print(f"[SSBOEditor] Model loaded. Total objects: {count}")
|
||||
mode_name = "hybrid" if use_hybrid_mode else "flat"
|
||||
print(f"[SSBOEditor] Model loaded. Total objects: {count}, mode={mode_name}, geoms={geom_count}")
|
||||
|
||||
def load_model(
|
||||
self,
|
||||
@ -2126,9 +2135,14 @@ class SSBOEditor:
|
||||
if not self.controller or self.selected_name is None:
|
||||
return None
|
||||
|
||||
has_dynamic_objects = bool(getattr(self.controller, "id_to_object_np", {}) or {})
|
||||
|
||||
if self._is_root_selection():
|
||||
return self.model if self._node_is_valid(self.model) else None
|
||||
|
||||
if not has_dynamic_objects:
|
||||
return self.model if self._node_is_valid(self.model) else None
|
||||
|
||||
if len(self.selected_ids) > 1:
|
||||
proxy = getattr(self, "_group_proxy", None)
|
||||
if proxy and self._node_is_valid(proxy):
|
||||
@ -2265,17 +2279,27 @@ class SSBOEditor:
|
||||
|
||||
self.selected_name = key
|
||||
self.selected_ids = self.controller.name_to_ids.get(key, [])
|
||||
has_dynamic_objects = bool(getattr(self.controller, "id_to_object_np", {}) or {})
|
||||
is_root_selection = (
|
||||
self.controller and
|
||||
key == getattr(self.controller, "tree_root_key", None)
|
||||
)
|
||||
is_top_level_heavy_selection = False
|
||||
if self.controller and not is_root_selection:
|
||||
try:
|
||||
root_key = getattr(self.controller, "tree_root_key", None)
|
||||
root_node = self.controller.tree_nodes.get(root_key, {}) if root_key else {}
|
||||
top_level_children = set(root_node.get("children", []) or [])
|
||||
is_top_level_heavy_selection = key in top_level_children and len(self.selected_ids) >= 256
|
||||
except Exception:
|
||||
is_top_level_heavy_selection = False
|
||||
if sync_world_selection:
|
||||
self._clear_editor_selection_visuals()
|
||||
self._sync_editor_selection_reference(self.get_selection_scene_node())
|
||||
|
||||
# Root selection should stay lightweight:
|
||||
# keep static chunks active and transform the model root directly.
|
||||
if is_root_selection:
|
||||
if is_root_selection or is_top_level_heavy_selection or not has_dynamic_objects:
|
||||
self.controller.set_active_ids([])
|
||||
if self._outline_manager:
|
||||
self._outline_manager.clear()
|
||||
|
||||
@ -1321,6 +1321,9 @@ class AppActions:
|
||||
except Exception:
|
||||
normalized_model_path = file_path
|
||||
|
||||
asset_guid = ""
|
||||
asset_path = ""
|
||||
|
||||
if normalized_model_path:
|
||||
model_np.setTag("model_path", normalized_model_path)
|
||||
model_np.setTag("saved_model_path", normalized_model_path)
|
||||
@ -1333,9 +1336,11 @@ class AppActions:
|
||||
asset_record = project_manager.register_project_asset(file_path)
|
||||
if asset_record:
|
||||
if asset_record.get("guid"):
|
||||
model_np.setTag("asset_guid", asset_record["guid"])
|
||||
asset_guid = asset_record["guid"]
|
||||
model_np.setTag("asset_guid", asset_guid)
|
||||
if asset_record.get("asset_path"):
|
||||
model_np.setTag("asset_path", asset_record["asset_path"])
|
||||
asset_path = asset_record["asset_path"]
|
||||
model_np.setTag("asset_path", asset_path)
|
||||
except Exception as e:
|
||||
print(f"[SSBO] register_project_asset failed: {e}")
|
||||
|
||||
@ -1372,6 +1377,37 @@ class AppActions:
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
target_source_child = None
|
||||
last_import_root_name = getattr(ssbo_editor, "last_import_root_name", None) if ssbo_editor else None
|
||||
if source_children:
|
||||
if last_import_root_name:
|
||||
for source_child in source_children:
|
||||
try:
|
||||
if source_child.getName() == last_import_root_name:
|
||||
target_source_child = source_child
|
||||
break
|
||||
except Exception:
|
||||
continue
|
||||
if target_source_child is None:
|
||||
target_source_child = source_children[-1]
|
||||
|
||||
if target_source_child is not None:
|
||||
try:
|
||||
if normalized_model_path:
|
||||
target_source_child.setTag("model_path", normalized_model_path)
|
||||
target_source_child.setTag("saved_model_path", normalized_model_path)
|
||||
if file_path:
|
||||
target_source_child.setTag("original_path", file_path)
|
||||
target_source_child.setTag("file", os.path.basename(file_path))
|
||||
if asset_guid:
|
||||
target_source_child.setTag("asset_guid", asset_guid)
|
||||
if asset_path:
|
||||
target_source_child.setTag("asset_path", asset_path)
|
||||
target_source_child.setTag("is_model_root", "1")
|
||||
target_source_child.setTag("is_scene_element", "1")
|
||||
except Exception as e:
|
||||
print(f"[SSBO] sync source child tags failed: {e}")
|
||||
|
||||
if scene_manager:
|
||||
try:
|
||||
scene_manager.setupCollision(model_np)
|
||||
|
||||
@ -80,6 +80,9 @@ class EditorPanelsLeftMixin:
|
||||
ssbo_model = getattr(ssbo_editor, "model", None) if ssbo_editor else None
|
||||
if ssbo_model and not ssbo_model.isEmpty() and ssbo_model.hasParent():
|
||||
return [ssbo_model]
|
||||
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()]
|
||||
return []
|
||||
|
||||
if hasattr(self.app, "scene_manager") and self.app.scene_manager and hasattr(self.app.scene_manager, "models"):
|
||||
@ -95,11 +98,11 @@ class EditorPanelsLeftMixin:
|
||||
ssbo_editor = getattr(self.app, "ssbo_editor", None)
|
||||
controller = getattr(ssbo_editor, "controller", None) if ssbo_editor else None
|
||||
if not ssbo_editor or not controller or not getattr(controller, "tree_root_key", None):
|
||||
return []
|
||||
return self._get_ssbo_virtual_top_level_model_keys(controller)
|
||||
|
||||
root_node = controller.tree_nodes.get(controller.tree_root_key)
|
||||
if not root_node:
|
||||
return []
|
||||
if not root_node or not root_node.get("children"):
|
||||
return self._get_ssbo_virtual_top_level_model_keys(controller)
|
||||
|
||||
keys = []
|
||||
for child_key in root_node.get("children", []):
|
||||
@ -108,6 +111,35 @@ class EditorPanelsLeftMixin:
|
||||
keys.append(child_key)
|
||||
return keys
|
||||
|
||||
def _get_ssbo_virtual_top_level_model_keys(self, controller):
|
||||
virtual_tree = getattr(controller, "virtual_tree", None) if controller else None
|
||||
if not isinstance(virtual_tree, dict):
|
||||
return []
|
||||
keys = []
|
||||
for child in (virtual_tree.get("children", {}) or {}).values():
|
||||
key = child.get("group_key") or child.get("leaf_key")
|
||||
if not key:
|
||||
continue
|
||||
if not self._ssbo_key_matches_scene_filter(controller, key):
|
||||
continue
|
||||
keys.append(key)
|
||||
return keys
|
||||
|
||||
def _find_ssbo_virtual_node_by_key(self, controller, key):
|
||||
virtual_tree = getattr(controller, "virtual_tree", None) if controller else None
|
||||
if not isinstance(virtual_tree, dict) or not key:
|
||||
return None
|
||||
|
||||
stack = [virtual_tree]
|
||||
while stack:
|
||||
node = stack.pop()
|
||||
if not isinstance(node, dict):
|
||||
continue
|
||||
if node.get("group_key") == key or node.get("leaf_key") == key:
|
||||
return node
|
||||
stack.extend(list((node.get("children", {}) or {}).values()))
|
||||
return None
|
||||
|
||||
def _get_scene_tree_model_display_count(self):
|
||||
models = self._get_scene_tree_models()
|
||||
ssbo_editor = getattr(self.app, "ssbo_editor", None)
|
||||
@ -126,14 +158,24 @@ class EditorPanelsLeftMixin:
|
||||
return True
|
||||
|
||||
node_data = controller.tree_nodes.get(key)
|
||||
if not node_data:
|
||||
if node_data:
|
||||
display_name = controller.display_names.get(key, key)
|
||||
if filter_text in display_name.lower() or filter_text in str(key).lower():
|
||||
return True
|
||||
return any(self._ssbo_key_matches_scene_filter(controller, child_key) for child_key in node_data["children"])
|
||||
|
||||
virtual_node = self._find_ssbo_virtual_node_by_key(controller, key)
|
||||
if not virtual_node:
|
||||
return False
|
||||
|
||||
display_name = controller.display_names.get(key, key)
|
||||
display_name = str(virtual_node.get("display_name", virtual_node.get("name", key)) or key)
|
||||
if filter_text in display_name.lower() or filter_text in str(key).lower():
|
||||
return True
|
||||
|
||||
return any(self._ssbo_key_matches_scene_filter(controller, child_key) for child_key in node_data["children"])
|
||||
return any(
|
||||
self._ssbo_key_matches_scene_filter(controller, child.get("group_key") or child.get("leaf_key"))
|
||||
for child in (virtual_node.get("children", {}) or {}).values()
|
||||
if child.get("group_key") or child.get("leaf_key")
|
||||
)
|
||||
|
||||
def _node_matches_scene_filter(self, node, name):
|
||||
filter_text = self._get_scene_tree_filter()
|
||||
@ -385,7 +427,7 @@ class EditorPanelsLeftMixin:
|
||||
|
||||
node_data = controller.tree_nodes.get(key)
|
||||
if not node_data:
|
||||
return
|
||||
return self._draw_ssbo_virtual_fallback_node(ssbo_editor, controller, key)
|
||||
|
||||
# Skip redundant wrapper nodes (e.g. ROOT), show their children instead.
|
||||
if controller.should_hide_tree_node(key):
|
||||
@ -433,6 +475,59 @@ class EditorPanelsLeftMixin:
|
||||
self._draw_ssbo_virtual_tree_node(ssbo_editor, controller, child_key)
|
||||
imgui.tree_pop()
|
||||
|
||||
def _draw_ssbo_virtual_fallback_node(self, ssbo_editor, controller, key):
|
||||
virtual_node = self._find_ssbo_virtual_node_by_key(controller, key)
|
||||
if not virtual_node:
|
||||
return
|
||||
|
||||
display = str(virtual_node.get("display_name", virtual_node.get("name", key)) or key)
|
||||
obj_count = len(controller.name_to_ids.get(key, []))
|
||||
is_selected = (getattr(ssbo_editor, "selected_name", None) == key)
|
||||
children = list((virtual_node.get("children", {}) or {}).values())
|
||||
|
||||
if display.strip().lower() == "root" and children:
|
||||
for child in children:
|
||||
child_key = child.get("group_key") or child.get("leaf_key")
|
||||
if child_key:
|
||||
self._draw_ssbo_virtual_tree_node(ssbo_editor, controller, child_key)
|
||||
return
|
||||
|
||||
if not children:
|
||||
label = f"{display} ({obj_count})##{key}"
|
||||
if imgui.selectable(label, is_selected)[0]:
|
||||
ssbo_editor.select_node(key)
|
||||
if hasattr(self.app, "lui_manager"):
|
||||
self.app.lui_manager.selected_index = -1
|
||||
if imgui.is_item_hovered() and imgui.is_mouse_clicked(1):
|
||||
try:
|
||||
ssbo_editor.select_node(key)
|
||||
except Exception:
|
||||
pass
|
||||
self._show_node_context_menu(getattr(ssbo_editor, "model", None), display, "model")
|
||||
return
|
||||
|
||||
flags = imgui.TreeNodeFlags_.open_on_arrow
|
||||
if is_selected:
|
||||
flags |= imgui.TreeNodeFlags_.selected
|
||||
label = f"{display} ({obj_count})##{key}"
|
||||
opened = imgui.tree_node_ex(label, flags)
|
||||
if imgui.is_item_clicked(0):
|
||||
ssbo_editor.select_node(key)
|
||||
if hasattr(self.app, "lui_manager"):
|
||||
self.app.lui_manager.selected_index = -1
|
||||
if imgui.is_item_hovered() and imgui.is_mouse_clicked(1):
|
||||
try:
|
||||
ssbo_editor.select_node(key)
|
||||
except Exception:
|
||||
pass
|
||||
self._show_node_context_menu(getattr(ssbo_editor, "model", None), display, "model")
|
||||
if opened:
|
||||
for child in children:
|
||||
child_key = child.get("group_key") or child.get("leaf_key")
|
||||
if child_key:
|
||||
self._draw_ssbo_virtual_tree_node(ssbo_editor, controller, child_key)
|
||||
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