This commit is contained in:
Hector 2026-04-07 10:02:09 +08:00
parent 885535031c
commit 283c891554

View File

@ -1189,15 +1189,31 @@ class ProjectManager:
except Exception:
pass
ssbo_loaded = self._load_scene_description_via_ssbo(scene_description, project_path, asset_database)
use_manual_asset_import = (
not ssbo_loaded
and callable(getattr(self.world, "_import_model_with_menu_logic", None))
)
scene_root, keep_nodes = self._build_scene_root_from_description(
scene_root, keep_nodes, built_nodes = self._build_scene_root_from_description(
scene_description,
project_path,
asset_database,
include_mode="all",
skip_asset_nodes=ssbo_loaded,
skip_asset_nodes=(ssbo_loaded or use_manual_asset_import),
return_node_map=True,
)
manually_imported_models = []
if use_manual_asset_import:
manually_imported_models = self._load_scene_assets_via_manual_import(
scene_description,
project_path,
asset_database,
scene_root,
keep_nodes,
built_nodes,
)
scene_manager = getattr(self.world, "scene_manager", None)
if scene_manager:
scene_manager.models = []
@ -1212,6 +1228,13 @@ class ProjectManager:
except Exception:
root_children = []
manual_model_keys = set()
for manual_model in manually_imported_models:
try:
manual_model_keys.add(id(manual_model))
except Exception:
continue
try:
ssbo_editor = getattr(self.world, "ssbo_editor", None)
runtime_model = getattr(ssbo_editor, "model", None) if ssbo_editor else None
@ -1233,9 +1256,9 @@ class ProjectManager:
if child.hasTag("is_model_root") or child.hasTag("asset_guid"):
built_model_nodes.append(child)
try:
if scene_manager and hasattr(scene_manager, "setupCollision"):
if scene_manager and hasattr(scene_manager, "setupCollision") and id(child) not in manual_model_keys:
scene_manager.setupCollision(child)
if scene_manager and hasattr(scene_manager, "_processModelAnimations"):
if scene_manager and hasattr(scene_manager, "_processModelAnimations") and id(child) not in manual_model_keys:
scene_manager._processModelAnimations(child)
except Exception:
pass
@ -1248,7 +1271,14 @@ class ProjectManager:
if scene_manager:
if not ssbo_loaded:
scene_manager.models = built_model_nodes
merged_models = []
for candidate in list(manually_imported_models) + built_model_nodes:
if not candidate or candidate.isEmpty():
continue
if any(existing == candidate for existing in merged_models):
continue
merged_models.append(candidate)
scene_manager.models = merged_models
else:
merged_models = list(getattr(scene_manager, "models", []) or [])
for child in built_model_nodes:
@ -1352,6 +1382,83 @@ class ProjectManager:
top_level_nodes.append(node)
return top_level_nodes, node_lookup
def _load_scene_assets_via_manual_import(
self,
scene_description,
project_path,
asset_database,
scene_root,
keep_nodes,
built_nodes,
):
import_with_menu = getattr(self.world, "_import_model_with_menu_logic", None)
if not callable(import_with_menu):
return []
top_level_nodes, node_lookup = self._iter_top_level_scene_asset_nodes(scene_description)
imported_models = []
for node in top_level_nodes:
components = dict(node.get("components", {}) or {})
model_component = dict(components.get("model", {}) or {})
imported_node_key = str(
model_component.get("imported_node_key", "")
or node.get("imported_node_key", "")
or ""
).strip()
if imported_node_key:
return []
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:
return []
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:
return []
asset_abs = os.path.join(project_path, asset_rel.replace("/", os.sep))
if not os.path.exists(asset_abs):
return []
imported_np = import_with_menu(
asset_abs,
select_model=False,
set_origin=False,
show_info_message=False,
show_success_message=False,
)
if not imported_np or imported_np.isEmpty():
return []
parent_np = scene_root
parent_id = self._resolve_scene_description_parent(node, keep_nodes, node_lookup)
if parent_id in built_nodes:
parent_np = built_nodes[parent_id]
imported_np.reparentTo(parent_np)
self._apply_scene_description_state_to_subtree(
imported_np,
node,
project_path,
node_lookup,
apply_material_state=True,
prune_missing_children=False,
)
imported_models.append(imported_np)
return imported_models
def _apply_scene_description_state_to_node(self, target_np, node, project_path, apply_material_state=True):
if not target_np or target_np.isEmpty() or not isinstance(node, dict):
return
@ -1447,9 +1554,17 @@ class ProjectManager:
target_np.setTag("has_scripts", "true")
target_np.setTag("scripts_info", json.dumps(scripts, ensure_ascii=False))
if apply_material_state:
if apply_material_state and self._should_restore_saved_material_state(node):
self._apply_saved_material_tags_to_node(target_np)
def _should_restore_saved_material_state(self, node):
if not isinstance(node, dict):
return False
tags = dict(node.get("tags", {}) or {})
if str(tags.get("scene_material_dirty", "") or "").strip().lower() == "true":
return True
return False
def _apply_saved_material_tags_to_node(self, target_np):
"""Rebuild runtime material state from serialized material_* tags."""
if not target_np or target_np.isEmpty():
@ -1590,7 +1705,19 @@ class ProjectManager:
runtime_children = []
try:
runtime_children = [child for child in target_np.getChildren() if child and not child.isEmpty()]
for child in target_np.getChildren():
if not child or child.isEmpty():
continue
child_name = ""
try:
child_name = str(child.getName() or "")
except Exception:
child_name = ""
if child_name.startswith("modelCollision_"):
continue
if child_name.startswith("selectionOutline"):
continue
runtime_children.append(child)
except Exception:
runtime_children = []
@ -1619,10 +1746,17 @@ class ProjectManager:
return ""
runtime_children_by_key = {}
runtime_children_by_name = {}
for runtime_index, runtime_child in enumerate(runtime_children):
key = _runtime_imported_key(runtime_child)
if key and key not in runtime_children_by_key:
runtime_children_by_key[key] = (runtime_index, runtime_child)
try:
runtime_name = str(runtime_child.getName() or "").strip()
except Exception:
runtime_name = ""
if runtime_name:
runtime_children_by_name.setdefault(runtime_name, []).append((runtime_index, runtime_child))
used_runtime_indices = set()
for child_entry in child_nodes:
@ -1638,6 +1772,15 @@ class ProjectManager:
target_runtime_index = keyed_index
target_runtime_child = keyed_child
if target_runtime_child is None:
entry_name = str(child_entry.get("name", "") or "").strip()
for named_index, named_child in runtime_children_by_name.get(entry_name, []):
if named_index in used_runtime_indices:
continue
target_runtime_index = named_index
target_runtime_child = named_child
break
if target_runtime_child is None:
child_index = _node_index(child_entry)
if child_index < 0 or child_index >= len(runtime_children):
@ -2148,7 +2291,7 @@ class ProjectManager:
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):
def _build_scene_root_from_description(self, scene_description, project_path, asset_database, include_mode="all", skip_asset_nodes=False, return_node_map=False):
nodes = list(scene_description.get("nodes", []) or [])
node_lookup = {
str(node.get("node_id", "") or ""): dict(node)
@ -2200,6 +2343,8 @@ class ProjectManager:
)
if rebuilt_np:
built_nodes[node_id] = rebuilt_np
if return_node_map:
return scene_root, keep_nodes, built_nodes
return scene_root, keep_nodes
def saveProject(self):